[ DOCUMENTATION_MENU ]
[ BUILD ]

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

  1. The agent calls the endpoint and gets 402 with a pai-escrow-zk challenge:
{
  "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.

  1. The agent verifies the proof against the verification key and checks the ciphertext against cHash. If either fails, it does not pay.
  2. It calls open(payTo, asset, maxAmountRequired, [keyHash, cHash, dHash], terms, deadline, proof) on PaiEscrowV2, which verifies the proof again.
  3. It retries with X-PAYMENT: base64({"order":"<id>","nonce":"<nonce>"}).
  4. The server checks the order on-chain (seller, asset, amount, key hash, deadline), claims it with k, and answers 200 with the claim transaction.
  5. The agent reads getOrder(id).key, decrypts, and checks the content against dHash.

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.