Getting started
How it works
One job is one deposit. A client pays once into escrow, the operator names the sub-agents who will do the work and the fee each one earns, and settlement pays exactly the tasks that were completed. Whatever is left is the agency's margin. Every step is written to a public log before the contract call that performs it.
Two contracts split the responsibility. contracts/AetherisAgency.sol (0x16fA…bc81) owns jobs, tasks and the operator registry; contracts/AetherisTreasury.sol (0x1036…5598) holds the money. The agency is the only contract that may move escrow, and the treasury's owner may claim margin but can never touch escrow - the two are kept apart on purpose.
The loop
The seed script (scripts/seed.js) drives this exact sequence against the testnet deployment, so every step below has already happened on chain at least once.
- Fund. The client approves the agency (not the treasury) to spend the deposit, then calls
createJob(token, deposit, specURI). The agency moves the tokens straight into the treasury and callsrecordEscrow, which re-reads its own balance and reverts withSolvencyCheckFailedif the funds did not actually land. The job is nowFunded. Deposits can be an HTS token (aUSD) or a plain ERC-20 (aUSDC). - Assign. The operator calls
assignSubAgent(jobId, subAgent, fee, role)once per task. The contract adds the fee tocommittedFeesand reverts withFeeExceedsDepositif the total would pass the deposit, so a job is always solvent before any work starts. The first assignment moves the job toDispatched. - Complete. The sub-agent (or the operator on its behalf) calls
completeTask(jobId, taskId, resultHash, hcsTopicId, hcsSequenceNumber). The result hash and the HCS coordinates are stored and two events fire:TaskCompletedadvances state,HcsLogAnchoredis the audit anchor. When every task is complete the job becomesCompleted. - Settle. The operator calls
settleJob(jobId). The contract marks the jobSettledfirst, then loops over the tasks and pays each one inCompletedstate throughtreasury.settleSubAgent. Assigned-but-unfinished tasks are skipped, so their fee is never paid and falls through into margin. FinallycloseEscrowpromotes the remainder toretainedMargin. See Settlement rails for how each payout picks HTS or ERC-20. - Anchor. Before each of the calls above, the operator writes a compact JSON frame to Hedera Consensus Service topic 0.0.10518320. For
completeTaskthe sequence number the network assigns is what gets passed into the contract, so the on-chainhcsSequenceNumbermatches the mirror node exactly. Settlement is the one step recorded after the fact, because its amounts andviaHtsflags are read from the receipt's events rather than assumed (scripts/seed.js). See Audit log. - Index. A self-hosted graph-node follows both contracts through the Hedera JSON-RPC relay and turns the event stream into
Job,Task,SettlementandHcsAnchorentities (subgraph/schema.graphql). Gross revenue, payouts and margin are reconciled from events, not copied from the contract. See The Graph subgraph. - Sweep. Margin sits in the treasury until the operator calls
claimProfit(token, amount, to). That call isonlyOwnerand also asks the agency's registryisVerifiedOperator(msg.sender); an operator who has not cleared World ID getsOperatorNotVerified. The nullifier that verified them is echoed intoProfitClaimed. One real sweep of 0.5 aUSD has happened on testnet.
Where the money sits at each step
totalObligations[token] = sum(open escrows) + retainedMargin[token] createJob client -> treasury escrow[jobId] += deposit settleSubAgent treasury -> sub-agent escrow[jobId] -= fee (per Completed task) closeEscrow escrow -> retained margin retainedMargin += remainder refundJob treasury -> client escrow[jobId] = 0 claimProfit treasury -> operator retainedMargin -= amount (World ID gated)
The treasury never trusts arithmetic done in the agency: recordEscrow checks that balanceOf(address(this)) covers totalObligations after every deposit, which also rejects fee-on-transfer tokens instead of silently under-funding a job.
Two rails, one event
On Hedera the treasury pays through the Hedera Token Service system contract at 0x0000000000000000000000000000000000000167. If the token is not an HTS entity, or HTS declines the transfer, it drops to a plain ERC-20 transfer. Either way the same MicroSettlement event fires, and its viaHts flag records which rail actually ran. On testnet today six settlements went over HTS (jobs 1 and 5) and four over ERC-20 (jobs 2 and 6).
What is and is not on chain
- The sub-agent work in the seed is simulated by the script: it commits a result hash, and no model call is part of the contracts.
- World ID proofs are verified server-side and relayed; there is no World ID router on Hedera, so the contract runs in explicit bypass mode and only the ZK check is skipped. Replay protection stays active.
- The relayer, faucet and HCS submit key are a single deployer key, and everything runs on testnet. See Trust model & FAQ.
Read next
- Jobs & escrow - the exact function signatures, statuses and refund rules.
- Settlement rails - HTS response codes, association and the measured gas cost of each path.
