A modern, developer-focused pay-per-use API demo server for the x402 protocol. Supports both x402 V1 and V2 protocols with CAIP-2 network format. Instantly test x402 payments, see live paywall enforcement, and get a rizzler GIF reward after payment—plus a full refund!
- x402 V2 protocol support: Uses CAIP-2 network format (e.g.,
eip155:8453) and V2 headers (PAYMENT-SIGNATURE,PAYMENT-RESPONSE) - Pay-per-use API endpoints on multiple networks (Base, Polygon, Avalanche, Sei, Solana, IoTeX, Peaq)
- x402 paywall middleware: Enforces payment before serving protected content
- Rizzler GIF reward: After payment, receive a fun GIF and full transaction/refund details
- 100% refunds: All payments are instantly refunded for demo/testing
- Modern UI: Built with Next.js, TailwindCSS, and shadcn/ui
- Edge-compatible: Middleware works on Vercel/Edge, with Node.js logic offloaded to API routes
- Request a paid endpoint (e.g.
/api/base/paid-content) - x402 middleware checks for payment and enforces the paywall
- After payment:
- Middleware verifies and settles the payment
- Instantly refunds the payment
- Returns a custom HTML page with:
- "Thank you for your payment! Have some rizz!"
- The rizzler
- Payment and refund transaction links
- The full payment response as a code snippet
-
Install dependencies:
npm install # or yarn install -
Set up environment variables:
- Copy
.env.exampleto.envand fill in required values (see below)
- Copy
-
Run the dev server:
npm run dev # or yarn dev- The app will be available at http://localhost:3000
-
NEXT_PUBLIC_SITE_URL- Base URL of the server (used by the web app) -
FACILITATOR_URL- URL of the x402 facilitator service (e.g.https://facilitator.payai.network) -
EVM_RECEIVE_PAYMENTS_ADDRESS- EVM address to receive payments to -
SVM_RECEIVE_PAYMENTS_ADDRESS- Solana address to receive payments to -
EVM_PRIVATE_KEY- EVM private key used to send refunds (hex string starting with0x) -
SVM_PRIVATE_KEY- Solana private key used to send refunds -
BASE_RPC_URL- Base Mainnet RPC URL (https) -
BASE_SEPOLIA_RPC_URL- Base Sepolia RPC URL (https) -
AVALANCHE_RPC_URL- Avalanche Mainnet RPC URL (https) -
AVALANCHE_FUJI_RPC_URL- Avalanche Fuji Testnet RPC URL (https) -
SEI_RPC_URL- Sei Mainnet RPC URL (https) -
SEI_TESTNET_RPC_URL- Sei Testnet RPC URL (https) -
POLYGON_RPC_URL- Polygon Mainnet RPC URL (https) -
POLYGON_AMOY_RPC_URL- Polygon Amoy Testnet RPC URL (https) -
SOLANA_RPC_URL- Solana Mainnet RPC URL (https) -
SOLANA_DEVNET_RPC_URL- Solana Devnet RPC URL (https) -
SOLANA_WS_URL- (optional) Solana Mainnet WebSocket URL (wss) -
SOLANA_DEVNET_WS_URL- (optional) Solana Devnet WebSocket URL (wss) -
IOTEX_RPC_URL- (optional) IoTeX Mainnet RPC URL (https) -
PEAQ_RPC_URL- Peaq Mainnet RPC URL (https) -
XLAYER_RPC_URL- xLayer Mainnet RPC URL (https) -
XLAYER_TESTNET_RPC_URL- xLayer Testnet RPC URL (https)
Pin @payai/x402, @payai/x402-evm, @payai/x402-next, @payai/facilitator, @payai/x402-svm, and @payai/x402-extensions to the same minor version as the facilitator you run against (see payai-x402-facilitator’s apps/api/package.json). Mismatched versions can break verify/settle or extension handling.
EVM routes default to EIP-3009 USDC. To opt into Permit2 (testnet demos or tokens without EIP-3009), extend RouteConfig in src/lib/x402-helpers.ts on each route:
| Field | Purpose |
|---|---|
assetTransferMethod?: 'eip3009' | 'permit2' |
Sets extra.assetTransferMethod on PaymentRequirements. Omit or use eip3009 for the legacy USDC path. |
permit2GasSponsoring?: 'none' | 'eip2612' | 'erc20' | 'both' |
Merges top-level PaymentRequired.extensions using @payai/x402-extensions only if GET /supported on FACILITATOR_URL lists the matching keys (eip2612GasSponsoring, erc20ApprovalGasSponsoring). |
permit2UserHint?: string |
Optional user-facing paywall line (e.g. one-time approval / gas coverage) without exposing raw protocol names. |
The Base Sepolia route (sepoliaConfig in src/middleware.ts) demonstrates permit2 + both gas-sponsoring declarations for integration testing.
Non-USDC or custom tokens need correct metadata (ERC20TokenAmount / EIP-712 domain) per SDK rules; wrong metadata will fail signing or settlement.
- Deploy to Vercel, your own Node.js server, or any platform supporting Next.js
- For Edge compatibility, all Node.js-only logic is handled in API routes, including: ---- authenticating with @coinbase/cdp-sdk which relies on the NodeJS crypto library
Apache V2
To add a new network (example: peaq) across the Echo Merchant UI, middleware, and API:
-
Make sure that you
npm installthex402package version that contains the new network. -
Frontend link on homepage
- Edit
src/app/page.tsx - Add a new entry to
MAINNET_ENDPOINTSorTESTNET_ENDPOINTS:{ label: 'Peaq Mainnet', url:${API_URL}/api/peaq/paid-content}
- Edit
-
Middleware route & config
- Edit
src/middleware.ts - Create a route config for the network:
const peaqConfig = { price: '$0.01', network: 'peaq', config: { description: '...' } }
- Wire the path to the paywall middleware:
if (pathname.startsWith('/api/peaq/')) { return paymentMiddleware(payToEVM, { '/api/peaq/paid-content': peaqConfig }, { url: facilitatorUrl })(request); }
- Add the matcher so the middleware runs:
- include
'/api/peaq/paid-content/:path*'inexport const config.matcher.
- include
- Edit
-
API route file
- Create
src/app/api/<network>/paid-content/route.tswith a basicGETthat returns{ ok: true }. - The actual paywall logic is enforced in middleware; the route acts as the endpoint.
- Create
-
Paywall app (wallet flow)
- Edit
src/paywall/src/PaywallApp.tsx - Import the chain from
viem/chainsand map it:- Import:
import { peaq } from 'viem/chains' - Add to
paymentChainswitch and tochainNamemapping.
- Import:
- Edit
-
Explorer links
- Edit
src/lib/utils.tsif you want explorer links for the new network in the rizzler page:- Update
getExplorerForNetworkandrenderRizzlerHtmlto include the new network.
- Update
- Edit
-
Environment variables
- Set
<NETWORK>_RPC_URLto a private custom RPC from Alchemy if possible, otherwise find a suitable RPC for the network and add it here.<NETWORK>is to be replaced by the network name. - Ensure you have
FACILITATOR_URLpointing to your facilitator (which must support the network). - Ensure
EVM_RECEIVE_PAYMENTS_ADDRESSis set for EVM networks. - For SVM networks, set
SVM_RECEIVE_PAYMENTS_ADDRESS.
- Set
-
Refund flow
- Edit
src/refund.tsand update the EVM signer factory:- Import your chain from
viem/chains(e.g.,peaq). - Add a
network === '<network>'branch ingetSignerthat returns acreateWalletClientwith the chain andprocess.env.<NETWORK>_RPC_URL.
- Import your chain from
- Ensure the
x402package version you installed includes the new network inSupportedEVMNetworksso the refund path triggers for EVM. - Set
<NETWORK>_RPC_URLin your.env(same value used bygetSigner).
- Edit
-
Test
- Visit
/api/<network>/paid-contentin a browser. - You should see the paywall, be able to connect a wallet, pay, and receive the rizzler page.
- Visit
A good way to test is by running the facilitator locally and using it to do an end-to-end test with the echo merchant. Make sure to read its README.md for how to set up a new network on the facilitator.