x402 paywall
HTTP 402 → on-chain escrow → release.
@aap/x402-adapter is an Express middleware that turns any route into a pay-per-call endpoint. Without payment it returns HTTP 402 with an x402-spec-compatible challenge JSON; with payment it validates the on-chain escrow, runs your handler, then synchronously settles before flushing the response.Wire it into Express
import express from "express";
import { aapPaywall } from "@aap/x402-adapter";
import { AapClient } from "@aap/sdk-ts";
const client = new AapClient({ provider });
const app = express();
app.get(
"/api/scrape",
aapPaywall({
client,
merchantPubkey: merchant.publicKey,
attestorKeypair: attestor, // never expose this
mint, // USDC (or mock)
priceStroops: 1_000_000n, // 1 USDC per call
resource: "/api/scrape",
description: "Pay-per-call scraper",
onPayment: ({ escrowPda, agentOwner, txSig }) => {
console.log("settled", { escrowPda, agentOwner, txSig });
},
}),
async (req, res) => {
const html = await scrapeUrl(req.query.url);
res.json({ html }); // adapter intercepts and settles
},
);Request lifecycle
┌──────────────────────────────────────────────────────────────┐
│ 1. agent → GET /api/scrape (no X-Payment) │
│ ← HTTP 402 + challenge JSON │
│ expectedTaskHash, mint, payTo, amount │
├──────────────────────────────────────────────────────────────┤
│ 2. agent → on-chain createEscrow(amount, expectedTaskHash) │
│ ← escrow PDA created, USDC custodied │
├──────────────────────────────────────────────────────────────┤
│ 3. agent → GET /api/scrape with X-Payment: <base64 envelope> │
│ middleware: validate escrow on-chain │
│ ✓ state == Pending │
│ ✓ merchant == config.merchantPubkey │
│ ✓ agent_owner == envelope.agent_owner │
│ ✓ amount >= priceStroops │
│ ✓ deterministic task_hash matches request │
│ handler runs → res.json({ html, ... }) │
│ middleware intercepts: │
│ proof_hash = SHA-256(body) │
│ attestor.signEd25519(escrow ‖ proof ‖ ts) │
│ tx = [Ed25519Ix, releaseEscrowIx] │
│ await sendAndConfirm(tx) ← SYNC settle │
│ ← HTTP 200 │
│ x-payment-settled: true │
│ x-aap-tx-sig: <base58> │
│ x-aap-proof-hash: <hex> │
└──────────────────────────────────────────────────────────────┘Demo endpoints
The demo merchant exposes several paywalled resources so the protocol feels less abstract than a single scrape endpoint:
GET /api/scrape?url=<url> mock web scrape response
GET /api/report agent commerce risk report
GET /api/inventory-check?sku=<id> inventory availability snapshot
POST /api/book-hotel-mock mock hotel booking receiptHosted demo: demo.settleproof.xyz. Headless API console: api.settleproof.xyz.
402 challenge format
{
"x402Version": 1,
"accepts": [
{
"scheme": "aap-solana",
"network": "solana-devnet",
"maxAmountRequired": "1000000",
"asset": "<usdc mint base58>",
"payTo": "<merchant pubkey base58>",
"resource": "/api/scrape",
"description": "Pay-per-call scraper",
"expectedTaskHash": "<hex 32B SHA-256 of canonical request>",
"extra": {
"custodyProgramId": "DbpCDp…nFp",
"attestorProgramId": "DcbEtM…Xaa",
"attestorPubkey": "<base58>"
}
}
]
}X-Payment header (base64 JSON envelope)
{
"version": 1,
"scheme": "aap-solana",
"payload": {
"escrow_pda": "<base58>",
"agent_owner": "<base58>",
"task_hash": "<hex>"
}
}The agent computes task_hash deterministically from resource ‖ body ‖ agent_owner using SHA-256 — the server re-derives the same hash and rejects mismatches.
Response headers (200 with payment)
HTTP/1.1 200 OK
content-type: application/json
x-payment-settled: true
x-aap-tx-sig: 4DbqF4hG1sXmUKpPnrX3frPSMkB7ZpCdJ7VhvVQ78gPEbe7…
x-aap-proof-hash: a8a40b252bb74302…Failure mode
If the on-chain settle fails after the handler ran (network blip, stale attestation, etc.) the response still flushes but with
x-payment-settled: false and x-aap-error headers. The escrow stays in Pending and the agent can claim a refund after TTL.Reference: spec compatibility
The challenge envelope follows x402.org conventions with a custom aap-solana scheme. Standard x402Version, accepts, maxAmountRequired, asset, payTo fields are preserved so existing x402 client libraries can reach the challenge surface without modification.