(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.
aggre•1h ago
x402 is already agent-callable at the protocol level, so no MCP wrapper is needed for payments. The MCP server exposes the read side of the same trust layer (query verified attributes, get schema, get circuit, get generator, get proof status) for agents in MCP-native environments (Claude Desktop, Cursor, etc.) that want to read from Lemma without a custom REST integration.
Two surfaces, one trust layer: x402 : payment rail + ZK proof bundle in PAYMENT-RESPONSE MCP : read interface for MCP-native agents