Zero. Not a low number, not mostly zero. Six samples of a hundred, spread across an index of 20,462 live resources, and the offer-receipt extension appeared in none of them.
I went looking because I had built the other half and wanted to know whether it had anything to attach to.
Here is the shape of the problem. When an agent pays for an API call over x402, two parties end up with an opinion about what happened. The seller knows what it served. The buyer knows what arrived. x402 ships an official extension for the seller's side: a signed receipt, which the extension's own documentation offers for audit trails and dispute resolution. I had spent a while on the buyer's side, a signed claim of what was actually delivered, which names the seller's receipt by the hash of its exact bytes so the two artifacts pin each other.
Then it occurred to me that I had never seen one of those receipts in the wild.
How I counted
The x402 bazaar extension means resource servers declare themselves, and facilitators catalogue them. So the question is answerable without asking anyone.
curl -s "https://facilitator.payai.network/discovery/resources?limit=100&offset=0"
That index reports 20,462 resources. I sampled at offsets 0, 4000, 8000, 12000, 16000 and 20300, a hundred each, and grepped the records for the extension's declaration key, which is the string offer-receipt.
Six hundred records. Zero declarations.
For contrast, in the same data the bazaar extension itself appears on every one of the first four hundred and on none of the last two hundred, so these records are not uniform and the index is not a single homogeneous dump.
What this does not prove
Seven of the eight facilitator addresses I tried returned HTML or a handful of bytes instead of JSON on that path. They are unmeasured, not zero. The largest index, Coinbase's, is among them.
A server could also support the extension without it appearing in this catalogue, since the declaration lives in the 402 response and a catalogue is a secondhand record of that. I tried to settle it by requesting a live resource directly and got a 402 with an empty body, which told me nothing either way.
So: a strong indication, not a proof. I would rather hand you the number with its edges than a rounder one without them.
Why the gap matters more than it sounds
A signature answers one question: who said this, and when. It does not answer whether what they said is true. For a receipt, that distinction is the whole thing, because the party signing is the party with an interest in the answer. A seller attesting to its own delivery is the interested party. So is a buyer attesting to what it received.
The usual remedy for a statement that stops being true is for its author to withdraw it. That works, and it is used at real scale: across Base, Optimism and Ethereum mainnet there are 733,203 revoked attestations out of 4,986,045, and on Base three schemas from a single issuer account for 97 percent of them, revoking roughly half of everything they issue. Every one of those events is an issuer correcting itself.
Which is exactly why it does not help here. Self-correction works when the author has no stake in the statement being false. It does nothing when the author is the one who benefits.
The only thing that helps is the other party's signature on the same event. Two signed accounts of one delivery, from two parties with opposing interests, where a contradiction between them is arithmetic rather than opinion. That is what the offer-receipt extension makes possible and what nobody is emitting.
So I made one
curl -i -X POST https://api.tokenizen.nl/api/paid/verify-ownership \
-H "Content-Type: application/json" \
-d '{"endpoint":"https://mcp.example.com/sse","ownerAddress":"0x1111111111111111111111111111111111111111","method":"dns-txt"}'
That answers 402 with a Payment-Required header. Base64-decode it and you get the payment requirements, plus an extensions object carrying offer-receipt with a signed offer inside, EIP-712 format.
Note the empty response body. The requirements are in the header, not the body, which is why my earlier probe of somebody else's service looked broken when it was working perfectly.
You can check the signature without trusting me. Recover it against the domain {name: "x402 offer", version: "1", chainId: 1} and the offer types from the specification, in order: version, resourceUrl, scheme, network, asset, payTo, amount, validUntil. It recovers to 0x4E1174b774643B6AD9EE966A81ea1e3665D1159a.
Then check that address is allowed to speak for the domain, which is a separate question the specification is blunt about: a valid signature is not authorization, and anyone can sign a receipt for any resourceUrl.
dig +short TXT _controllers.tokenizen.nl
"v=1;controller=did:pkh:eip155:1:0x4E1174b774643B6AD9EE966A81ea1e3665D1159a"
Same address. That is the loop closed by a stranger with two commands.
It runs on Base Sepolia, which means the money is fake and the cryptography is not. The price is a tenth of a cent. What you are buying is the receipt, because the answer itself is free at /api/verify-ownership and always will be.
Use it as a test fixture
If you are implementing the client side of offer-receipt, you need something real to test against, and as far as I can measure there is nothing. Point your client at that URL. It costs me nothing and you get a live, signed, spec-conformant artifact instead of a fixture you wrote yourself and therefore cannot be surprised by.
If you find it wrong, tell me and I will fix it. If it stays up and other people start emitting these too, the number at the top of this post stops being interesting, which is the point.
What I have not done yet: nobody has paid, so no receipt has been issued, only offers. That needs a funded Base Sepolia client and it is the next thing.
The code is open: github.com/holistis/tokenizen
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.