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:

SuiteCommand (from repo root unless noted)Expect
Contractsforge test226/226 (unit + fuzz + invariant, 11 suites)
Gas regressionforge snapshot --checkno diffs vs .gas-snapshot
Offchain typescd offchain && npx tsc --noEmitclean
Adapterscd offchain && npm run test:adapters19/19 (local mock upstream: rest incl. env refs, llm-openai, ollama, fal, apify, brightdata)
Template gallerycd offchain && npm run test:templates6/6 (every template loads, declares its keys, has sample args)
Results + evidencecd offchain && npm run test:results8/8 + 9/9 (pinned vectors, verifier)
Private-node boundarycd offchain && npm run test:boundary11/11
HA store/leasecd offchain && npm run test:ha7/7
Billing policycd offchain && npm run test:billing7/7
Agent runnercd offchain && npm run test:agents10/10 + 6/6 (the workflow engine; the marketplace search an agent uses to find tools on its own)
TS SDKcd packages/sdk && npm run build && npm testdrift checks OK (artifacts + results-core), 20/20
MCPcd packages/mcp && npm run buildcompiles (checks SDK dist)
Python SDKcd packages/python-sdk && ./.venv/bin/python -m unittest discover -s tests7/7 (same pinned vectors)
Subgraphcd subgraph && npx graph codegen && npx graph buildBuild 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 for settle-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.

SymptomCause / fix
stale-bytecode reverts after contract editsrun forge build && cd offchain && npm run gen-abis && cp src/sdk/artifacts.ts ../packages/sdk/src/artifacts.ts, redeploy
transcripts/vouchers attributed to wrong sessionsstale ~/.aioum-node on a fresh chain — wipe it
no voucher stored for session on /settlethat instance never served the session (or store wiped)
sdk build fails check-artifacts: DRIFTre-copy the canonical file it names from offchain
billing demo: account exists / $NaNaccount ids persist in billing.db — demo uses per-run ids; wipe ~/.aioum-node for a clean slate
demo owner Insufficient fundsowner key must be a funded local-chain account (anvil funds indexes 0–9)
bundler port conflictbundler owns 8548; relay is 8553
python ModuleNotFoundError: aioumrun 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.