Roles
The client
A client is any wallet that funds a job. You sign in with a passkey, get testnet HBAR and aUSDC from the faucet, approve the agency and deposit once. From then on the deposit sits in the treasury under your job id, and you can take it back any time before settlement.
Signing in with a passkey
The sidebar's "Sign in with passkey" button (components/connect-button.tsx) opens Privy. lib/privy.ts offers email, Google, passkey and external wallet, in that order, and creates an embedded wallet on login for users who have none (createOnLogin: "users-without-wallets"). The default chain is Hedera testnet, chain id 296 (lib/chains.ts), so the embedded wallet is ready to sign Aetheris transactions without a network switch. After login the wallet menu shows the full address, a copy action and a reverse ENS lookup.
components/app/role-context.tsx then decides what you are. If the wallet equals the operator, the Operator view opens. If the subgraph has indexed at least one job with your wallet as client, the Client view opens and the sidebar status line reads "Client - N jobs funded by 0x...". A wallet with no jobs yet sees "has no jobs yet - switch to Client to fund one"; the toggle switches the view and remembers the choice.
The faucet
A fresh embedded wallet has no HBAR for gas and no tokens. The "Testnet faucet (1 HBAR + 10 aUSDC)" button on the fund form calls the faucet with your wallet address; you can also call it directly.
| Route | POST /api/faucet (app/api/faucet/route.ts) |
|---|---|
| Body | {"address": "0x..."} |
| Drips | 1 HBAR if the wallet holds under 0.5 HBAR (skipped otherwise), then 10 aUSDC minted from the open MockERC20.mint at 0x21DC…5961. |
| Limits | 3 drips per address per hour and 30 per hour overall, in memory. |
| Chain | Refuses unless the RPC reports chain id 296. |
| Signer | The deployer key (PRIVATE_KEY). |
curl -s -X POST http://localhost:3000/api/faucet \
-H 'content-type: application/json' \
-d '{"address":"0xYourWallet"}'
# 200
{ "ok": true, "to": "0x...",
"hbar": { "amount": "1", "tx": "0x...", "hashscan": "https://hashscan.io/testnet/transaction/0x..." },
"token": { "symbol": "aUSDC", "amount": "10", "address": "0x21DCc52AbbCAef92B4573dc8B0e1658417c85961",
"tx": "0x...", "hashscan": "https://hashscan.io/testnet/transaction/0x..." } }
# when the wallet already holds 0.5 HBAR or more:
"hbar": { "skipped": "wallet already holds >= 0.5 HBAR" }400 | address is not a 20-byte EVM address. |
|---|---|
429 RATE_LIMITED | "Faucet limit reached: 3 drips per address per hour." |
502 | A transfer or mint failed on the relay. |
503 FAUCET_NOT_CONFIGURED | PRIVATE_KEY is not set on the server. |
503 WRONG_CHAIN | The configured RPC is not Hedera testnet. |
The faucet drips the ERC-20 aUSDC only. The HTS token aUSD needs a token association on the receiving account before it can be held, which an embedded wallet does not have by default, so the browser path uses the ERC-20 rail. See Settlement rails.
Funding a job
The "New job" card (components/app/fund-job-card.tsx) takes a token, a deposit and a spec URI (default ipfs://bafybeiaetherisclientjobspec). The token list is every token the subgraph has seen a job funded in. Submitting runs approveAndCreateJob in lib/write.ts, signed in the browser with viem against the wallet's EIP-1193 provider on chain 296:
- Switch chain.
getProvider()asks the wallet to switch to 296 first. Embedded wallets accept; an external wallet may prompt. - Approve the agency.
allowance(you, agency)is read; if it is below the deposit,approve(agency, deposit)is signed and mined. The spender isAetherisAgency(0x16fA…bc81), not the treasury: the agency pulls the deposit and hands it to the treasury itself. The step is skipped when a previous approval still covers the amount. - createJob.
createJob(token, deposit, specURI)is signed with a fixed gas limit of 1,000,000. That is generous for an ERC-20 deposit; it is set that high because an HTS deposit routes through the token service system contract and needs far more than a plain transfer. The receipt is awaited and a non-success status throws.
Each step is listed under the form as it happens - approve - signing, pending with a HashScan link, mined, then the same for createJob. On success the notice links the createJob transaction and says the subgraph indexes it within about 10 seconds; the page refreshes itself 8 seconds later, and the job then appears under "Your jobs" here, in the operator's Job pipeline, and on the agency page. Failures are translated by explainWriteError: a rejected signature, not enough HBAR, or a named contract error such as ZeroAmount.
The button is disabled, with the reason printed beside it, whenever one of these holds:
NEXT_PUBLIC_PRIVY_APP_ID is not set - no wallet can sign.Wallet initialising...Sign in to fund a job.No settlement token is known yet.No HBAR for gas - use the testnet faucet.No aUSDC to deposit - use the testnet faucet.Enter a positive amount.Amount exceeds your balance.
Your jobs, deposits, refundable
components/app/client-workspace.tsx filters the indexed jobs to those whose client is your wallet and shows three tiles:
- Jobs funded - the count, with "by your wallet" when signed in.
- Total deposited - the sum of
depositRawper token symbol, formatted with each token's decimals; escrowed onAetherisTreasury. - Refundable - how many of your jobs are still
FundedorDispatched.
Below the tiles, "Your jobs" is the same job board the operator sees, restricted to your wallet, with each task's status and any HCS anchor. The panel carries the LIVE or DEMO DATA pill of the job loader; nothing on it is presented as chain data unless it came from the subgraph.
Refund rules
"Refundable deposits" appears only when you are signed in and have at least one refundable job. Each row's Refund button signs AetherisAgency.refundJob(jobId) (lib/write.ts, gas limit 1,000,000). The contract (contracts/AetherisAgency.sol) enforces:
- The caller must be the job's client or the operator, else
NotClientOrOperator. - The job must be
FundedorDispatched, elseInvalidJobStatus. A job that isCompleted(every task done) can no longer be refunded; it can only be settled. - The job becomes
Refunded; every task stillAssignedorCompletedis set toCancelledand emitsTaskCancelled. Completed-but-unpaid work is not paid on a refund. treasury.refundEscrow(jobId, client)returns the full deposit to the client andJobRefunded(jobId, client, token, amount)is emitted.
On success the row's note links the transaction on HashScan and the page refreshes after 8 seconds so the job shows as Refunded.
Signed out: the client picker
Without a wallet the Client view is still real, just not personal. app/(app)/layout.tsx loads up to 100 indexed jobs, counts them per client and passes the list, sorted by job count, into the role context. The workspace shows a "Viewing client" select of those addresses with their job counts, the heading becomes "Client jobs", and the note says "sign in to see and manage your own". The refund section and the sign-in-gated parts of the fund form stay hidden because there is no wallet to sign with.
Chain facts a client should know
- Hedera testnet only, chain id 296, relay
https://testnet.hashio.io/api. Every link the app shows goes tohttps://hashscan.io/testnet. - The agency can commit sub-agent fees only up to your deposit (
FeeExceedsDeposit); on settlement, whatever was not paid out becomes the agency's margin, not a refund. See Jobs & escrow. - The faucet and the World ID relay both spend from the deployer's key, which is why both are rate-limited.
