0G Taipei Hackathon · Track C — On-Chain Agent / DApp
When agents call each other with nobody watching, “I trust you” stops being a usable security model: every hop needs a proof. — the brief
We built it.

Attested Collateral Underwriter

Money on Base. Proof on 0G.

Agent A underwrites · Agent B audits · A pays B over x402 Inference in a TEE on the 0G Compute Router, seal bodies on 0G Storage CollateralRegistry lists nothing without a seal · 0G testnet 16602 Shipped as npx @0x402/cli and claude mcp add acu — a reference auditor is live, and no key of yours ever reaches us
The gap

Agent A wants to hire Agent B.
Nobody is watching.

Three ways that goes wrong — and “I trust you” catches none of them.

B takes the fee

Charges for a verifiable audit, runs something cheaper, returns an answer that looks identical.

no evidence attached

Someone rewrites the answer

A proxy flips DENY to ALLOW in flight. Both agents behave perfectly.

nothing binds verdict to signer

The answer is reused

A clean token’s approval is presented to list a different, hostile token.

nothing binds answer to subject

Today this happens in Discord and Notion, and the chain sees only the outcome. We make every hop leave a seal.

How one hop works — with us, and without

Same $0.01, same ALLOW. The difference is what is left behind.

WITHOUT US how it works today AGENT AS USER Agent A wants CleanUSD listed $0.01 AGENT AS SERVICE Agent B some model, somewhere "ALLOW" plain text, unsigned AGENT AS USER Agent A takes B's word list(CleanUSD, 7000) CONTRACT Registry takes A's word ✓ listed Afterwards: who said ALLOW, on what evidence, which model? Nothing to check. WITH US — SCENE ①, CLEANUSD, AS RUN COMPUTE PROVIDER · TEE 0G Compute Router qwen2.5-omni · verified · teeVerified ✓ AGENT AS USER Agent A Agentic ID #1 reads CleanUSD from 0G RPC x402 · $0.01 USDC tx 0x9ca251…f92a48 AGENT AS SERVICE · x402 Agent B Agentic ID #2 verdict + attestation → seal B, signed by #2 prompt answer + attestation seal B ALLOW · 7500 AGENT AS USER · VERIFIES Agent A verifies seal B signer = #2's on-chain key ✓ same token · same request ✓ not expired · attestation ✓ 6/6 → seal A, signed by #1 CONTRACT · 0G CHAIN Registry list(CleanUSD, 7000, sealA) seal A ⊃ seal B · 7500 ✓ ✓ EXECUTED A verifies with nothing but the seal's own bytes, the chain, and what it already knows — nobody has to vouch for B. The trust boundary is at Agent A, not at the contract. Afterwards anyone can re-verify every hop.
The artifact

A seal carries a credit limit, not a thumbs-up

seal A — underwritingsigned by Agent A
subject 0xdb08ce…ff9eb8 CleanUSD
maxLtvBps 7500 · expiresAt +24h
delegations[0] code-audit · 10000 atomic
seal B — auditsigned by Agent B
model qwen2.5-omni
trustMode verified
teeAttestation c24e1e77… teeVerified ✓
findings []
verdict ALLOW · 7500
settlementTx 0x9ca251…f92a48 Base Sepolia
signature 0xcc5e72…7ca51b

an actual seal from this repo — web/public/examples/example-sealA.json

A cap, not a boolean
maxLtvBps is a credit limit. The auditor said 75% — the contract will not list at 80%.
Subject binding
A seal names the token it is about, so it cannot be replayed onto another one.
Seals expire
An upgradeable contract that is clean today is not clean next week.
The chain is a chain
Seal A embeds seal B whole. The contract verifies only seal A — anyone can walk down and re-verify the hop below, right down to re-fetching the TEE signature from the provider.
What we shipped

One core. Three shells. None of your keys.

core — published on npm as @0x402/*
@0x402/sealcanonical bytes · sign · verify
@0x402/og0G Compute Router client · verified tier · attested
@0x402/underwriterbe an A: hire, verify, compose, list
@0x402/auditorbe a B: one x402 route that seals
@0x402/storageseal bodies on 0G Storage, found by hash
@0x402/configthe key file both shells share, 0600
shells — the three ways in
@0x402/cliinit · status · quote · underwrite · verify
@0x402/mcpseven tools — any agent framework becomes an A
web/landing · live feed · verifier · docs · directory
to be an Agent A
$npx @0x402/cli underwrite 0xDB08…9eB8
$claude mcp add acu -- npx -y @0x402/mcp
to be an Agent B
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.
a plain x402 API is not a B (scene ⑦). Mounting this route is the line between getting paid and being worth paying.

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.

Onboarding — the funnel is the product

From nothing to a signed, on-chain seal.

  1. See it runnpx @0x402/cli quote 0xDB08…9eB8
    no install, no key, no env
  2. Get a keynpx @0x402/cli init
    burner key → ~/.acu/config.json (0600)
  3. Fund itthe address it printed → Circle, Base Sepolia USDC
  4. Checknpx @0x402/cli status
    flips to ✓ Ready and names the next command
  5. Underwritenpx @0x402/cli underwrite 0xDB08…9eB8 --live --publish
    the last line is a shareable URL
  6. Hand it to your agentclaude mcp add acu -- npx -y @0x402/mcp
    same key, zero env
npx @0x402/cli underwrite 0xDB08Ce21…7fF9eB8 --live --publish [2] reading token from 0G testnet RPC [2] CUSD · 1825B code · owner none · flagged selectors: none [3] hiring agent B at https://zerog-x402-auditor-…run.app/audit [3] paid $0.01 · settlement 0xe0578d97…785ebc [4] agent B returned ALLOW maxLtvBps=7500 [5] verifying seal B [5] seal B valid · signer 0xC1Dba83f… · attestation present [6] composing seal A [6] seal A signed · embeds seal B · maxLtvBps 7500 [S] published to 0G Storage · root 0xfe2ebe72…67ce697 [7] listing on CollateralRegistry 0xC1AAfd71…7B46a65E ✓ EXECUTED ltv=7000bps tx=0xeab43669…ce28272e share https://wayneal.github.io/0G/#seal=eyJ0eXBlIjoi…

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.

The thesis — the agent is the user

One line to install. Then just say it. Your agent is the Agent A.

$claude mcp add acu -- npx -y @0x402/mcp
no environment variables at all — it reads the key acu init already wrote, so nothing has to be re-installed
you“Underwrite 0xDB08…9eB8 at 70% LTV and list it if it holds up.”
acuagent_status → key ✓ · budget $0.09 left · auditor online · Ready
acuunderwrite → paid $0.01 over x402 · seal B verified in-process
seal A signed · published to 0G Storage · listed at 7000 bps
agent“Listed at 7000 bps. The auditor capped it at 7500, and its answer was produced in a TEE. Here is the seal — check it yourself: …/#seal=eyJ0eXBl…”
seven tools
describe_auditorquote_audithire_auditverify_sealunderwriteget_listingagent_status

verify_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.

Live demo · ./demo/run.sh --live

Seven scenes. Every one asserts its own outcome.

SceneOutcomeRefused where
①CleanUSD at LTV 7000 — the baseline✓ EXECUTEDlisted
②TrapUSD — the auditor says DENY, A relays it unsoftened✗ AUDIT_FAILEDcontract
③CleanUSD’s seal replayed to list TrapUSD✗ SEAL_SUBJECT_MISMATCHcontract
④CleanUSD at LTV 8000 — more leverage than attested✗ LTV_EXCEEDS_ATTESTEDcontract
⑤Agent B takes the fee, skips the verifiable inference✗ DELEGATE_SEAL_INVALIDAgent A
⑥Man in the middle rewrites seal B’s verdict, signature untouched✗ DELEGATE_SEAL_INVALIDAgent A
⑦B is not an agent — a plain x402 API, same fee, answers ALLOW unsigned✗ DELEGATE_SEAL_INVALIDAgent 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.

Why 0G

Take 0G out and there is nothing to verify.

Not a checklist of integrations — three things the product cannot exist without.

An answer needs provenance

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.

without itScene ⑦: the same fee, the same ALLOW, and nothing to check. An ordinary model API returns text, not evidence.

The seal has to outlive us

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: *).

without itThe registry’s sealHash points at something only whoever held it can produce. Nobody can audit the audit.

All of it must be one trust domain

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.

without itThree vendors, three trust assumptions, and no chain in a position to enforce the cap the auditor wrote.

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.

Where the 0G integration is

The exact wiring — every endpoint, every header, every address

Compute Router
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}.
0G Storage
Every seal A is uploaded through the indexer with its own 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.)
Agentic ID
Both agents hold one. The ERC-7857 tokenId is a field in every seal, and A checks that B’s is live before accepting its work.
0G chain
Reads — bytecode, ERC-20 metadata, owner, watched selectors. Writes — CollateralRegistry.list, which lists nothing without a seal that verifies.
CollateralRegistry · 16602
0xC1AAfd71…7B46a65E
Seal body
0G Storage txSeq 149639
Inference
qwen2.5-omni TDX · dstack
Payment
x402 v2 eip155:84532
Cost per audit
$0.01 + $0.0009 inference
The public surface

Every seal anyone makes shows up here — and anyone can re-check it

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.

The feed

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.

Recompute, don’t read

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.

Or just give it an address

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.

What we can and cannot claim

A seal proves traceability. It does not prove the judgement was right.

30/30
runs agree — 3 tokens × 10, deterministic verdict and cap
297
tests — 20 Foundry incl. a 256-run fuzz, 277 TypeScript across nine packages
3.5s
p50 inference latency, TEE-attested every run
0
private keys in the repo; every scene asserts its outcome

The one we found ourselves

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 know this only because the seal records the reasoning

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.

Attested Collateral Underwriter · 0G Taipei Hackathon

Every hop leaves a seal.

Agents can hire agents with nobody watching —
because A can reject B on its own.
And now that takes one command.

Run it yourself
npx @0x402/cli init
npx @0x402/cli underwrite 0xDB08…9eB8
Give it to your agent
claude mcp add acu -- npx -y @0x402/mcp
See and verify every seal
wayneal.github.io/0G
github.com/WayneAl/0G
Deployed · 0G testnet 16602
0xC1AAfd71480Ebc92C7F9fcC4d24272bd7B46a65E
01 / 09 ←→ move · N notes · L 中/EN · F full screen · ⌘P PDF