Drop-in Next.js 15 experience for creating paywalled QR codes that settle in USDC via @coinbase/x402. Replace the Nano Banana Cam imaging flow with:
- Two QR types
- Link – one-time ticket → HTTP 303 redirect to your target URL after payment.
- Product – collects payment → shows a green success screen with product name, masked payer, and sequential receipt number.
- Two creation paths
- Human UI web forms at
/create/linkand/create/product.
- Human UI web forms at
- Paid MCP tools served from
/api/mcpusingx402-mcp; agents invoke tools likecreate_link_qr/create_product_qrand receive QR PNG payloads after payment. - x402 pay endpoints
/api/pay/link/[slug]→ mints ticket →303→/go/[nonce]./api/pay/product/[id]→ stores receipt → mints ticket →303→/receipt/[nonce].
Persistence is handled with Upstash Redis (REST API). Tickets are single-use keys with TTL, verified by JWTs signed server-side.
app/
(public)/
create/ # Browser forms for human operators
link/page.tsx
product/page.tsx
p/[slug]/page.tsx # Link paywall UI
buy/[id]/page.tsx # Product paywall UI
api/
links/route.ts # POST create link (returns QR PNG base64)
products/route.ts # POST create product (returns QR PNG base64)
pay/
link/[slug]/route.ts # 402-protected link payment flow
product/[id]/route.ts # 402-protected product payment flow
mcp/route.ts # MCP server exposing paid QR creation tools
go/[nonce]/route.ts # Consumes ticket, 302 to target URL
receipt/[nonce]/route.ts # Consumes ticket, renders green receipt
lib/
origin.ts # resolves canonical origin for QR URLs
qr-service.ts # shared helpers for creating link/product records + QR PNGs
ids.ts # nanoid helpers for slug/id generation
money.ts # USD ⇄ micro-USDC helpers + address masking
qr.ts # QRCode buffer → base64 PNG
redis.ts # Upstash Redis REST client
tickets.ts # mint/spend/verify one-time tickets
x402.ts # enforceX402 helper (verify + settle + requirement builder)
x402_impl.ts # wraps @coinbase/x402 facilitator utilities
| Key | Value |
|---|---|
link:<slug> |
{ slug, targetUrl, priceMicroUSDC, network, payee } |
product:<id> |
{ id, name, priceMicroUSDC, network, payee } |
ticket:<nonce> |
{ kind: 'link' | 'receipt', refId, exp } (TTL via set + getdel) |
receipt:<global> |
{ id, productId, productName, payerAddress?, perProductSeq, txHash? } |
receipt:global_seq |
global receipt counter (INCR) |
receipt:seq:<productId> |
per-product counter (INCR) |
Prices are stored as micro-USDC integers (e.g. $1.23 → 1230000). Tickets expire after 5–10 minutes by default and are single-use.
Copy .env.local.example to .env.local and provide:
CDP_API_KEY_ID=...
CDP_API_KEY_SECRET=...
NEXT_PUBLIC_CDP_PROJECT_ID=...
RECIPIENT_WALLET_ADDRESS=0xYourWallet
NEXT_PUBLIC_NETWORK=base # or base-sepolia
NEXT_PUBLIC_USDC_CONTRACT=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=...
KV_REST_API_URL=https://your-kv-cluster.upstash.io
KV_REST_API_TOKEN=...
SIGNED_REDIRECT_SECRET=super-long-random-stringThe SIGNED_REDIRECT_SECRET secures the ticket JWT so only the server can mint/verify redirect tokens.
- Install dependencies
npm install
- Copy
.env.local.example→.env.localand fill in the variables above. - Run the dev server
npm run dev
- Visit:
http://localhost:3000/– agent chat demo with paid tool examples.http://localhost:3000/playground– job playground UI (connects to internal APIs).http://localhost:3000/create/link– human link QR creator.http://localhost:3000/create/product– human product QR creator.http://localhost:3000/p/<slug>– sample paywall (after QR scan).http://localhost:3000/buy/<id>– product paywall.
You’ll need a wallet funded with USDC on Base (or Base Sepolia for testing) plus an x402 facilitator API key.
- Client hits
/api/pay/link/[slug]or/api/pay/product/[id]without payment → receives402withaccepts. - Client retries using
x402-fetch, which signs and submits the payment authorization. lib/x402.tsverifies with@coinbase/x402and settles the payment.- On success:
- Links →
mintTicket("link")→ redirect to/go/<nonce>?t=<jwt>→ 302 to target URL after consuming the ticket. - Products →
mintTicket("receipt")+ receipt storage → redirect to/receipt/<nonce>?t=<jwt>which renders a full-page green success card.
- Links →
Tickets use redis.getdel so they cannot be replayed. Both /go/* and /receipt/* set Cache-Control: no-store.
Paid agent integrations live under a single MCP endpoint /api/mcp, powered by x402-mcp. Tools enforce an additional x402 fee (default $0.10) before creating the QR, and each response mirrors the human APIs (slug/id, pay_url, qr_png_base64).
| Tool name | Description |
|---|---|
create_link_qr |
Pay-gated creation of a link QR |
create_product_qr |
Pay-gated creation of a product QR |
Agents should call these with a JSON body matching the human forms (fields: target_url/name, price_usdc, payee_address, optional network).
wrapFetchWithPaymentin the paywall pages caps spend to10 USDCby default—tweak as needed.enforceX402returns payer + tx hash so you can enhance receipts or ledger entries.- Receipts expire after 1 hour. Increase TTL if you need longer-lived audit trails.
- Update
MCP_PRICE_USDCinapp/api/mcp/route.tsfor your desired agent fee.
- Original Nano Banana Cam template by @EstebanSuarez.
- x402 protocol and facilitator helpers by the Coinbase Developer Platform team.
This repo now focuses purely on QR monetization. No AI image generation, camera capture, or fal.ai dependencies remain.