When agents call each other with nobody watching, “I trust you” stops being a usable security model: every hop needs a proof. — the brief
Money on Base. Proof on 0G.
Three ways that goes wrong — and “I trust you” catches none of them.
Charges for a verifiable audit, runs something cheaper, returns an answer that looks identical.
A proxy flips DENY to ALLOW in flight. Both agents behave perfectly.
A clean token’s approval is presented to list a different, hostile token.
Today this happens in Discord and Notion, and the chain sees only the outcome. We make every hop leave a seal.
an actual seal from this repo — web/public/examples/example-sealA.json
maxLtvBps is a credit limit. The auditor said 75% — the contract will not list at 80%.import { sealedAuditRoute } from "@0x402/auditor"; sealedAuditRoute(app, config); // GET /agent → the agent card // POST /audit → x402-gated. No seal, no charge: // a failed audit answers 502.
We hold no key of yours. A’s key stays in A’s process, B’s key in B’s own server, and money is one hop: A → B over x402.
What we put online is a file and one container — directory.json maps agentId → signer and names the reference auditor,
which runs on Cloud Run with our own burner key and scales to zero. Your own B is the route above; an ERC-7857 registry replaces the file without a caller changing a line.
That transcript is not a mock-up. It is one live run on 2026-09-07: the x402 payment settled on Base Sepolia,
the seal body went to 0G Storage at txSeq 149639, and the listing landed in 0G block 53536319 — every hash is checkable on stage.
acu status and the MCP’s agent_status are the same function, so the two shells can never disagree about what to do next.
acu init already wrote, so nothing has to be re-installedverify_seal is @0x402/seal running inside your agent’s own process.
Your agent never asks us whether a seal is valid — that is the entire point. With no key, every paid tool stops at the quote and returns the next step instead of an error.
| Scene | Outcome | Refused where | |
|---|---|---|---|
| ① | CleanUSD at LTV 7000 — the baseline | ✓ EXECUTED | listed |
| ② | TrapUSD — the auditor says DENY, A relays it unsoftened | ✗ AUDIT_FAILED | contract |
| ③ | CleanUSD’s seal replayed to list TrapUSD | ✗ SEAL_SUBJECT_MISMATCH | contract |
| ④ | CleanUSD at LTV 8000 — more leverage than attested | ✗ LTV_EXCEEDS_ATTESTED | contract |
| ⑤ | Agent B takes the fee, skips the verifiable inference | ✗ DELEGATE_SEAL_INVALID | Agent A |
| ⑥ | Man in the middle rewrites seal B’s verdict, signature untouched | ✗ DELEGATE_SEAL_INVALID | Agent A |
| ⑦ | B is not an agent — a plain x402 API, same fee, answers ALLOW unsigned | ✗ DELEGATE_SEAL_INVALID | Agent A |
⑤, ⑥ and ⑦ are the ones that matter. They are refused at Agent A, before anything reaches a contract — because “nobody is watching” means A has to be able to reject B on its own. ⑥’s mitm.ts is a real proxy and the forgery still dies; ⑦ is the world before seals: the fee clears, an ALLOW comes back, and A holds nothing it can check. x402 decides whether B gets paid; 0G decides whether B’s answer is worth anything.
Not a checklist of integrations — three things the product cannot exist without.
The Router runs the audit inside a TEE, and the attestation is anchored on chain — so a third party can re-check it: GET /v1/proxy/signature/{chatId}, EIP-191 against the provider’s registered TEE signer.
A seal must not depend on a server of ours, so it has to be reachable from a bytes32 alone. 0G Storage takes the bytes tagged with the seal’s own sealHash, and the indexer’s gateway serves them straight to a browser (access-control-allow-origin: *).
sealHash points at something only whoever held it can produce. Nobody can audit the audit.The provider’s TEE signer, the seal body’s Flow.Submit tx and CollateralRegistry are all read from one RPC — and it answers a browser directly. One endpoint verifies the whole chain of custody.
And what we did not put on 0G: the money. x402 settles on Base Sepolia, because that is where USDC and the facilitator live. Money on Base, proof on 0G — what went onto 0G is the three things that only work if they share a chain, and nothing else added to make the integration look bigger.
POST /v1/chat/completions with X-0G-Provider-Trust-Mode: verified and verify_tee: true.
The attestation comes back as the ZG-Res-Key header and x_0g_trace.tee_verified — raw fetch, on purpose, because that is exactly what an SDK hides.
Re-checkable afterwards by anyone: GET /v1/proxy/signature/{chatId}.sealHash in tags.
To find it again you need nothing from us: scan Flow.Submit for the tag, pull the bytes from the indexer’s HTTPS gateway, and
sealDigest(bytes) == sealHash is the integrity check. Measured: root 0xfe2ebe72…, txSeq 149639, located again in 1.4 s.
(0G-KV has no reachable public node — we went through the log layer instead.)CollateralRegistry.list, which lists nothing without a seal that verifies.A static site. It bundles @0x402/seal, so every check runs in your own tab — there is no API of ours that could lie to you.
Tokens come from the registry’s Listed events, the seal bodies come from 0G Storage, and every one is verified in your browser before its card turns green. Empty until somebody underwrites — which is the point.
The canonical digest is rebuilt from the seal’s own bytes, the signer is recovered from it, and then it walks down into the embedded seal B and its TEE attestation. Eight checks, in order, and it shows you which one stopped it.
The page reads listings(token) on chain itself, fetches the body by its hash, and shows ON_CHAIN_HASH as a row of its own: sealDigest(fetched) == listings[token].sealHash.
Proven end to end: starting from one bytes32 on chain — 0x324d95a0… — the body was located on 0G Storage in 1.4 s,
the digest matched, and verifySealA passed, walking down into seal B with teeVerified: true.
Nobody had to keep a copy for any of that to work.
On InjectionUSD the model returned DENY 10 times out of 10 — and every time for the wrong reason. It saw the injection, short-circuited, and never audited the contract at all. Same string in a clean token and it would misfire.
We are not replacing audits with AI. We are turning a pre-screen into an on-chain auditable, attributable, expiring credential.
Also stated plainly in the README: a TEE is not trustlessness, the seal does not cover the invoice,
and holder concentration is left null rather than invented — there is no indexer on this testnet.
And what v1 does not do yet, said out loud: the registry trusts one underwriter signer, Agentic ID is a field rather than a live ERC-7857 registry, and the reference Agent B is one container that scales to zero, on a 50-audits-a-day testnet quota — so the site’s pill tells you honestly when it is off.
Agents can hire agents with nobody watching —
because A can reject B on its own.
And now that takes one command.