x402 Express Server

x402 Solana Protocol Implementation
A TypeScript implementation of the x402 payment protocol for Solana blockchain, featuring instant finality, sponsored transactions, and replay attack protection.
Overview
The x402 protocol enables payment-gated access to web resources through Solana blockchain payments. This implementation provides:
- Instant Finality: Client funds move immediately to merchant (on-chain settlement)
- Sponsored Transactions: Facilitator pays gas fees, client authorizes SOL transfer
- Replay Protection: Cryptographic nonce system prevents duplicate payments
- TypeScript: Full type safety with Zod validation
- Production Ready: PM2 process management, structured logging, error handling
Architecture
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ │ HTTP │ │ HTTP │ │
│ Client App │────────>│ Server App │────────>│ Facilitator App │
│ │ │ │ │ │
│ • Signs auth │ │ • x402 mw │ │ • Verifies sig │
│ • Signs tx │ │ • Protected │ │ • Adds fee sig │
│ • Sends both │ │ routes │ │ • Broadcasts tx │
└─────────────────┘ └──────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Solana Devnet │
│ (Blockchain) │
└─────────────────┘Flow
- Client creates and signs Solana transaction (client -> merchant transfer)
- Client signs authorization payload (for replay protection)
- Client sends both to server via HTTP header
- Server middleware extracts payment and forwards to facilitator
- Facilitator verifies signatures and checks nonce
- Facilitator adds signature as fee payer (sponsors transaction)
- Facilitator broadcasts to Solana blockchain
- Instant Settlement: Client's SOL moves to merchant on-chain
- Server delivers protected resource
Project Structure
x402_ts/
├── src/
│ ├── facilitator/
│ │ ├── index.ts # Main facilitator app entry
│ │ └── nonce.db # SQLite database (runtime, gitignored)
│ ├── server/
│ │ └── index.ts # Main server app entry
│ ├── errors/
│ │ └── index.ts # Custom error classes
│ ├── lib/
│ │ ├── api-logger.ts # Structured logging
│ │ ├── api-response-helpers.ts # Standardized responses
│ │ ├── get-facilitator-config.ts # Zod config validation
│ │ ├── get-facilitator-context.ts # Dependency injection
│ │ ├── get-server-config.ts # Server config validation
│ │ ├── get-server-context.ts # Server context
│ │ ├── nonce-database.ts # SQLite nonce management
│ │ ├── payment-request.ts # Payment payload structures
│ │ ├── solana-utils.ts # Solana/Gill SDK utilities
│ │ └── x402-middleware.ts # Express middleware
│ └── routes/
│ ├── health.ts # Health check endpoint
│ ├── verify.ts # Payment verification
│ ├── settle.ts # Payment settlement
│ ├── nonce.ts # Nonce management
│ ├── stats.ts # Statistics
│ └── index.ts # Route exports
├── dist/ # Compiled TypeScript output (gitignored)
├── logs/ # PM2 log files (gitignored)
├── test-true-x402.mjs # TRUE x402 instant finality test
├── test-replay-attack.mjs # Replay attack prevention test
├── test-402-response.mjs # HTTP 402 response test
├── generate-test-client.mjs # Generate test client wallet
├── ecosystem.config.cjs # PM2 configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
└── .env # Environment configurationQuick Start
Prerequisites
Make sure you are using Node 22+ so that node_sqlite3, needed for the facilitator, is configured correctly.
1. Install Dependencies
npm install2. Configure Environment
cp env.example .env
# Edit .env with your configurationRequired environment variables:
FACILITATOR_PRIVATE_KEY- Facilitator's private key (base58)SOLANA_RPC_URL- Solana RPC endpoint (default: devnet)SIMULATE_TRANSACTIONS- Set tofalsefor real blockchain transactions
3. Build TypeScript
npm run build4. Start Applications
# Start both facilitator and server with PM2
npm start
# View logs
npm run logs
# Stop applications
npm stopThe facilitator runs on port 3001, server on port 3000 (configurable in .env).
Testing
Run All Tests
# Make sure apps are running first
npm start
# Run main test (TRUE x402 instant finality)
npm test
# Test HTTP 402 response (missing payment)
npm run test:402
# Test replay attack prevention
npm run test:replayGenerate Test Client Wallet
npm run generate:client
# Creates test-client-keypair.json with a new walletFund Test Wallet on Devnet
solana airdrop 1 <YOUR_CLIENT_PUBLIC_KEY> --url devnetHow It Works
TRUE x402 Protocol (Instant Finality)
This implementation uses sponsored transactions for TRUE x402 instant finality:
Client Side:
- Creates authorization payload with nonce
- Signs authorization (Ed25519 signature for replay protection)
- Creates Solana transaction (client -> merchant transfer)
- Signs transaction with their private key
- Sends both to server
Facilitator Side:
- Verifies authorization signature
- Checks nonce is unused (prevents replay attacks)
- Marks nonce as used immediately
- Deserializes client-signed transaction
- Adds facilitator signature as fee payer
- Broadcasts to Solana blockchain
- Client's SOL moves to merchant instantly
Result:
- Client's funds committed on-chain (instant finality)
- Facilitator paid gas fee
- No debt tracking needed
- Single atomic transaction
Payment Request Structure
{
payload: {
amount: "10000000", // Amount in lamports
recipient: "merchant_address", // Merchant Solana address
resourceId: "/api/resource", // Resource identifier
resourceUrl: "/api/resource", // Resource URL
nonce: "unique_hex_string", // Cryptographic nonce
timestamp: 1234567890, // Unix timestamp
expiry: 1234571490 // Expiration timestamp
},
signature: "base58_signature", // Client's Ed25519 signature
clientPublicKey: "client_pub", // Client's Solana public key
signedTransaction: "base64_tx" // Client-signed Solana transaction
}Nonce System (Replay Protection)
The nonce database prevents replay attacks:
- Each payment request includes a unique nonce
- Nonce is stored and marked as "used" during verification
- Subsequent requests with same nonce are rejected
- Nonces expire after 24 hours (configurable)
- Automatic cleanup removes expired nonces
Protected Endpoints
The server provides examples of payment-protected routes:
// Public endpoint (no payment required)
GET /public
// Protected endpoint (requires x402 payment)
GET /api/premium-data
- Requires 0.01 SOL payment
- Returns premium content after payment verification
// Other protected routes
POST /api/generate-content
GET /api/download/:fileId
GET /api/tier/:tierAPI Reference
Facilitator Endpoints
Health Check
GET /health
Response: { success: true, data: { status: "healthy", ... } }Verify Payment
POST /verify
Body: { paymentRequest: "serialized_payment_request" }
Response: { success: true, data: { verified: true, ... } }Settle Payment
POST /settle
Body: { paymentRequest: "serialized_payment_request" }
Response: { success: true, data: { transactionSignature: "...", ... } }Get Nonce Status
GET /nonce/:nonce
Response: { success: true, data: { nonce: "...", usedAt: ..., ... } }Get Statistics
GET /stats
Response: {
success: true,
data: {
totalNonces: 25,
usedNonces: 25,
activeNonces: 0
}
}Server Endpoints
Health Check
GET /health
Response: { success: true, data: { status: "healthy", facilitator: {...} } }Public Endpoint
GET /public
Response: { success: true, data: { message: "No payment required" } }Protected Endpoint
GET /api/premium-data
Headers: { X-Payment: "serialized_payment_request" }
Response: { success: true, data: { secret: "premium content", ... } }Development
Run in Development Mode
# Terminal 1: Facilitator
npm run dev:facilitator
# Terminal 2: Server
npm run dev:serverCode Quality
# Run linter
npm run lint
# Format code
npm run fmt
# Check formatting
npm run fmt:checkTechnology Stack
- Runtime: Node.js with TypeScript
- Framework: Express.js
- Blockchain: Solana (via Gill SDK and @solana/web3.js)
- Database: SQLite3 (for nonce management)
- Process Management: PM2
- Validation: Zod
- Logging: Structured logging with timestamps
- Testing: Custom test scripts using native fetch
Configuration
Environment Variables
See env.example for all available configuration options:
# Facilitator
FACILITATOR_PORT=3001
FACILITATOR_PRIVATE_KEY=<base58_private_key>
SOLANA_RPC_URL=https://api.devnet.solana.com
SIMULATE_TRANSACTIONS=false
MAX_PAYMENT_AMOUNT=1000000000
# Server
SERVER_PORT=3000
FACILITATOR_URL=http://localhost:3001
MERCHANT_SOLANA_ADDRESS=<merchant_public_key>
# Solana
SOLANA_NETWORK=devnetPM2 Configuration
The ecosystem.config.cjs file configures PM2 process management:
- Auto-restart on crashes
- Environment variable loading
- Separate log files for each app
- Memory limits and monitoring
Troubleshooting
Common Issues
Apps won't start
- Check
.envfile exists and has validFACILITATOR_PRIVATE_KEY - Ensure ports 3000 and 3001 are available
- Check PM2 logs:
npm run logs
Tests fail with "insufficient SOL"
- Fund test client wallet:
solana airdrop 1 <address> --url devnet - Check
SIMULATE_TRANSACTIONS=falsein.env
Replay attack test succeeds twice
- Restart facilitator to reset nonce database
- Check facilitator logs for nonce verification
Transaction fails on-chain
- Ensure facilitator has SOL for gas fees
- Check Solana devnet is operational
- Verify RPC endpoint is accessible
Security Considerations
- Private Keys: Never commit
.envor keypair files to git - Nonce Database: Stored locally, contains transaction history
- HTTPS: Use HTTPS in production for all HTTP communication
- Rate Limiting: Implement rate limiting on facilitator endpoints
- Input Validation: All inputs validated with Zod schemas
- Error Handling: Structured errors without sensitive data exposure
License
MIT
Contributing
This implementation follows the Gill Node Express template patterns and addresses PR feedback for clean, maintainable code. Contributions should maintain:
- TypeScript with strict type checking
- ES modules (import/export)
- Structured error handling
- Native fetch (no axios)
- Direct module consumption (no HTTP calls to own endpoints)
- PM2 process management
- Zod validation
- Context pattern for dependency injection
Credits
Built with:
- Gill SDK - Solana TypeScript SDK
- @solana/web3.js - Solana JavaScript API
- Express.js - Web framework
- PM2 - Process manager
- Zod - Schema validation
