Skip to main content
ETHOnline 2026 — Continuity trackFor judges. The first command needs no key.

payOrRefuse — an x402 payment gate that refuses before a signature exists

git clone https://github.com/kzmttkc/vet402.git && cd vet402 && npm run judge-check

What

A gate an AI agent passes through before it pays an x402 endpoint it has not seen before. The question is not "can I pay" but "is there evidence this wallet delivers" — and if there is none, no signature is created.

How

It reads two sources live: vet402's own ledger of real purchases, and The Graph's x402 subgraph on Base. If either source cannot be read, or the evidence is below the caller's floor, it refuses — the signing module is not even loaded. It does not fall back to a cached number.

Proof

On 2026-09-05 the gate read 259 receipts from The Graph's subgraph and paid 0.01 USDC to The Graph's own receiving wallet: block 50898704 on Basescan. vet402's own engine rated that payee WARN; the record keeps the WARN next to the payment. vet402 did not rewrite its judgement to match the money.

1.Run it yourself — no key, one command

The command under the title, explained. A clean clone. judge-check installs the SDK, the MCP server, the demo and the A/B harness, runs the test suites, and prints one exit code per step. Any API key in your environment is dropped first, so a green run cannot be borrowing my credentials.

Then the walkthrough in SKILL.md — How a judge can run it: the gate refusing offline, the MCP server listing its tool over stdio, and the gate failing closed against the live API with a deliberately wrong key. The last block that actually pays is marked --live and is a human decision, not a default.

The demo CLI's pay and refuse need a free Graph key: export GRAPH_API_KEY=… from https://thegraph.com/studio (both commands).pay also needs VOUCH_API_KEY (free, no card: vet402.com/signup): its payee, The Graph's gateway, is not in vet402's catalogue, so the verdict comes from a keyed payee-score read. With the Graph key alone, pay prints verdict not read and predicts a refusal; refuse runs without it. judge-check above needs neither.

2.What to read, in order

  1. 1.README — §ETHOnline 2026 (Continuity) — the boundary tag, the two git log commands (what I claim / everything on main), the caveat.
  2. 2.SKILL.md — the gate itself, as commands you can paste. Each block is re-run against production on a schedule, in CI.
  3. 3.docs/ethonline-2026/DISCLOSURE_2026-09-05.md — the message I sent ETHGlobal before judging, verbatim, and the two corrections I made to it.
  4. 4.AI_USAGE.md — who wrote which code, by area and representative file, and what I did.

3.Pre-existing work (Continuity)

vet402 existed before the hackathon. The boundary tag pre-ethonline-2026 is commit c42daca, cut 2026-09-04 00:05:36 UTC. Hacking began at 2026-09-04 16:00 UTC (ETHGlobal's published schedule), so the tag sits 15 h 54 min before the start. Between my application (2026-08-23) and the tag I made 214 commits of ordinary product work. The message I sent ETHGlobal on 2026-09-05 said 207; the disclosure explains why the count here is 214. Three commits in the range I claim were made before 16:00 UTC — their SHAs, times and contents are listed in the disclosure, with the command that lists them. I am not moving the tag: the submission and the disclosure link to it.

4.The Graph

Load-bearing, not decorative. With policy.evidence.source: "subgraph" the gate reads the x402 Base subgraph through the Graph Gateway; if the read fails it refuses with evidence_unavailable + subgraph_evidence_unavailable and does not fall back to vet402's ledger.

Live, and provably so. The evidence row on the decision carries the subgraph's own _meta.block and queriedAt, so a reader can tell a live read from a cached number. Pinned. The caller may name a deploymentId; the response's _meta.deployment must match or the read counts as unavailable.

The payment above ran on exactly this path — The Graph's data, not vet402's. Details: SKILL.md — Paying on The Graph's own data.

5.Bazantic

Recipe vs no Recipe, 10 trials each, same model, same prompt, same 57 tools. Success (right verdict, and each reason code real) was 5/10 and 5/10 — no difference, and I say so first. What the Recipe did fix is vocabulary: the share of reason codes that are real vet402 identifiers went from 63% to 91% (20/32 → 29/32). That metric is exploratory and labelled so.

One finding for Bazantic itself. On 2026-09-06, $0 routes answered 402, and paying $0 posted a real 0-USDC transfer on chain: in 20 trials, 110 tool calls, 88 settled, 88 free reads cost 88 facilitator transactions. Each hash is in the raw log. On 2026-09-09 and 2026-09-11 the free tools I checked answered without a 402. Harness and results: examples/ethonline-2026-ab · BAZANTIC_FEEDBACK.md.

The demo the gate protects is the same one the public uses: verify a payee (no key), the observatory (each purchase vet402 made, settled or not), and the demo CLI (refuse, pay dry-run, judge <url>).