(Recap: x402 is Coinbase's revival of HTTP 402. Server returns 402, client signs an EIP-3009 USDC auth on Base, retries with PAYMENT-SIGNATURE, facilitator settles. Spec: https://x402.org)
I'm the CTO of FRAME00. We've been building Lemma, a document oracle that binds ZK attribute proofs to on-chain Merkle commitments. Three weeks ago I started asking whether the same proof bundle could ride inside an x402 round trip instead of a separate verification step. This repo is the answer.
The gap x402 leaves: the server gets a wallet address and a tx hash. It doesn't know who authorized the payment, under what policy, or whether the data arrives intact. As agents become the payer, a wallet address is an anonymous primitive, not a principal.
What this adds to the x402 flow:
Phase 1 Agent hits any x402-protected endpoint with @x402/fetch.
-> standard 402 with accepts[]. Nothing Lemma-specific.
Phase 2 Wallet signs EIP-3009 USDC auth, retries with
PAYMENT-SIGNATURE, facilitator settles.
-> PAYMENT-RESPONSE carries extensions.lemma =
{ proof, inputs, circuitId, generatedAt }.
A registered x402 extension, not a sidecar.
Phase 3 Agent checks SHA-256(body) against
attributes.integrity in the proof. No round trip.
Phase 4 POST /query for BBS+ selective disclosure. Released
only when the caller's x402 payment satisfies
condition.circuitId = "x402-payment-v1" -- today this
is API-level access gating; the binding is moving
into the BBS+ challenge itself (see below).
The demo wraps Phase 1 in a free GET /article returning an X-Lemma-Attestation header pointing at /verify/:hash. Point @x402/fetch at any paid resource directly and Phase 1 is just standard 402.Server side is a drop-in for @x402/hono:
import { paymentMiddleware, x402ResourceServer,
ExactEvmScheme } from "@lemmaoracle/x402";
const server = new x402ResourceServer(facilitatorClient)
.register("eip155:84532", new ExactEvmScheme());
app.use("*", paymentMiddleware(routes, server));
paymentMiddleware auto-attaches a hook that writes extensions.lemma into PAYMENT-RESPONSE. Route handlers don't change.Agent side uses stock @x402/fetch; no Lemma SDK on the client:
const x402Fetch = wrapFetchWithPayment(fetch, client);
const res = await x402Fetch(`${WORKER_URL}/example/verify/${hash}`);
const settle = JSON.parse(atob(res.headers.get("PAYMENT-RESPONSE")));
// settle.extensions.lemma = { proof, inputs, circuitId }
What this doesn't claim: * Data source is not on-chain. We bind SHA-256 of the body
to a chain commitment; not authorship.
* The issuer's BBS+ key is the trust anchor. Identity,
settlement, and integrity proofs are independent.
* Facilitator is a liveness dependency (x402 design).
Fail-closed. Swap or self-host any x402-compatible one.
What is shipping next: * Agent-side identity. did:key -> agentId with role, scope,
spendLimit. Lifts the paying wallet from anonymous
primitive to verifiable principal.
* Cryptographic settlement binding. Today condition.circuitId
is enforced at the API layer; the BBS+ proof itself
verifies offline once obtained. We are folding the x402
settlement record into the BBS+ challenge so re-use
without a fresh payment becomes infeasible at the
proof layer.
Both are on-axis with the thesis: payment is the trigger; verifiable trust rides on top.Repo: https://github.com/lemmaoracle/example-x402 x402 SDK: https://www.npmjs.com/package/@lemmaoracle/x402 Lemma SDK: https://www.npmjs.com/package/@lemmaoracle/sdk
Questions on the circuit layer, BBS+ binding, or DID roadmap welcome.