[ DOCUMENTATION_MENU ]
x402 integration
A paid HTTP endpoint settled through pAI, with the answer proven before the agent pays.
x402 lets an HTTP server answer 402 Payment Required with instructions, and lets a client pay and retry. pAI plugs a zero-knowledge proof of delivery into that handshake: the server hands over its answer encrypted, with a proof of what it is, and only gets paid by revealing the key.
The live demo is /api/x402/demo. It sells a signed snapshot of Robinhood Chain (block, time, gas price, escrow stats) for 0.00001 ETH.
The handshake
- The agent calls the endpoint and gets
402with apai-escrow-zkchallenge:
{
"x402Version": 1,
"accepts": [{
"scheme": "pai-escrow-zk",
"chainId": 4663,
"maxAmountRequired": "10000000000000",
"asset": "0x0000000000000000000000000000000000000000",
"payTo": "0x...seller",
"extra": {
"escrow": "0x6FdD...64A0",
"nonce": "0x...",
"keyHash": "0x...", "cHash": "0x...", "dHash": "0x...",
"deliveryPackage": "pai2....",
"verificationKey": "https://paiprotocol.app/zk/verification_key.json"
}
}]
}
The server proved the answer before replying: the package holds the ciphertext and a Groth16 proof that it opens under keyHash to exactly the content behind dHash.
- The agent verifies the proof against the verification key and checks the ciphertext against
cHash. If either fails, it does not pay. - It calls
open(payTo, asset, maxAmountRequired, [keyHash, cHash, dHash], terms, deadline, proof)on PaiEscrowV2, which verifies the proof again. - It retries with
X-PAYMENT: base64({"order":"<id>","nonce":"<nonce>"}). - The server checks the order on-chain (seller, asset, amount, key hash, deadline), claims it with
k, and answers200with the claim transaction. - The agent reads
getOrder(id).key, decrypts, and checks the content againstdHash.
If the claim fails, the server answers 502 and the order stays open: the agent gets its money back after the deadline.
Stateless keys
The server keeps no database. For each challenge it draws a random nonce and derives the field key
k = keccak256( keccak256(sellerKey) ++ nonce ) mod p
so the same nonce always gives the same key, and nobody without the seller key can compute it.
Run the buyer agent
BUYER_PK=0x... node scripts/agent-pay.mjs https://paiprotocol.app/api/x402/demo
It prints each step: the challenge, the proof check, the lock, the paid retry, the revealed key and the decrypted snapshot, checked against dHash.
Run your own seller
Copy src/app/api/x402/demo/route.ts with public/zk/ and set two environment variables:
| Variable | Meaning |
|---|---|
PAI_SELLER_PK |
the hot key that receives payments and pays the claim gas (keep a little ETH on it) |
PAI_X402_PRICE_WEI |
the price in wei, default 10000000000000 |
The route proves each answer server side with snarkjs, in a few seconds. Answers are capped at 1 953 bytes, the size one proof covers.