Testing guide
You are changing code that other people's money runs on. Every check, static and live, runs green.
0. Prerequisites
You need Foundry (forge), Node 22+, Python ≥ 3.9 and the git submodules pulled, with Foundry on
your path (export PATH="$HOME/.foundry/bin:$PATH"). The one-time installs are
cd offchain && npm install · cd packages/sdk && npm install ·
cd packages/mcp && npm install · cd subgraph && npm install ·
cd packages/python-sdk && python3 -m venv .venv && ./.venv/bin/pip install -U pip && ./.venv/bin/pip install -e .
Ports are fixed, and a straggler from an earlier scenario is the commonest cause of a puzzling
failure, so kill them between scenarios: anvil 8545 · node 8546 (HA peer 8556) ·
facilitator 8547 · bundler 8548 · indexer 8549 · billing 8550 · relay 8553 · private
node 8770.
Reset between live scenarios:
pkill -f "tsx src/" ; pkill -x anvil ; rm -rf ~/.aioum-node
Stores are keyed by session id, and a fresh chain restarts ids at 1, so a stale ~/.aioum-node
poisons transcripts and vouchers with the previous run's sessions.
1. The full static battery (run before any commit)
Contracts, node and adapters, listing gallery, evidence format, private-node boundary, HA store, billing, agent runner, both SDKs, MCP server and subgraph:
| Suite | Command (from repo root unless noted) | Expect |
|---|---|---|
| Contracts | forge test | 226/226 (unit + fuzz + invariant, 11 suites) |
| Gas regression | forge snapshot --check | no diffs vs .gas-snapshot |
| Offchain types | cd offchain && npx tsc --noEmit | clean |
| Adapters | cd offchain && npm run test:adapters | 19/19 (local mock upstream: rest incl. env refs, llm-openai, ollama, fal, apify, brightdata) |
| Template gallery | cd offchain && npm run test:templates | 6/6 (every template loads, declares its keys, has sample args) |
| Results + evidence | cd offchain && npm run test:results | 8/8 + 9/9 (pinned vectors, verifier) |
| Private-node boundary | cd offchain && npm run test:boundary | 11/11 |
| HA store/lease | cd offchain && npm run test:ha | 7/7 |
| Billing policy | cd offchain && npm run test:billing | 7/7 |
| Agent runner | cd offchain && npm run test:agents | 10/10 + 6/6 (the workflow engine; the marketplace search an agent uses to find tools on its own) |
| TS SDK | cd packages/sdk && npm run build && npm test | drift checks OK (artifacts + results-core), 20/20 |
| MCP | cd packages/mcp && npm run build | compiles (checks SDK dist) |
| Python SDK | cd packages/python-sdk && ./.venv/bin/python -m unittest discover -s tests | 7/7 (same pinned vectors) |
| Subgraph | cd subgraph && npx graph codegen && npx graph build | Build completed |
The pinned Merkle vectors appear in three suites (offchain/ethers, packages-sdk/viem, python): if any implementation drifts, its suite fails. Never change vectors without a format version bump.
2. Contract suites in isolation (debugging)
When one contract suite fails, run it alone:
forge test --match-contract AIOUMProtocolTest # core protocol + token (incl. burn test)
forge test --match-contract EconomicsUpgradeTest # escrow caps + proportional slash
forge test --match-contract VerifiabilityTest # commitRequired + root-before-settle
forge test --match-contract CommitRevealArbitratorTest
forge test --match-contract VerifyingPaymasterTest # sponsorship gating via real EntryPoint
forge test --match-contract AccountAbstractionTest # 4337 session keys, A6 regressions
forge test --match-test invariant_ -vv # invariant/fuzz campaign
forge test --match-test test_Dispute_UpheldSlashesNodeAndRefundsClient -vvv # worked example
3. Live scenario A — core payment loop (x402)
The paid call for real, node and facilitator each in their own terminal:
# T1: anvil
# T2: cd offchain && npm run deploy && npm run node
# T3: cd offchain && npm run facilitator
# T4: cd offchain && npm run demo
Expect: 3 paid wordcount calls; settle tx; the money-flow report ends
Conservation check … = 10.000000 USDC and On-chain deltas match split: YES ✓.
4. Live scenario B — verifiability (anchored roots + arbitrator CLI)
After scenario A (session 1 settled), check that the evidence a juror would receive matches what the operator anchored on-chain:
cd offchain
npm run verify-evidence -- --export --session 1 -o /tmp/ev.json # operator-side evidence
npm run verify-evidence -- /tmp/ev.json # arbitrator verification
Expect: ✓ session · ✓ transcript · ✓ anchored-root ("matches the operator's on-chain results root exactly"); vouchers/re-execution lines may be ○ (not provided).
Optional Tier-2 re-execution: wordcount is deterministic, so an independent re-implementation
must match the transcript call for call:
npm run verify-evidence -- /tmp/ev.json --exec "node scripts-or-your-reimpl.mjs"
# expect: ✓ re-execution: re-executed N/N call(s); every output matches the transcript
Also verify the contract enforcement: registry.setCommitRequired(serviceId, true)
then a paid settle without a root must revert results root required (covered by
VerifiabilityTest; to see it live, use cast against a fresh session).
5. Live scenario C — privacy relay
An agent buys a service through a relay with no on-chain session linking it to the target:
# reset, then:
# T1: anvil T2: cd offchain && npm run deploy && npm run node
# T3: cd offchain && npm run relay # :8553, stakes + registers privacy:relay
# T4: cd offchain && npm run demo:relay
Expect: quote = target price +30%; 3 proxied calls; settle; then the session-graph
proof: every on-chain session listed with parties, ending
✓ no on-chain session links the CLIENT to the target service.
6. Live scenario D — HA: two instances, one operator key
Two instances, one operator key, one shared store; the leader dies mid-session and the survivor must finish the job and anchor every call:
# reset, then:
# T1: anvil T2: cd offchain && npm run deploy
# T3: AIOUM_STORE=sqlite AIOUM_NODE_PORT=8546 AIOUM_SETTLE_JITTER_MS=0 npm run node
# T4: AIOUM_STORE=sqlite AIOUM_NODE_PORT=8556 AIOUM_SETTLE_JITTER_MS=0 npm run node
# T5: cd offchain && npm run demo:ha
Expect: roles: A=leader B=follower; calls 1–3 alternate A→B→A on one cumulative
line; killing instance A… then survivor role: B=leader (lease takeover ≤ 20s); call 4
- settle on B; final assertion
✓ HA PROOF … anchored root covers every call from BOTH instances. Check T4's log forsettle-lease ACQUIRED.
7. Live scenario E — managed billing (fiat tier)
The fiat path from Managed billing: a top-up credited to a smart account, a gasless session through the verifying paymaster, and the free tier's daily cap:
# reset, then:
# T1: anvil
# T2: cd offchain && USE_VERIFYING_PAYMASTER=1 npm run deploy && npm run node
# T3: cd offchain && npm run bundler
# T4: cd offchain && BILLING_ADMIN_TOKEN=devtest npm run billing
# T5: cd packages/sdk && npx tsx examples/billing-demo.ts
Expect: top-up $50.00 → credited $48.75 (fee $1.25); treasury USDC lands in the
smart account; session N open (gasless via the verifying paymaster — the TS
sponsorship digest was accepted by the real EntryPoint); billing balance drops by the gas
estimate; free-tier account opens one $0.00 session; its second request is refused
daily sponsored-gas cap reached; final ledger shows topup / fee / deposit / gas rows.
8. Live scenario F — Python SDK + indexer
A Python agent finds a service through the indexer, pays, and verifies the anchored root with its own Merkle implementation:
# reset, then:
# T1: anvil T2: cd offchain && npm run deploy && npm run node
# T3: cd offchain && npm run indexer # :8549
# T4 (checks):
curl -s localhost:8549/health # ok:true, services counted, scanErrors 0
curl -s localhost:8549/relays # [] unless a relay runs
# T5: python — from packages/python-sdk:
./.venv/bin/python - <<'EOF'
import json, time
from aioum import AioumClient
dep = json.load(open("../../offchain/deployments.json"))
c = AioumClient(rpc_url="http://127.0.0.1:8545", node_url="http://127.0.0.1:8546",
chain_id=dep["chainId"], payment_manager=dep["payment"], usdc=dep["usdc"],
private_key="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a")
svc = c.discover(url="http://127.0.0.1:8549")[0] # discovery via the INDEXER
s = c.open_session(svc, deposit=5_000_000, tool="wordcount")
print(s.call({"text": "python agents pay per call"}))
s.close(); time.sleep(20)
v = s.verify_anchored_root(); assert v["anchored"] and v["matches"], v
print("anchored root matches python-computed root ✓")
EOF
Then feed s.evidence_package(...) (write it to JSON) into npm run verify-evidence: every
check must be ✓, which is the cross-language interop proof.
9. Frontend manual checks
One command starts everything: chain, all services, the multi-service catalogue, relay, billing, the app, the landing site, the developer consoles, the docs, and seeded activity from the paid demo:
scripts/devstack.sh review # DAPP_PORT=3100 if 3000 is taken; MDBOOK=/path/to/mdbook for the docs
scripts/devstack.sh urls # the URL table
scripts/devstack.sh clean # stop every process group and wipe local network state
Marketplace (/marketplace) shows the catalogue's 50 live services across the local
network's demo operators, plus a relay, as clickable cards, and the ready-made listings; any card
opens its service page (cost, node health, operator, tools, the live 402 challenge, how-to
tabs, activity). Deploy Agents (/agents) builds, runs, schedules and publishes an agent;
/agents/budget is the wallet-authorized budget → session key handoff. Earn (/earn) goes
template gallery → services.json, price and stake, one test call, go live. ZAI (/zai) has
the stake, jury, token and activity tabs; Docs (/docs) serves the documentation. Import an
Anvil test key into the wallet to try staking, the jury and the budget page.
On Deploy Agents, build an agent in assistant mode with Let it find tools on its own ticked and run it. Its run log shows each marketplace search (free), the paid calls with their receipts and, once each session settles, the rating it filed for the operator, 0–100 from the errors and slow answers it saw. The Marketplace's rating sort and a service page's reputation stars are built from those ratings, so the agent's verdict is on the operator's page.
The static developer consoles configure themselves from offchain/deployments.json:
python3 -m http.server 8080 # from the repo root, then open http://localhost:8080/frontend/index.html — the consoles load ../offchain/deployments.json for auto-config
index.html (marketplace): with the indexer up, the services table populates and the relay
section lists relays; connect a wallet on the local chain's RPC to walk the delegate flow.
explorer.html: refresh scans sessions, disputes, staking and ZAI transfers; anchored-root
badges show ✓ on settled sessions, and a "Tx explorer base" turns hashes into links.
arbitration.html: config auto-fills from deployments and detect fills the arbitrator when
the commit-reveal module is installed; commit a vote, confirm the salt row appears in "Your
commitments" BEFORE the tx confirms, export the salt backup, reveal after the commit window,
finalize after reveal. site/index.html and site/token.html are static: open them
directly and check nav, tabs and links.
10. Ops tooling checks
The operator's tooling from Operating the network:
cd offchain
npm run paymaster # deposit + runway; exit 1 if below AIOUM_PAYMASTER_MIN_ETH
curl -s localhost:8546/metrics # Prometheus text (bearer-gated when AIOUM_NODE_TOKEN set)
curl -s localhost:8546/health # role: single|leader|follower
npm run verify-deploy # post-deploy verification of a live deployment
The instance lock: start npm run node twice without AIOUM_STORE=sqlite; the second must
exit FATAL: another node instance … already runs.
Troubleshooting
The cause is almost always one of these.
| Symptom | Cause / fix |
|---|---|
| stale-bytecode reverts after contract edits | run forge build && cd offchain && npm run gen-abis && cp src/sdk/artifacts.ts ../packages/sdk/src/artifacts.ts, redeploy |
| transcripts/vouchers attributed to wrong sessions | stale ~/.aioum-node on a fresh chain — wipe it |
no voucher stored for session on /settle | that instance never served the session (or store wiped) |
sdk build fails check-artifacts: DRIFT | re-copy the canonical file it names from offchain |
| billing demo: account exists / $NaN | account ids persist in billing.db — demo uses per-run ids; wipe ~/.aioum-node for a clean slate |
demo owner Insufficient funds | owner key must be a funded local-chain account (anvil funds indexes 0–9) |
| bundler port conflict | bundler owns 8548; relay is 8553 |
python ModuleNotFoundError: aioum | run via ./.venv/bin/python from packages/python-sdk |
Next: The app — what the app is, page by page, and where the story that began on the welcome page ends.