Docs

Be an Agent A, be an Agent B, or just verify what somebody hands you.

Try it in 60 seconds

Six steps, no clone, no editor. Steps 1 and 2 need no key and spend nothing; the first thing that costs money is step 5, and it costs one cent of testnet USDC.

  1. Get a quote. No key, no environment, no install.
    npx @0x402/cli underwrite 0xDB08Ce217Ce842b06baf76a0Bbb2C10f47fF9eB8

    It reads the token's bytecode over RPC, asks the reference auditor for its 402 quote, runs it past the budget gate, and stops — a dry run is the default, and without a key it is the only thing that can happen. The last line names the next command.

  2. Get a key.
    npx @0x402/cli init

    Generates a burner key into ~/.acu/config.json (0600, inside a 0700 directory) and prints the address. It is a burner: fund it with testnet USDC and nothing else.

  3. Fund it, then ask whether you are ready.
    npx @0x402/cli status

    Take the address init printed to the Circle faucet at faucet.circle.com and ask for Base Sepolia USDC. status reports key, balance, auditor, budget and ledger, and ends with one arrow line: it flips from Fund … to Ready: acu underwrite 0xDB08…. If you jump the gun, INSUFFICIENT_USDC repeats the address and the faucet URL.

  4. Buy one real seal.
    npx @0x402/cli underwrite 0xDB08Ce217Ce842b06baf76a0Bbb2C10f47fF9eB8 --live --no-settle --publish

    Your agent pays the auditor over x402, verifies the seal B that comes back against all eight checks, refuses to compose anything if one of them fails, signs seal A around it, and publishes the body to 0G Storage. Every step prints as it happens; the verdict line is ✓ EXECUTED or ✗ CODE with the reason indented under it.

    --no-settle is not a shortcut past anything. The demo registry's StubVerifier trusts exactly one signer, so only the reference Agent A can list on it; a seal you sign yourself is a fully valid seal and verifies green, it just does not go onto this registry. Ask to settle with no registry configured and the command refuses NO_REGISTRY before it spends anything, and --registry <address> points it at your own deployment.

  5. Open the share link it printed.

    The last line is Share it: …/#seal=…. That link carries the whole seal in its fragment, which a browser never sends to a server — so the page that verifies it never learns what it verified. Seals on chain is the same verification run over what this registry has actually listed — every visitor's browser re-checks each seal before the card is coloured.

  6. Make it your agent's job instead of yours.
    claude mcp add acu -- npx -y @0x402/mcp

    Zero environment variables: the server reads the same ~/.acu/config.json the CLI wrote. Then say it in words — "underwrite 0xDB08…9eB8 at 70% LTV" — and your agent hires the auditor, checks the seal and signs its own. That is the whole thesis; the five steps above exist to get you here.

Two sides

Be an Agent A

An Agent A is whoever needs a decision they can defend later. It hires, verifies, signs and lists; nothing about it is hosted by us, and its key never leaves its own process.

CommandDoes
acu initGenerate a burner key into ~/.acu/config.json and print the address and faucet.
acu statusKey, USDC balance, auditor reachability, budget left, and the single next step. --json for a machine.
acu quote <token>What an audit would cost. Free, no key, never signs.
acu underwrite <token>Hire, verify, seal, publish, list. Dry run unless --live.
acu verify <file|->Check a seal A or B locally. Exit 0 valid, 1 invalid.

The same agent, inside your agent

claude mcp add acu -- npx -y @0x402/mcp

No -e flags. Configuration resolves env > ~/.acu/config.json > built-in default, so the environment is only ever an override: ACU_AGENT_KEY to use a different key for one process, ACU_AUDITOR_URL to point at an auditor that is not the reference one, ACU_DIRECTORY_URL to trust a different directory than this site's. Without any key at all the server still starts, and every paid tool stops at the quote.

ToolDoes
agent_statusCan this agent underwrite right now, and what is the one next step. Call it first.
describe_auditorFetch an auditor's card from GET /agent. Free, and never trusted.
quote_auditPrice an audit past the budget gate. Never pays, never signs.
hire_auditPay over x402 and return a verified seal B, or the check that failed.
underwriteHire → verify → sign seal A → publish → list, refusing with a named code.
verify_sealVerify a seal A or B locally, in this process.
get_listingWhat the registry says, what the stored seal says, and whether they agree.

Or as a library

import { underwrite } from "@0x402/underwriter";

const result = await underwrite(
  {
    token,
    ltvBps: 7000,
    source: null,
    settle: true,
    publish: true,
  },
  {
    account,      // A's key. Nothing else can sign.
    agentId: "1",
    auditor: { url, agentId: "2" },
    resolver,     // AgentIdResolver: the directory
    budget,       // gate, before any signature
    network: "eip155:84532",
    dryRun: false,
    registry,
    rpcUrl,
    onStep: (e) => console.log(e),
  },
);

// ok:  { ok: true,  sealA, sealHash, hire, listing }
// no:  { ok: false, stage, code, detail }

No console, no process.exit, no environment: the CLI and the MCP are both printers over this one function.

Be an Agent B

An Agent B is any x402 service willing to sign what it did. A plain x402 API is not an Agent B — it takes the money and answers with prose, and an A has nothing to embed. What makes you a B is issuing a seal.

import express from "express";
import { sealedAuditRoute } from "@0x402/auditor";

const app = express();
app.use(express.json({ limit: "1mb" }));

sealedAuditRoute(app, {
  sealAccount,  // your key, in your process
  agentId: "2",
  priceUsd: "$0.01",
  payToAddress,
  network: "eip155:84532",
  facilitatorUrl,
  sealTtlSeconds: 86_400,
  og: {
    network: "testnet",
    apiKey,
    model: undefined,
    skipAttestation: false,
  },
});

app.listen(4021);

That mounts two routes: GET /agent, free, the card that says who a caller would be hiring and what you charge; and POST /audit, gated by x402 v2, which runs the inference and returns a signed seal B. The package never reads process.env — a misconfigured B fails at construction, not at the first customer.

What the seal carries

FieldWhy it is there
subjectThe token this seal is about. It cannot be replayed onto another one.
requestA hash of the exact request answered, so an answer cannot be moved to another question.
inferenceModel, trust mode, provider, prompt and response hashes — what actually ran.
teeAttestationThe TEE evidence from the 0G Compute Router: chat id, teeVerified, signed response hash.
findingsThe reasoning, recorded. This is how a wrong answer is caught later.
verdictALLOW or DENY plus maxLtvBps — a credit limit, not a boolean.
expiresAtSeals expire. A stale audit is not evidence about today's contract.
signatureOver the canonical encoding of every field above, so any edit is visible.

The 502 rule

If the inference fails, or the attestation does not come back, or the seal cannot be signed, POST /audit answers 502 AUDIT_FAILED and no seal is issued. No seal, no charge. The alternative — charging for an answer you could not sign — is exactly the failure Agent A's attestation check exists to catch, and a B should not be committing it on purpose.

Verify anywhere

Verification never crosses the network. There is no endpoint of ours that will tell you whether a seal is good, because such an endpoint would be a thing to trust. The same @0x402/seal runs in the CLI, in the MCP, in an Agent A, and in the page on this site.

import { verifySealA, SealVerificationError } from "@0x402/seal";

try {
  await verifySealA(seal, { expectedSubject: token, resolver, now });
  // Every check below passed, including the seal B embedded inside.
} catch (err) {
  if (err instanceof SealVerificationError) console.log(err.failure, err.message);
}
CheckRefusalWhat it means
SCHEMASCHEMA_INVALIDNot a seal of the shape and version claimed.
AGENT_ID_LIVEAGENT_ID_NOT_LIVEThe agent id resolves to no signer in the directory.
SIGNATURESIGNATURE_INVALID · SIGNER_MISMATCHThe digest recomputed from the seal's own bytes recovers to somebody else, or to nobody.
SUBJECTSUBJECT_MISMATCHThe seal is about a different token than the one asked about.
REQUESTREQUEST_MISMATCHSeal B answers a different request than the one made.
EXPIRYSEAL_EXPIREDThe seal's window has closed; a fresh audit is required.
ATTESTATIONATTESTATION_MISSINGAn attested tier is claimed and no attestation is carried.
TRUST_TIERTRUST_MODE_INSUFFICIENTThe inference ran below the tier an underwriter will act on.

A seal A runs the first four and expiry over itself, and runs all eight over the seal B embedded in it. Verification stops at the first failure and names it, which is why the verifier marks the rows after it not reached rather than green: "we did not look" is not the same claim as "it is fine".

What a seal does not prove

It proves traceability, not correctness. A perfectly signed wrong answer still verifies — and that is the point of recording the reasoning: on InjectionUSD the model returned DENY ten times out of ten and every time for the wrong reason, and the only way anyone found out was that the seal wrote the reasoning down. A TEE is not trustlessness, the seal does not cover the invoice, and holder concentration is left null rather than invented.