Home / Part 3 / Build a USDC Paywall

Build a USDC Paywall

Learn to design and ship a USDC paywall with a clear plan: problem framing, architecture, phases, and tradeoffs.

USDC paywall from idea to execution
Build a simple payment gate for content or SaaS.

Learning Objective

  • Translate a product goal into an on-chain design
  • Map the system architecture (client + program + token accounts)
  • Plan a build with phases, tests, and tradeoffs

What to Expect

  • Problem framing and constraints
  • Architecture and data flow
  • Step-by-step execution checklist

Analyze the Problem

  • Goal: user pays USDC to unlock premium content.
  • User story: pay once, get access immediately.
  • Constraints: minimize fraud, confirm payment, keep UX fast.
  • Success: payment verified, access granted, audit trail retained.
  • Edge cases: server watcher misses tx, duplicate payments, invalid memo, server restarts mid-invoice, pending payments past 5 minutes.
  • Recovery: client stores tx + invoice in localStorage and can trigger a manual recheck endpoint.

System Architecture

  • Client: wallet connects and signs a USDC transfer.
  • On-chain: Token Program transfers USDC to merchant account.
  • Off-chain: server checks transaction signature and grants access.
  • Data: record tx signature, buyer wallet, and access window.

User Story: USDC Paywall (UML)

Use Case Diagram (Mermaid)

Request-to-pay Sequence Diagram

The system flow when User click pay -> Server return invoice_id -> User pay USDC with invoice_id as Txn Memo.
Server poll blockchain to see if there is any txn with correct information {invoice_id, amount, token=USDC, wallet} -> update wallet as paid in database

Tech Stack

  • Client: React.js + Solana Wallet Adapter
  • Server: Go + PostgreSQL
  • Solana Network: Devnet
  • RPC Provider: Hosted RPC (API key)
  • Deployment: DigitalOcean droplet (server) + static hosting for client

Implementation Phases

Focus: client code and reading blockchain state.

  1. User opens the dapp, connects wallet, and sees USDC balance on devnet.
  2. Admin airdrops USDC devnet to the user.
  3. User opens the site again, reconnects, and sees the updated balance.

Check out code here

Focus: server + database for premium content gating.

  1. Build DB tables: paid_wallet{id, wallet, paid_at, invoice_id} and invoice{id, wallet, amount, sql_token_address, invoice_id_rndstr, status, txn, expired_at}.
  2. Implement GET /api/content with wallet param: return content if paid; else false.
  3. User opens dapp, connects wallet, requests /api/content, receives false, UI prompts to pay.

Check out code here

Focus: payment workflow on client + server.

Client

  1. Enable Pay only if balance >= required amount; otherwise disable.
  2. On Pay, call POST /api/invoice with {wallet, amount, sql_token_address}; receive invoice_id_rndstr.
  3. Create a tx with memo = invoice_id_rndstr and request signature.
  4. Immediately persist invoice_id_rndstr before sending the tx to avoid a refresh losing state.
  5. After signature, poll /api/content every 15s for 3 minutes. If unlocked, show success + content. Else prompt refresh after 2 minutes. Store txn + invoice_id_rndstr in localStorage for dispute.
  6. If user refreshes right after signing and txn is missing, allow recheck by invoice + wallet.

Server

  1. POST /api/invoice checks if wallet already paid; validates amount + sql_token_address.
  2. Create invoice_id_rndstr and insert invoice row.
  3. Start a 5-minute watcher polling chain every 15s for a tx matching {wallet, amount, sql_token_address, invoice_id_rndstr}.
  4. On match, update invoice status and insert row into paid_wallet.
  5. Return invoice_id_rndstr to client.

Check out code here

Focus: test coverage, recheck flows, and production polish.

Client

  1. Persist txn + invoice_id_rndstr in localStorage after payment.
  2. If server returns “not paid,” show a Recheck Payment button.
  3. On click, call POST /api/recheck with {wallet, txn, invoice_id_rndstr}.
  4. Show retry/pending status and refresh the content on success.

Server

  1. Add POST /api/recheck to re-validate a completed tx.
  2. If the 5-minute watcher missed the tx, re-scan by signature + memo.
  3. Update invoice + paid_wallet rows when recheck succeeds.

Testing & Hardening

  1. Missing-tx edge case: watcher timeout + successful recheck.
  2. Double payment: prevent duplicate inserts and access conflicts.
  3. Invalid memo: reject and keep access locked.
  4. Partial failures: server restart with pending invoices.
  5. Rate limiting for recheck endpoint.

Check out code here

Checkpoint Quiz