Production Deployment Guide
Complete guide for deploying X402-enabled applications to production environments.
Pre-Deployment Checklist
Security
[ ] Use mainnet RPC URLs
[ ] Use real USDC mint address
[ ] Never log private keys or sensitive data
[ ] Use HTTPS for all API endpoints
[ ] Implement rate limiting
[ ] Set up monitoring and alerting
[ ] Configure reasonable payment timeouts
[ ] Implement proper error handling
[ ] Use environment variables for all secrets
[ ] Test with small amounts first
[ ] Set maximum payment limits
[ ] Implement wallet balance monitoring
[ ] Enable CORS properly
[ ] Use hardware wallets for hot wallets (if applicable)
Infrastructure
[ ] Set up load balancing
[ ] Configure auto-scaling
[ ] Implement health checks
[ ] Set up logging aggregation
[ ] Configure backup strategy
[ ] Set up CI/CD pipeline
[ ] Configure CDN (if applicable)
[ ] Set up database backups (if applicable)
Environment Configuration
Production Environment Variables
# Solana Configuration
SOLANA_RPC_URL = https://api.mainnet-beta.solana.com
PAYMENT_WALLET_ADDRESS = <your-mainnet-wallet>
USDC_MINT_ADDRESS = EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
X402_NETWORK = solana-mainnet
# API Configuration
NODE_ENV = production
PORT = 8080
LOG_LEVEL = info
# Security
CORS_ORIGIN = https://yourdomain.com
RATE_LIMIT_WINDOW = 900000 # 15 minutes
RATE_LIMIT_MAX_REQUESTS = 100
# Monitoring
SENTRY_DSN = <your-sentry-dsn>
Mainnet Addresses
# Solana Mainnet
USDC_MINT = EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
USDC_DECIMALS = 6
# RPC Endpoints (use multiple for redundancy)
PRIMARY_RPC = https://api.mainnet-beta.solana.com
BACKUP_RPC = https://solana-api.projectserum.com
1. Vercel (TypeScript/Next.js)
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel --prod
# Set environment variables
vercel env add SOLANA_RPC_URL
vercel env add PAYMENT_WALLET_ADDRESS
# ... add all required env vars
vercel.json:
{
"env" : {
"SOLANA_RPC_URL" : "@solana_rpc_url" ,
"PAYMENT_WALLET_ADDRESS" : "@payment_wallet"
},
"regions" : [ "iad1" ],
"functions" : {
"api/**/*.ts" : {
"maxDuration" : 10
}
}
}
2. Fly.io (Python/FastAPI)
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Initialize
flyctl launch
# Set secrets
flyctl secrets set SOLANA_RPC_URL = https://api.mainnet-beta.solana.com
flyctl secrets set PAYMENT_WALLET_ADDRESS = your_wallet
flyctl secrets set USDC_MINT_ADDRESS = EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
# Deploy
flyctl deploy
fly.toml:
app = "x402-api"
primary_region = "iad"
[build]
builder = "paketobuildpacks/builder:base"
[env]
PORT = "8080"
WORKERS = "4"
[[services]]
internal_port = 8080
protocol = "tcp"
[[services.ports]]
port = 80
handlers = [ "http" ]
[[services.ports]]
port = 443
handlers = [ "tls" , "http" ]
[[services.http_checks]]
interval = "10s"
timeout = "2s"
grace_period = "5s"
method = "GET"
path = "/health"
3. Railway (Node.js/Deno)
# Install Railway CLI
npm i -g @railway/cli
# Login
railway login
# Initialize project
railway init
# Set variables
railway variables set SOLANA_RPC_URL = https://api.mainnet-beta.solana.com
# Deploy
railway up
4. AWS (Docker/ECS)
Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD [ "uvicorn" , "main:app" , "--host" , "0.0.0.0" , "--port" , "8080" ]
Deploy to ECS:
# Build and push to ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.com
docker build -t x402-api .
docker tag x402-api:latest <account-id>.dkr.ecr.us-east-1.amazonaws.com/x402-api:latest
docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/x402-api:latest
# Deploy via ECS
aws ecs update-service --cluster x402-cluster --service x402-service --force-new-deployment
5. Google Cloud Run
# Build container
gcloud builds submit --tag gcr.io/PROJECT-ID/x402-api
# Deploy
gcloud run deploy x402-api \
--image gcr.io/PROJECT-ID/x402-api \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars SOLANA_RPC_URL = https://api.mainnet-beta.solana.com,PAYMENT_WALLET_ADDRESS= your_wallet
High Availability Setup
Load Balancing
# nginx.conf
upstream x402_backend {
least_conn ;
server backend1 : 8080 max_fails=3 fail_timeout=30s ;
server backend2 : 8080 max_fails=3 fail_timeout=30s ;
server backend3 : 8080 max_fails=3 fail_timeout=30s ;
}
server {
listen 443 ssl http2 ;
server_name api.yourdomain.com ;
ssl_certificate /etc/ssl/certs/cert.pem ;
ssl_certificate_key /etc/ssl/private/key.pem ;
location / {
proxy_pass http://x402_backend ;
proxy_http_version 1 .1 ;
proxy_set_header Upgrade $http_upgrade ;
proxy_set_header Connection 'upgrade' ;
proxy_set_header Host $host ;
proxy_set_header X-Real-IP $remote_addr ;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ;
proxy_set_header X-Forwarded-Proto $scheme ;
proxy_cache_bypass $http_upgrade ;
}
location /health {
access_log off ;
return 200 "healthy\n" ;
}
}
Redis Caching for Payment Verification
import redis
import json
class CachedPaymentService :
def __init__ ( self , payment_service , redis_url ):
self . payment_service = payment_service
self . redis = redis . from_url ( redis_url )
self . cache_ttl = 3600 # 1 hour
async def verify_payment ( self , tx_hash , amount , recipient ):
# Check cache
cache_key = f "payment: { tx_hash } "
cached = self . redis . get ( cache_key )
if cached :
return json . loads ( cached )
# Verify payment
is_valid = await self . payment_service . verify_payment (
tx_hash , amount , recipient
)
# Cache result
self . redis . setex (
cache_key ,
self . cache_ttl ,
json . dumps ( is_valid )
)
return is_valid
Monitoring & Observability
Health Checks
# health.py
from fastapi import APIRouter
from datetime import datetime
router = APIRouter ()
@router . get ( "/health" )
async def health_check ():
return {
"status" : "healthy" ,
"timestamp" : datetime . utcnow () . isoformat (),
"version" : "1.0.0"
}
@router . get ( "/health/ready" )
async def readiness_check ():
# Check dependencies
try :
await check_rpc_connection ()
await check_database_connection ()
return { "status" : "ready" }
except Exception as e :
return { "status" : "not ready" , "error" : str ( e )}, 503
Logging
import logging
import json
# Structured logging
class JSONFormatter ( logging . Formatter ):
def format ( self , record ):
log_data = {
"timestamp" : datetime . utcnow () . isoformat (),
"level" : record . levelname ,
"message" : record . getMessage (),
"module" : record . module ,
"function" : record . funcName ,
}
if record . exc_info :
log_data [ "exception" ] = self . formatException ( record . exc_info )
return json . dumps ( log_data )
# Configure logger
handler = logging . StreamHandler ()
handler . setFormatter ( JSONFormatter ())
logger = logging . getLogger ( "x402" )
logger . addHandler ( handler )
logger . setLevel ( logging . INFO )
# Usage
logger . info ( "Payment verified" , extra = {
"tx_hash" : tx_hash ,
"amount" : amount ,
"recipient" : recipient
})
Metrics (Prometheus)
from prometheus_client import Counter , Histogram , generate_latest
# Define metrics
payment_requests = Counter (
'x402_payment_requests_total' ,
'Total payment requests' ,
[ 'status' ]
)
payment_verification_duration = Histogram (
'x402_payment_verification_seconds' ,
'Payment verification duration'
)
# Use metrics
@payment_verification_duration . time ()
async def verify_payment ( tx_hash ):
try :
result = await processor . verify_transaction ( tx_hash )
payment_requests . labels ( status = 'success' ) . inc ()
return result
except Exception as e :
payment_requests . labels ( status = 'error' ) . inc ()
raise
# Expose metrics
@app . get ( "/metrics" )
async def metrics ():
return Response ( generate_latest (), media_type = "text/plain" )
Security Best Practices
1. Rate Limiting
from slowapi import Limiter , _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
limiter = Limiter ( key_func = get_remote_address )
app . state . limiter = limiter
app . add_exception_handler ( RateLimitExceeded , _rate_limit_exceeded_handler )
@app . get ( "/api/premium" )
@limiter . limit ( "10/minute" )
async def premium_endpoint ( request : Request ):
# ...
2. CORS Configuration
from fastapi.middleware.cors import CORSMiddleware
app . add_middleware (
CORSMiddleware ,
allow_origins = [ "https://yourdomain.com" ], # Specific origins only
allow_credentials = True ,
allow_methods = [ "GET" , "POST" ],
allow_headers = [ "X-Payment-Authorization" ],
)
3. Secrets Management
# Use environment variables
import os
from pathlib import Path
def load_secrets ():
# For local development
if Path ( ".env" ) . exists ():
from dotenv import load_dotenv
load_dotenv ()
# For production, use secrets manager
if os . getenv ( "ENV" ) == "production" :
# AWS Secrets Manager
import boto3
client = boto3 . client ( 'secretsmanager' )
secret = client . get_secret_value ( SecretId = 'x402/production' )
# Parse and set secrets
return {
"rpc_url" : os . getenv ( "SOLANA_RPC_URL" ),
"wallet" : os . getenv ( "PAYMENT_WALLET_ADDRESS" ),
# Never log these values
}
Database Considerations
Payment State Management
-- PostgreSQL schema for payment tracking
CREATE TABLE payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
transaction_hash VARCHAR ( 255 ) UNIQUE NOT NULL ,
payer_address VARCHAR ( 255 ) NOT NULL ,
recipient_address VARCHAR ( 255 ) NOT NULL ,
amount DECIMAL ( 20 , 6 ) NOT NULL ,
token_mint VARCHAR ( 255 ) NOT NULL ,
verified BOOLEAN DEFAULT FALSE ,
verified_at TIMESTAMP ,
created_at TIMESTAMP DEFAULT NOW (),
INDEX idx_tx_hash ( transaction_hash ),
INDEX idx_payer ( payer_address )
);
-- Payment requests tracking
CREATE TABLE payment_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
payment_id VARCHAR ( 255 ) UNIQUE NOT NULL ,
resource VARCHAR ( 255 ) NOT NULL ,
amount DECIMAL ( 20 , 6 ) NOT NULL ,
expires_at TIMESTAMP NOT NULL ,
fulfilled BOOLEAN DEFAULT FALSE ,
created_at TIMESTAMP DEFAULT NOW (),
INDEX idx_payment_id ( payment_id )
);
Backup & Disaster Recovery
Database Backups
# Automated PostgreSQL backup
#!/bin/bash
DATE = $( date +%Y%m%d_%H%M%S)
BACKUP_DIR = "/backups"
pg_dump -U postgres x402_db | gzip > " $BACKUP_DIR /backup_ $DATE .sql.gz"
# Upload to S3
aws s3 cp " $BACKUP_DIR /backup_ $DATE .sql.gz" s3://x402-backups/
# Cleanup old backups (keep last 30 days)
find $BACKUP_DIR -type f -mtime +30 -delete
Wallet Backup
# CRITICAL: Store wallet keys securely
# 1. Use hardware wallet for hot wallet
# 2. Store backup phrase in secure location
# 3. Use multi-signature for large amounts
# 4. Regular security audits
Connection Pooling
from sqlalchemy.pool import QueuePool
engine = create_engine (
DATABASE_URL ,
poolclass = QueuePool ,
pool_size = 20 ,
max_overflow = 10 ,
pool_pre_ping = True ,
pool_recycle = 3600 ,
)
RPC Request Optimization
class OptimizedRPCClient :
def __init__ ( self , primary_url , backup_urls ):
self . primary = primary_url
self . backups = backup_urls
self . current_index = 0
async def request ( self , method , params ):
urls = [ self . primary ] + self . backups
for url in urls :
try :
return await self . _make_request ( url , method , params )
except Exception as e :
logger . warning ( f "RPC request failed on { url } : { e } " )
continue
raise Exception ( "All RPC endpoints failed" )
Scaling Strategies
Horizontal Scaling
# Kubernetes deployment
apiVersion : apps/v1
kind : Deployment
metadata :
name : x402-api
spec :
replicas : 3
selector :
matchLabels :
app : x402-api
template :
metadata :
labels :
app : x402-api
spec :
containers :
- name : x402-api
image : x402-api:latest
ports :
- containerPort : 8080
env :
- name : SOLANA_RPC_URL
valueFrom :
secretKeyRef :
name : x402-secrets
key : rpc-url
resources :
requests :
memory : "256Mi"
cpu : "250m"
limits :
memory : "512Mi"
cpu : "500m"
livenessProbe :
httpGet :
path : /health
port : 8080
initialDelaySeconds : 30
periodSeconds : 10
readinessProbe :
httpGet :
path : /health/ready
port : 8080
initialDelaySeconds : 5
periodSeconds : 5
Cost Optimization
RPC Cost Management
# Use caching to reduce RPC calls
class CachedRPCClient :
def __init__ ( self , rpc_url , cache_ttl = 60 ):
self . client = AsyncClient ( rpc_url )
self . cache = {}
self . cache_ttl = cache_ttl
async def get_transaction ( self , tx_hash ):
if tx_hash in self . cache :
cached_time , result = self . cache [ tx_hash ]
if time . time () - cached_time < self . cache_ttl :
return result
result = await self . client . get_transaction ( tx_hash )
self . cache [ tx_hash ] = ( time . time (), result )
return result
See Also