# x402-xpr-hello-world — Pay with XPR on the Web This project demonstrates a paywalled web app using HTTP 402, XPR (Xprnetwork/Proton), and optional a facilitator Worker for JWT-based proof of payment. It is designed to be reproducible: you can recreate it from this description and the linked docs. ## Summary - **Main app:** Cloudflare Worker (Hono) that serves free routes and one paywalled route. Unpaid requests to the protected route get 402 with payment details; after paying on XPR Network, the client sends the transaction id (or a facilitator-issued JWT) to unlock. - **Optional facilitator:** A second Worker that verifies payments on Proton (Hyperion) and issues short-lived JWTs so the main Worker can trust payment without calling the chain on every request. Supports single-tenant (one shared secret) or multi-tenant (per-partner registration and secrets). - **Payment flow:** 402 → pay on Proton → proof via tx_id or Bearer token → 200 and content. Each tx_id is one-time (stored in KV). ## Stack - Runtime: Cloudflare Workers - Framework: Hono - Chain: XPR Network (Proton) mainnet — eosio.token (XPR), xtokens (XUSDC) - Verification: Hyperion history API for transaction lookup - Optional: Facilitator Worker (separate deploy) for JWT issuance; HS256 JWT with shared or per-audience secret ## Repo structure - src/index.ts — Main Worker: routes /, /ai, /x402, /xpr, /readme, /docs/facilitator, /docs/samples, /llm.txt, /llms.txt, /llm.md, /robots.txt, /sitemap.xml, /protected-route; 402 payload; JWT verification; KV for used tx_ids - facilitator/src/index.ts — Facilitator Worker: POST /verify (tx_id, resource, optional audience); POST /register and PUT /keys/:audience (admin); KV for issued tx_ids and partner secrets - wrangler.toml — Main Worker config (vars, KV, routes) - facilitator/wrangler.toml — Facilitator config - docs/FACILITATOR_FLOW_AND_TESTING.md — Full flow, setup, and testing (PowerShell + curl) - README.md — User-facing docs, config, deploy, JWT secret, multi-tenant ## Environment (main Worker) - XPR_RECEIVER_ACCOUNT — Account that receives payments - XPR_MIN_AMOUNT — e.g. "0.1000 XPR" or "0.100000 XUSDC" - USED_TX_IDS — KV namespace for one-time tx_id use (replay protection) - FACILITATOR_URL — (optional) Base URL of facilitator; 402 includes facilitatorUrl - FACILITATOR_JWT_SECRET — (optional) Secret to verify facilitator JWTs - FACILITATOR_AUDIENCE — (optional) Audience id when using shared facilitator; 402 includes facilitatorAudience ## Environment (facilitator) - XPR_RECEIVER_ACCOUNT, XPR_MIN_AMOUNT — Same semantics as main Worker - USED_TX_IDS — KV for "already issued" tx_ids - FACILITATOR_ADMIN_SECRET — Required for POST /register and PUT /keys/:audience - FACILITATOR_JWT_SECRET — (single-tenant only) Shared secret to sign JWTs; omit for multi-tenant and use partner secrets from KV ## How to recreate 1. Clone or create a Worker project with Hono. Implement GET / that returns a simple page and JSON for API clients. 2. Add a protected route that, when no payment proof is present, returns 402 with a JSON body containing payTo (receiver), maxAmountRequired, resource, network (e.g. proton-mainnet), and howToPay. Use x402-style fields (see README and x402.org). 3. Implement payment verification: on receipt of tx_id (prefer headers like X-Payment-Authorization / Authorization: Payment; query is legacy/dev convenience), call Proton Hyperion to fetch the transaction; check that it includes a transfer of the required token to the receiver with amount >= min; store tx_id in KV and return 200. Reject if tx_id already in KV. 4. Optional: Add a facilitator Worker that accepts POST /verify with tx_id and resource; verifies the tx on Hyperion; stores "issued" in KV; returns a short-lived JWT (e.g. 5 min) signed with HMAC-SHA256. Main Worker then accepts Authorization: Bearer , verifies JWT, extracts tx_id, marks used in KV, returns 200. 5. For multi-tenant facilitator: store per-audience secrets in KV; register via POST /register (admin); sign JWTs with the audience’s secret when client sends audience in POST /verify. 6. Build steps: inline README and facilitator doc into TS (e.g. scripts/inline-readme.cjs, inline-facilitator-doc.cjs) so GET /readme and GET /docs/facilitator serve HTML; optionally inline llm.txt for GET /llm.txt. 7. Deploy main Worker and optionally facilitator with wrangler; set secrets (FACILITATOR_JWT_SECRET, FACILITATOR_ADMIN_SECRET) via wrangler secret put. ## Key URLs (this deployment) - Site: https://xpr.testsitout.com - Readme (HTML): https://xpr.testsitout.com/readme - Readme (raw markdown): https://xpr.testsitout.com/readme?format=md - Facilitator flow doc (HTML): https://xpr.testsitout.com/docs/facilitator - Facilitator flow (raw markdown): https://xpr.testsitout.com/docs/facilitator?format=md - Client & agent samples (HTML): https://xpr.testsitout.com/docs/samples - Samples (raw markdown): https://xpr.testsitout.com/docs/samples?format=md - This file: https://xpr.testsitout.com/llm.txt - Markdown version: https://xpr.testsitout.com/llm.md ## Easy test flows Get 402 (payment required) as JSON (required for API/agent; omit header to get HTML in browser): curl -sS -H "Accept: application/json" https://xpr.testsitout.com/protected-route Unlock with transaction id (after paying): curl -sS -H "Accept: application/json" -H "X-Payment-Authorization: YOUR_TX_ID" "https://xpr.testsitout.com/protected-route" Full flow with facilitator (after paying, set TX_ID then): POST to https://facilitator-xpr.testsitout.com/verify with body {"tx_id":"TX_ID","resource":"/protected-route","audience":"mpc-test"}. If response 402, wait 2–5s and retry (indexer lag). Then GET https://xpr.testsitout.com/protected-route with Authorization: Bearer and Accept: application/json. Complete copy-paste curl sequence at /docs/samples. ## Flows in practice - Browser: open protected URL → pay → paste tx_id in form or use facilitator token (POST /verify then Bearer). - Agent/CLI: GET 402 with Accept: application/json → parse payTo, maxAmountRequired, facilitatorUrl, facilitatorAudience, resource → pay → if facilitatorUrl: POST /verify (retry on 402 after 2s, 3s, 5s), then GET with Bearer; else GET with ?tx_id=. ## Samples (client and agentic) - Full facilitator curl flow and client/agent steps: https://xpr.testsitout.com/docs/samples (or ?format=md). Includes retry-on-402 (2s, 3s, 5s) for indexer lag. - Agentic: GET 402 → parse → pay → POST facilitator (retry 2s, 3s, 5s on 402) → GET with Bearer; or GET with ?tx_id= when no facilitator. ## References - x402: https://www.x402.org — payment standard; whitepaper and docs linked there - XPR Network: https://docs.xprnetwork.org - Source code: https://github.com/French-River-Hosting/xpr-x402 - Proton Hyperion (history): used for transaction lookup by id