Skip to content

Protocol

Contracts & HTTP API

Two contracts on Hedera testnet hold the money and the rules; a handful of Next.js route handlers keep the keys that cannot live in a browser. This page is the reference for both: addresses, every external function, the frozen event set, and each HTTP route with its exact body, success shape and error codes.

Sources: contracts/ (AetherisAgency.sol, AetherisTreasury.sol, HederaTokenServiceLib.sol, interfaces/*.sol), covered by 37 Hardhat tests in test/aetheris.test.js. Route handlers are under app/api/**/route.ts; the shared error envelope is app/api/_lib/http.ts.

Addresses and blocks

Deployed contracts, tokens and accounts on Hedera testnet
NetworkHedera testnet, chain id 296JSON-RPC relay https://testnet.hashio.io/api (rejects eth_getLogs inside JSON-RPC batches); mirror node https://testnet.mirrornode.hedera.com
AetherisAgency0x16fA9CC838Ab5380F0Ebe3C261a2F57E0FBAbc81deploy block 40,396,781
AetherisTreasury0x10360383a6b43Fd22BE257bE334E9A9ad83B5598deploy block 40,396,776
HTS system contract0x0000000000000000000000000000000000000167same address on every Hedera network; returns int64 response codes, SUCCESS = 22
aUSD (HTS token)0.0.104846736 decimals; EVM alias 0x00000000000000000000000000000000009ffBC1
aUSDC (test ERC-20)0x21DCc52AbbCAef92B4573dc8B0e1658417c859616 decimals; open mint (used by the faucet)
Operator0.0.10484502EVM alias 0x69677C85945796066B449c00F90A0582896F1F9b; owner of both contracts, deployer, relayer, faucet and HCS submit key
Seeded sub-agents0.0.104846740.0.104846760.0.10484678
HCS audit topic0.0.10518320

State today: 7 jobs created, 4 settled, one real claimProfit of 0.5 aUSD. The measured cost of a settlement is 2,360,527 gas over HTS against 229,111 gas over ERC-20.

AetherisAgency

The agency owns the job and task state machines and the World ID operator registry. It is Ownable (the owner is the operator) and ReentrancyGuarded where it hands control to token contracts. Job statuses: Funded, Dispatched, Completed, Settled, Refunded. Task statuses: Assigned, Completed, Paid, Cancelled.

  • Function
    constructor(initialOwner, treasury, worldIdRouter, worldIdGroupId, worldIdAppId, worldIdAction, ensName)
    Who may call
    Deployer
    Emits
    AgencyDeployed WorldIdBypassActive (if router = 0)
    Notes
    Binds the agency to its treasury and derives externalNullifierHash from the World ID app id and action. AgencyDeployed carries nullifier 0; personhood is established afterwards.
  • Function
    verifyOperator(address signal, uint256 root, uint256 nullifierHash, uint256[8] proof, string ensName)
    Who may call
    Anyone (the app relays it with the deployer key)
    Emits
    OperatorVerified OperatorVerifiedWithoutProof (bypass)
    Notes
    Burns nullifierHash first (NullifierAlreadyUsed on replay, InvalidNullifier for 0), then calls the router's verifyProof if one is set. Marks signal verified and records its ENS name.
  • Function
    setWorldId(address router, uint256 groupId)
    Who may call
    Owner
    Emits
    WorldIdRouterUpdated WorldIdBypassActive (if router = 0)
    Notes
    Points the agency at a World ID router, or clears it to re-enter bypass mode.
  • Function
    createJob(address token, uint256 deposit, string specURI) returns (uint256 jobId)
    Who may call
    Anyone - the caller becomes the client
    Emits
    JobCreated
    Notes
    nonReentrant. Pulls deposit via safeTransferFrom straight into the treasury, then calls treasury.recordEscrow. The client must approve the agency, not the treasury. Job ids start at 1.
  • Function
    assignSubAgent(uint256 jobId, address subAgent, uint256 fee, string role) returns (uint256 taskId)
    Who may call
    Owner
    Emits
    SubAgentAssigned
    Notes
    Job must be Funded or Dispatched. Reverts with FeeExceedsDeposit if committed fees would exceed the deposit, so a job is always solvent before work begins. Moves the job to Dispatched.
  • Function
    completeTask(uint256 jobId, uint256 taskId, bytes32 resultHash, string hcsTopicId, uint64 hcsSequenceNumber)
    Who may call
    The task's sub-agent or the owner
    Emits
    TaskCompleted HcsLogAnchored
    Notes
    Task must be Assigned; EmptyHcsTopic if the topic id is empty. Marks the job Completed when every task is complete. The sequence number is the one returned by the HCS receipt - see Audit log.
  • Function
    cancelTask(uint256 jobId, uint256 taskId)
    Who may call
    Owner
    Emits
    TaskCancelled
    Notes
    Task must be Assigned. Releases its fee back into the job's uncommitted balance (future margin).
  • Function
    settleJob(uint256 jobId) returns (uint256 paidToSubAgents, uint256 netMargin)
    Who may call
    Owner
    Emits
    JobSettled (+ one MicroSettlement per paid task, from the treasury)
    Notes
    nonReentrant. Job must be Dispatched or Completed; status is set to Settled before any payout. Pays every Completed task through treasury.settleSubAgent, then closeEscrow sweeps the remainder into retained margin. Unfinished work is never paid.
  • Function
    refundJob(uint256 jobId) returns (uint256 refunded)
    Who may call
    The client or the owner
    Emits
    TaskCancelled (per open task) JobRefunded
    Notes
    nonReentrant. Job must be Funded or Dispatched (InvalidJobStatus otherwise); any other caller gets NotClientOrOperator. Cancels Assigned and Completed tasks and returns the whole escrow to the client.
  • Function
    getJob(jobId) / getTask(jobId, taskId) / getTasks(jobId) / taskLength(jobId) / jobStatus(jobId)
    Who may call
    View
    Emits
    -
    Notes
    Job and Task structs and counts. UnknownJob / UnknownTask on bad ids.
  • Function
    isVerifiedOperator(address) / operatorNullifier(address) / operatorEnsName(address) / nullifierHashUsed(uint256)
    Who may call
    View
    Emits
    -
    Notes
    The operator registry the treasury reads before releasing profit (IAetherisOperatorRegistry).
  • Function
    worldIdVerificationBypassed() / externalNullifierHash() / worldIdGroupId() / jobCount()
    Who may call
    View
    Emits
    -
    Notes
    worldIdVerificationBypassed() is true whenever no router is configured - it is true on the testnet deployment.

Custom errors

ZeroAddress, ZeroAmount, UnknownJob(jobId), UnknownTask(jobId, taskId), InvalidJobStatus(jobId, actual), InvalidTaskStatus(jobId, taskId, actual), FeeExceedsDeposit(jobId, committed, deposit), NotTaskOwner(jobId, taskId, caller), NotClientOrOperator(jobId, caller), NullifierAlreadyUsed(nullifierHash), InvalidNullifier, EmptyHcsTopic

AetherisTreasury

The treasury holds every token the system touches and enforces one invariant: for each token, balanceOf(this) >= totalObligations[token], where obligations are open escrows plus retained margin. Only the wired agency can open, spend or close an escrow (onlyAgency); only the owner can move margin, and only if the agency says that owner is a verified human.

  • Function
    constructor(address initialOwner)
    Who may call
    Deployer
    Emits
    -
    Notes
    The owner is the human operator who will own the retained margin.
  • Function
    setAgency(address newAgency)
    Who may call
    Owner
    Emits
    AgencyUpdated
    Notes
    Wires the one contract allowed to open, spend and close escrows. Also the registry consulted by claimProfit.
  • Function
    setHtsEnabled(bool enabled)
    Who may call
    Owner
    Emits
    HtsEnabledUpdated
    Notes
    Circuit breaker. When false, every payout goes straight to ERC-20 transfer.
  • Function
    associateToken(address token) returns (int64 responseCode)
    Who may call
    Owner
    Emits
    TokenAssociated
    Notes
    Must be called once per HTS token before the treasury can be funded in it. Accepts response code 22 (SUCCESS) or 194 (TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT). Reverts HtsCallFailed on a chain without the HTS system contract.
  • Function
    dissociateToken(address token) returns (int64 responseCode)
    Who may call
    Owner
    Emits
    TokenDissociated
    Notes
    Fails at the HTS level if the treasury still holds a balance of the token.
  • Function
    recordEscrow(uint256 jobId, address token, uint256 amount)
    Who may call
    Agency only
    Emits
    EscrowRecorded
    Notes
    Called after the agency has moved the deposit in. Adds to totalObligations[token] and reverts SolvencyCheckFailed if balanceOf(this) is below the new total - the transfer is proven by balance, not trusted.
  • Function
    settleSubAgent(uint256 jobId, uint256 taskId, address subAgent, uint256 amount) returns (bool viaHts)
    Who may call
    Agency only
    Emits
    MicroSettlement HtsPayoutFallback (if HTS failed)
    Notes
    nonReentrant. Debits the job's escrow and pays HTS-first with an ERC-20 fallback; viaHts records which rail moved the funds. InsufficientEscrow if the escrow cannot cover it.
  • Function
    closeEscrow(uint256 jobId) returns (uint256 margin)
    Who may call
    Agency only
    Emits
    -
    Notes
    Moves whatever is left in the escrow into retainedMargin[token]. Obligations are unchanged - the funds stay owed, now to the operator instead of the job.
  • Function
    refundEscrow(uint256 jobId, address client) returns (uint256 refunded)
    Who may call
    Agency only
    Emits
    EscrowRefunded
    Notes
    nonReentrant. Returns the remaining escrow to the client over the same dual rail.
  • Function
    claimProfit(address token, uint256 amount, address to)
    Who may call
    Owner, and must be a verified operator in the agency
    Emits
    ProfitClaimed
    Notes
    nonReentrant. AgencyNotSet if no agency is wired, OperatorNotVerified if isVerifiedOperator(msg.sender) is false, InsufficientMargin if retainedMargin[token] is short. The event echoes the operator's nullifier so the payout links back to the proof that authorised it.
  • Function
    rebalance(address fromToken, address toToken, uint256 amountIn, uint256 amountOut, uint256 dstChainId, bytes32 oneInchTxHash)
    Who may call
    Owner
    Emits
    TreasuryRebalanced
    Notes
    nonReentrant. Accounting record for a 1inch swap executed off-chain: debits amountIn of margin in fromToken and, for same-chain swaps only, credits amountOut in toToken. No token movement happens in this call.
  • Function
    sweepSurplus(address token, address to) returns (uint256 surplus)
    Who may call
    Owner
    Emits
    SurplusSwept
    Notes
    Moves only balanceOf(this) - totalObligations[token], so it can never touch escrow or retained margin. For airdrops and accidental transfers.
  • Function
    escrowToken(jobId) / escrowBalance(jobId) / isEscrowOpen(jobId)
    Who may call
    View
    Emits
    -
    Notes
    Per-job escrow state.
  • Function
    htsAvailable() / isHtsToken(address) / retainedMargin(address) / totalObligations(address) / agency() / htsEnabled()
    Who may call
    View
    Emits
    -
    Notes
    htsAvailable() probes 0x167 with a staticcall and is false on Hardhat and every non-Hedera chain. isHtsToken requires a full 64-byte (int64, bool) reply with code 22.

Custom errors

NotAgency(caller), ZeroAddress, ZeroAmount, AgencyNotSet, EscrowAlreadyOpen(jobId), EscrowNotOpen(jobId), InsufficientEscrow(jobId, requested, available), InsufficientMargin(token, requested, available), SolvencyCheckFailed(token, held, required), OperatorNotVerified(operator), TokenMismatch(expected, actual); from HederaTokenServiceLib: HtsCallFailed(selector, responseCode), HtsAmountOverflow(amount)

The dual rail lives in the private _payout: if htsEnabled and HederaTokenServiceLib.isHtsToken(token) both hold, it tries transferToken on 0x167; otherwise, or if HTS answers anything but 22, it emits HtsPayoutFallback with the response code and uses SafeERC20. The library maps empty or malformed return data to UNKNOWN = 21 rather than 0, so a call to an address with no code (every non-Hedera chain) can never look like success. See Settlement rails.

Events

Ten event signatures are frozen in contracts/interfaces/IAetherisEvents.sol. The contracts implement them verbatim and subgraph/subgraph.yaml indexes them verbatim; neither side may change a name, arity, type or indexed flag without updating the interface first and regenerating the subgraph ABI.

contracts/interfaces/IAetherisEvents.sol
// contracts/interfaces/IAetherisEvents.sol - FROZEN. Implemented verbatim by contracts/,
// indexed verbatim by subgraph/. Never change a signature without regenerating both.

// Identity & governance
event AgencyDeployed(address indexed agency, address indexed operator, string ensName, uint256 nullifierHash);
event OperatorVerified(address indexed operator, uint256 nullifierHash, string ensName);

// Job lifecycle
event JobCreated(uint256 indexed jobId, address indexed client, address indexed token, uint256 deposit, string specURI);
event SubAgentAssigned(uint256 indexed jobId, uint256 indexed taskId, address indexed subAgent, address token, uint256 fee, string role);
event TaskCompleted(uint256 indexed jobId, uint256 indexed taskId, bytes32 resultHash, string hcsTopicId, uint64 hcsSequenceNumber);
event MicroSettlement(uint256 indexed jobId, uint256 indexed taskId, address indexed subAgent, address token, uint256 amount, bool viaHts);
event JobSettled(uint256 indexed jobId, uint256 grossDeposit, uint256 paidToSubAgents, uint256 netMargin);

// Treasury operations
event TreasuryRebalanced(address indexed fromToken, address indexed toToken, uint256 amountIn, uint256 amountOut, uint256 dstChainId, bytes32 oneInchTxHash);
event ProfitClaimed(address indexed operator, address indexed token, uint256 amount, uint256 nullifierHash);
event HcsLogAnchored(uint256 indexed jobId, bytes32 messageHash, string topicId, uint64 sequenceNumber);

Each contract also emits operational events that are visible on HashScan but outside the frozen set, and therefore not indexed by the subgraph:

contract-local events
// AetherisAgency.sol - operational events, not part of the frozen set and not indexed
event WorldIdBypassActive(address indexed agency, string reason);
event OperatorVerifiedWithoutProof(address indexed operator, uint256 nullifierHash);
event WorldIdRouterUpdated(address indexed router, uint256 groupId);
event JobRefunded(uint256 indexed jobId, address indexed client, address indexed token, uint256 amount);
event TaskCancelled(uint256 indexed jobId, uint256 indexed taskId, address indexed subAgent);

// AetherisTreasury.sol
event TokenAssociated(address indexed token, int64 responseCode);
event TokenDissociated(address indexed token, int64 responseCode);
event HtsPayoutFallback(address indexed token, address indexed to, uint256 amount, int64 responseCode);
event AgencyUpdated(address indexed previousAgency, address indexed newAgency);
event HtsEnabledUpdated(bool enabled);
event EscrowRecorded(uint256 indexed jobId, address indexed token, uint256 amount);
event EscrowRefunded(uint256 indexed jobId, address indexed client, address indexed token, uint256 amount);
event SurplusSwept(address indexed token, address indexed to, uint256 amount);

HTTP API

Every route runs on the Node runtime with dynamic = "force-dynamic". Bodies must be JSON objects (arrays, primitives and malformed JSON are a 400). Errors share one envelope with a real status code and a stable code:

error envelope - app/api/_lib/http.ts
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "`limit` must be an integer between 1 and 50.",
    "details": "optional, human-readable"
  }
}
  • 400 BAD_REQUEST - validation; 401 PROOF_REJECTED - World ID said no; 403 OPERATOR_NOT_VERIFIED / NULLIFIER_MISMATCH - HCS write by an unverified operator; 405 METHOD_NOT_ALLOWED; 409 NULLIFIER_ALREADY_USED; 429 RATE_LIMITED (with a retry-after header where the limiter is per-IP); 502 UPSTREAM_ERROR - Hedera, World ID or 1inch failed; 503 NOT_CONFIGURED and its specific variants - a required env var is missing, and nothing was attempted.
  • Rate limiters are in-memory per process: 5 per minute per IP for POST /api/hcs and POST /api/operator/verify; 3 per address per hour and 30 per hour overall for the faucet.

GET /api/hcs

GET /api/hcs
Fileapp/api/hcs/route.ts
Querylimit integer 1 to 50 (default 12); topicId optional shard.realm.num, defaults to HEDERA_HCS_TOPIC_ID
Success200 { topicId, source: "live" | "demo", notice?, messages[] } - frames newest first with corrections already applied
Errors400 on a bad limit or topicId. Never 5xx: an unset topic, an empty topic or an unreachable mirror node all answer 200 with source: "demo" and a notice.
response
{
  "topicId": "0.0.10518320",
  "source": "live",            // or "demo", with a "notice" explaining why
  "messages": [
    { "sequenceNumber": "27", "contents": "{\"evt\":\"Correction\",...}", "consensusTimestamp": "1789280736.723520104" }
  ]
}

POST /api/hcs

POST /api/hcs
Body{ message, topicId? } - message is a JSON string of at most 1024 characters matching exactly {evt, operator, amount, nullifier} with evt = "ProfitClaimed"; amount is a formatted USD figure; nullifier a uint256 string or null. topicId, if present, must equal the configured topic.
ChecksisVerifiedOperator(operator) must be true on-chain, and a supplied nullifier must equal operatorNullifier(operator). The server composes the frame (adds agency, ts, src: "aetheris/api/hcs") and signs with the operator key.
Success201 { topicId, sequenceNumber, transactionId, frame }
Errors400 (shape, extra fields, wrong topic); 403 OPERATOR_NOT_VERIFIED, NULLIFIER_MISMATCH; 429 RATE_LIMITED; 502 UPSTREAM_ERROR (RPC read or HCS submit failed); 503 NOT_CONFIGURED (no topic or no agency address).
request / response
// request
{ "message": "{\"evt\":\"ProfitClaimed\",\"operator\":\"0x6967...1F9b\",\"amount\":\"$0.50\",\"nullifier\":\"3319...2123\"}" }

// 201
{ "topicId": "0.0.10518320", "sequenceNumber": "28", "transactionId": "0.0.10484502@...", "frame": "<the server-composed JSON that was anchored>" }

POST /api/faucet

POST /api/faucet
Fileapp/api/faucet/route.ts
Body{ address }
EffectSends 1 HBAR if the wallet holds under 0.5, then mints 10 aUSDC via the test token's open mint. Signs with PRIVATE_KEY. Refuses unless the RPC reports chain 296.
Success200 { ok, to, hbar, token }
Errors400; 429 RATE_LIMITED (3 drips per address per hour); 503 FAUCET_NOT_CONFIGURED, WRONG_CHAIN; 502 UPSTREAM_ERROR.
request / response
// request
{ "address": "0x..." }

// 200
{
  "ok": true,
  "to": "0x...",
  "hbar": { "amount": "1", "tx": "0x...", "hashscan": "https://hashscan.io/testnet/transaction/0x..." },
        // or { "skipped": "wallet already holds >= 0.5 HBAR" }
  "token": { "symbol": "aUSDC", "amount": "10", "address": "0x21DCc52AbbCAef92B4573dc8B0e1658417c85961", "tx": "0x...", "hashscan": "..." }
}

POST /api/worldid/rp-context

POST /api/worldid/rp-context
Fileapp/api/worldid/rp-context/route.ts
Body{ action } - non-empty, at most 256 characters
EffectSigns an IDKit v4 request context with WORLD_ID_RP_SIGNING_KEY (TTL 300 s) so the key never reaches the browser.
Success200 { rp_context: { rp_id, nonce, created_at, expires_at, signature } }
Errors400; 503 NOT_CONFIGURED when WORLD_ID_RP_ID or the signing key is unset, or signing fails.
request / response
// request
{ "action": "aetheris-operator" }

// 200
{ "rp_context": { "rp_id": "...", "nonce": "...", "created_at": 1789280000, "expires_at": 1789280300, "signature": "..." } }

POST /api/operator/verify

POST /api/operator/verify
Fileapp/api/operator/verify/route.ts
Body{ result, signal, ensName? } (IDKit 4.0) or { proof, signal, ensName? } (legacy). signal is the operator's EVM address; ensName must look like an ENS name. No other fields.
EffectVerifies at https://developer.worldcoin.org/api/v4/verify/{rp_id} (or the legacy verifier), checks nullifierHashUsed, then relays verifyOperator(signal, 0, nullifierHash, [0 x 8], ensName) with the deployer key and waits one confirmation.
Success200 { ok, txHash, nullifierHash, hashscan, signal, ensName, blockNumber, worldIdBypassed, verificationLevel }
Errors400 BAD_REQUEST; 401 PROOF_REJECTED; 409 NULLIFIER_ALREADY_USED (pre-check or revert); 429 RATE_LIMITED; 502 UPSTREAM_ERROR; 503 WORLD_ID_NOT_CONFIGURED, RELAYER_NOT_CONFIGURED. GET answers 405.
request / response
// request (World ID 4.0)
{ "result": { "protocol_version": "4.0", "responses": [ ... ] }, "signal": "0x<operator address>", "ensName": "aetheris.eth" }
// or legacy (3.0)
{ "proof": { "merkle_root": "...", "nullifier_hash": "...", "proof": "...", "verification_level": "orb" }, "signal": "0x...", "ensName": "..." }

// 200
{
  "ok": true,
  "txHash": "0x...",
  "nullifierHash": "0x...",              // 32-byte hex
  "hashscan": "https://hashscan.io/testnet/transaction/0x...",
  "signal": "0x...",
  "ensName": "aetheris.eth",             // defaults to aetheris.eth when omitted
  "blockNumber": 40453000,
  "worldIdBypassed": true,               // contract ran without a router - true on testnet
  "verificationLevel": "orb"
}

POST /api/verify-worldid

POST /api/verify-worldid
Fileapp/api/verify-worldid/route.ts
BodyAn IDKit 4.0 result (bare or as { result }), or legacy { proof: { merkle_root, nullifier_hash, proof, verification_level }, signal }
EffectVerification only - nothing is sent on-chain. The operator flow uses /api/operator/verify instead.
Success200 { success: true, version?, nullifierHash, verificationLevel }
Errors400; 401 PROOF_REJECTED; 502 UPSTREAM_ERROR; 503 NOT_CONFIGURED. GET answers 405.
request / response
// request: an IDKit 4.0 result, either as the body itself or wrapped as { "result": ... }
// 200
{ "success": true, "version": "4", "nullifierHash": "...", "verificationLevel": "orb" }

// request: legacy { "proof": {...}, "signal": "<string>" }
// 200
{ "success": true, "nullifierHash": "...", "verificationLevel": "orb" }

POST /api/swap/quote and POST /api/swap/build

1inch proxy routes
Filesapp/api/swap/quote/route.ts, app/api/swap/build/route.ts
Body{ chainId, src, dst, amount }; build adds from and optional slippage (greater than 0, at most 50). amount is a decimal string in base units; src and dst must differ.
ChainsEthereum 1, Arbitrum 42161, Base 8453, Optimism 10, Polygon 137. Hedera (296) is not supported by 1inch and is rejected with a 400 listing the supported chains.
Successquote: 200 { quote, chainId }; build: 200 { tx: { to, data, value, gas? }, chainId } - unsigned calldata, never broadcast by the server
Errors400; 502 UPSTREAM_ERROR (upstream failure, including a missing ONEINCH_API_KEY); 503 NOT_CONFIGURED only if the 1inch module itself fails to load. The key never reaches the browser.
request / response
// POST /api/swap/quote
{ "chainId": 8453, "src": "0x...", "dst": "0x...", "amount": "1000000" }
// 200
{ "quote": { "srcToken": "0x...", "dstToken": "0x...", "srcAmount": "1000000", "dstAmount": "...", "estimatedGas": "...", "protocols": [...] }, "chainId": 8453 }

// POST /api/swap/build
{ "chainId": 8453, "src": "0x...", "dst": "0x...", "amount": "1000000", "from": "0x<wallet>", "slippage": 1 }
// 200 - unsigned; nothing is broadcast
{ "tx": { "to": "0x...", "data": "0x...", "value": "0", "gas": "..." }, "chainId": 8453 }

GET /api/ens

GET /api/ens
Fileapp/api/ens/route.ts
Queryexactly one of address (20-byte hex) or name (dot-separated ENS name)
Success200 { address, name, avatar, source: "live" | "unconfigured" }
Errors400 when neither or a malformed parameter is given. Resolution failures are 200 with null fields.
request / response
// GET /api/ens?name=vitalik.eth   or   /api/ens?address=0x...
// 200 (cache-control: public, max-age=300, stale-while-revalidate=1800)
{ "address": "0x...", "name": "vitalik.eth", "avatar": "https://..." | null, "source": "live" }
// unresolvable names answer 200 with null fields and "source": "unconfigured"