Published 12/4/2025 · 7 min read
Tags: solana , javascript , x402 , deployment , production
We’ve built everything locally. Now let’s deploy for real users with real payments.
Architecture Overview
┌──────────────────┐ ┌──────────────────┐
│ SvelteKit App │ │ Bun API Server │
│ (Vercel/CF) │────▶│ (Fly.io/Rail) │
└──────────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ Solana Mainnet │
│ (via RPC) │
└──────────────────┘
We’ll deploy:
- Frontend: SvelteKit on Vercel or Cloudflare Pages
- API: Bun server on Fly.io or Railway
Production Checklist
Before deploying:
- Switch from devnet to mainnet
- Use mainnet USDC mint address
- Set up production RPC endpoint
- Configure real treasury wallet
- Set up error monitoring
- Enable HTTPS everywhere
Mainnet Configuration
Update your environment variables:
# .env.production
# Network
SOLANA_NETWORK=solana-mainnet
# Mainnet USDC
USDC_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
# Your treasury wallet (receives payments)
TREASURY_ADDRESS=YourMainnetWalletAddress
# Production RPC (get from Helius, QuickNode, etc.)
RPC_URL=https://your-rpc-provider.com/api-key
# Facilitator
FACILITATOR_URL=https://x402.org/facilitator
Getting a Production RPC
Public RPC endpoints are rate-limited. For production, use a dedicated provider:
Helius (Recommended for Solana)
RPC_URL=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY
QuickNode
RPC_URL=https://your-endpoint.solana-mainnet.quiknode.pro/YOUR_KEY
Triton
RPC_URL=https://your-project.rpcpool.com/YOUR_KEY
Most have free tiers sufficient for starting out.
Deploying the Bun API Server
Option 1: Fly.io
Fly.io has great Bun support:
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Login
fly auth login
# Initialize
cd your-api-directory
fly launch
Create fly.toml:
app = "your-x402-api"
primary_region = "iad"
[build]
builder = "paketobuildpacks/builder:base"
[env]
PORT = "8080"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 256
Create a Dockerfile for Bun:
FROM oven/bun:1 as base
WORKDIR /app
# Install dependencies
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
# Copy source
COPY . .
# Run
ENV NODE_ENV=production
EXPOSE 8080
CMD ["bun", "run", "server.ts"]
Set secrets:
fly secrets set TREASURY_ADDRESS=your_wallet
fly secrets set RPC_URL=your_rpc_url
fly secrets set SOLANA_NETWORK=solana-mainnet
fly secrets set USDC_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Deploy:
fly deploy
Option 2: Railway
Railway is even simpler:
- Connect your GitHub repo
- Railway auto-detects Bun
- Add environment variables in dashboard
- Deploy
# Or use CLI
railway login
railway init
railway up
Deploying SvelteKit Frontend
Vercel
SvelteKit works great on Vercel:
# Install adapter
bun add -D @sveltejs/adapter-vercel
Update svelte.config.js:
import adapter from "@sveltejs/adapter-vercel";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
export default {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
runtime: "edge", // or 'nodejs18.x'
}),
},
};
Deploy:
# Install Vercel CLI
bun add -g vercel
# Deploy
vercel
Set environment variables in Vercel dashboard:
PUBLIC_API_URL= your Fly.io API URLPUBLIC_SOLANA_NETWORK= solana-mainnet
Cloudflare Pages
# Install adapter
bun add -D @sveltejs/adapter-cloudflare
Update svelte.config.js:
import adapter from "@sveltejs/adapter-cloudflare";
export default {
kit: {
adapter: adapter(),
},
};
Deploy via Cloudflare dashboard or Wrangler CLI.
Environment Variables in SvelteKit
For client-side variables, prefix with PUBLIC_:
# .env
PUBLIC_API_URL=https://your-api.fly.dev
PUBLIC_SOLANA_NETWORK=mainnet-beta
PUBLIC_USDC_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Access in code:
import { PUBLIC_API_URL, PUBLIC_SOLANA_NETWORK } from "$env/static/public";
Updating the Wallet Store for Mainnet
// src/lib/stores/wallet.ts
import { PUBLIC_SOLANA_NETWORK } from "$env/static/public";
import { Connection, clusterApiUrl } from "@solana/web3.js";
// Use environment-based RPC
function getRpcUrl(): string {
if (PUBLIC_SOLANA_NETWORK === "mainnet-beta") {
// Use your production RPC
return import.meta.env.VITE_RPC_URL || clusterApiUrl("mainnet-beta");
}
return clusterApiUrl("devnet");
}
export const connection = derived(
wallet,
() => new Connection(getRpcUrl(), "confirmed")
);
CORS Configuration
Update your Bun server for production CORS:
const ALLOWED_ORIGINS = [
"https://your-app.vercel.app",
"https://your-domain.com",
];
const server = Bun.serve({
port: process.env.PORT || 3000,
async fetch(req) {
const origin = req.headers.get("origin");
const corsHeaders: Record<string, string> = {
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers":
"Content-Type, X-PAYMENT, X-Session-Token",
};
// Check origin
if (origin && ALLOWED_ORIGINS.includes(origin)) {
corsHeaders["Access-Control-Allow-Origin"] = origin;
}
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
// Your route handling...
const response = await handleRequest(req);
// Add CORS headers to response
Object.entries(corsHeaders).forEach(([key, value]) => {
response.headers.set(key, value);
});
return response;
},
});
Health Checks
Add a health endpoint for monitoring:
if (url.pathname === "/health") {
// Check RPC connection
try {
const slot = await connection.getSlot();
return Response.json({
status: "healthy",
slot,
network: process.env.SOLANA_NETWORK,
});
} catch {
return Response.json(
{ status: "unhealthy", error: "RPC connection failed" },
{ status: 503 }
);
}
}
Error Monitoring
Add Sentry or similar:
bun add @sentry/bun
import * as Sentry from "@sentry/bun";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
});
// In your error handling
try {
// ...
} catch (err) {
Sentry.captureException(err);
throw err;
}
Logging
Structured logging for production:
function log(level: "info" | "warn" | "error", message: string, data?: object) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...data,
};
console.log(JSON.stringify(entry));
}
// Usage
log("info", "Payment received", {
wallet: walletAddress,
amount: price,
endpoint: url.pathname,
});
Database for Sessions (Production)
Replace in-memory sessions with Redis:
bun add redis
import { createClient } from "redis";
const redis = createClient({
url: process.env.REDIS_URL,
});
await redis.connect();
async function createSession(walletAddress: string): Promise<string> {
const token = crypto.randomUUID();
await redis.setEx(
`session:${token}`,
86400, // 24 hours
JSON.stringify({
walletAddress,
createdAt: Date.now(),
requestsRemaining: 100,
})
);
return token;
}
async function getSession(token: string): Promise<Session | null> {
const data = await redis.get(`session:${token}`);
return data ? JSON.parse(data) : null;
}
Security Checklist
- HTTPS only (enforced by hosting provider)
- CORS restricted to your domains
- Rate limiting enabled
- No secrets in client-side code
- Treasury wallet secured (hardware wallet recommended)
- RPC API key not exposed to frontend
- Input validation on all endpoints
Monitoring Your Revenue
Create a simple dashboard endpoint:
if (url.pathname === "/admin/stats" && isAuthorized(req)) {
const stats = await getUsageStats();
return Response.json({
last24h: {
requests: stats.last24h,
revenue: stats.revenue / 1_000_000, // Convert to USDC
},
allTime: {
requests: stats.total,
revenue: stats.totalRevenue / 1_000_000,
},
topEndpoints: stats.byEndpoint,
});
}
Going Live Checklist
- Test on devnet thoroughly
- Switch environment to mainnet
- Deploy API server
- Deploy frontend
- Test with small mainnet payment ($0.01)
- Monitor logs for errors
- Set up alerts for failures
- Announce your launch!
What You Built
Congratulations! You’ve built a complete x402 payment system:
✅ Solana wallet connection in Svelte ✅ USDC payments for API access ✅ Session management for repeat users ✅ Dynamic pricing ✅ Production deployment
This is a real, monetizable product. The same architecture powers AI agent payments, premium APIs, and content paywalls across the x402 ecosystem.
What’s Next
Ideas to extend your x402 app:
- Subscription tiers - Weekly/monthly access tokens
- Referral system - Discounts for bringing new users
- Usage dashboard - Let users see their payment history
- Webhook notifications - Alert on payments received
- Multi-token support - Accept SOL, USDT, etc.
Resources
Series complete! You went from zero Solana knowledge to deploying a production payment system. That’s a real skill.
Build something cool. Ship it. Get paid.
🟢 Vue developer 🟠 Learned Svelte ⚡ Built on Solana 💰 x402 payments live
Related Articles
- Understanding requestAnimationFrame
A practical guide to browser animation timing. Learn what requestAnimationFrame actually does, why it beats setInterval, and how to use it properly.
- Lambda Expressions vs Anonymous Functions
When learning a functional programming style you will often come across the term Lambda Expressions or Lambda Functions. In simple terms they are just functions that can be used as data and therefore declared as a value. Let's explore a few examples.
- JavaScript typeof Number
Often you will need to check that you have a number before using it in your JavaScript, here's how.