Skip to content

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.

  1. 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 calls recordEscrow, which re-reads its own balance and reverts with SolvencyCheckFailed if the funds did not actually land. The job is now Funded. Deposits can be an HTS token (aUSD) or a plain ERC-20 (aUSDC).
  2. Assign. The operator calls assignSubAgent(jobId, subAgent, fee, role) once per task. The contract adds the fee to committedFees and reverts with FeeExceedsDeposit if the total would pass the deposit, so a job is always solvent before any work starts. The first assignment moves the job to Dispatched.
  3. 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: TaskCompleted advances state, HcsLogAnchored is the audit anchor. When every task is complete the job becomes Completed.
  4. Settle. The operator calls settleJob(jobId). The contract marks the job Settled first, then loops over the tasks and pays each one in Completed state through treasury.settleSubAgent. Assigned-but-unfinished tasks are skipped, so their fee is never paid and falls through into margin. Finally closeEscrow promotes the remainder to retainedMargin. See Settlement rails for how each payout picks HTS or ERC-20.
  5. Anchor. Before each of the calls above, the operator writes a compact JSON frame to Hedera Consensus Service topic 0.0.10518320. For completeTask the sequence number the network assigns is what gets passed into the contract, so the on-chain hcsSequenceNumber matches the mirror node exactly. Settlement is the one step recorded after the fact, because its amounts and viaHts flags are read from the receipt's events rather than assumed (scripts/seed.js). See Audit log.
  6. Index. A self-hosted graph-node follows both contracts through the Hedera JSON-RPC relay and turns the event stream into Job, Task, Settlement and HcsAnchor entities (subgraph/schema.graphql). Gross revenue, payouts and margin are reconciled from events, not copied from the contract. See The Graph subgraph.
  7. Sweep. Margin sits in the treasury until the operator calls claimProfit(token, amount, to). That call is onlyOwner and also asks the agency's registry isVerifiedOperator(msg.sender); an operator who has not cleared World ID gets OperatorNotVerified. The nullifier that verified them is echoed into ProfitClaimed. One real sweep of 0.5 aUSD has happened on testnet.

Where the money sits at each step

per token, inside AetherisTreasury
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.
  • Jobs & escrow - the exact function signatures, statuses and refund rules.
  • Settlement rails - HTS response codes, association and the measured gas cost of each path.