A production-ready TypeScript library for integrating x402 payment protocol with support for Solana and EVM networks (Base, Polygon, Avalanche, etc.).
- ✅ Multi-chain Support: Solana, Base, Polygon, Avalanche, and more
- ✅ Solana Payments: Pre-signed transactions, no backend private key needed
- ✅ EVM Payments: EIP-3009 transferWithAuthorization support
- ✅ Type-safe: Full TypeScript support with type definitions
- ✅ Production Ready: Clean, minimal, and battle-tested code
# Install latest version
npm install git+https://github.com/0xstealthnode/x402-package.git
# Install specific version/tag
npm install git+https://github.com/0xstealthnode/x402-package.git#v1.0.0
# Or with SSH
npm install git+ssh://git@github.com:0xstealthnode/x402-package.gitIn your package.json:
{
"dependencies": {
"x402-payment-executor": "git+https://github.com/0xstealthnode/x402-package.git#v1.0.0"
}
}# Install from local tarball file
npm install ./x402-payment-executor-1.0.0.tgzThe package will automatically install:
ethers- For EVM network supportx402- Core x402 protocol@solana/web3.js- For Solana network support@solana/spl-token- For Solana token operations
cd your-project
# Install from GitHub
npm install git+https://github.com/0xstealthnode/x402-package.git
# Or from tarball
npm install ./x402-payment-executor-1.0.0.tgzCreate a .env file in your project root:
# Your merchant wallet address
PAY_TO_ADDRESS=6Cu2CyJpBXsKFpaBSXpD5wfypJn4pnzPn97oHanD6tTa
# Network (solana-devnet for testing, solana for production)
NETWORK=solana-devnet
# Price in USD
PRICE=0.10
# Private key (ONLY needed for EVM networks, leave empty for Solana)
PRIVATE_KEY=
# Server configuration
PORT=3000
FRONTEND_URL=http://localhost:5173import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { PaymentExecutor } from 'x402-payment-executor';
dotenv.config();
const app = express();
// Enable CORS for your frontend
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'x-402-payment', 'x402-payment'],
}));
app.use(express.json());
// Initialize payment executor
const executor = new PaymentExecutor({
payToAddress: process.env.PAY_TO_ADDRESS!,
network: process.env.NETWORK as any,
price: parseFloat(process.env.PRICE || '0.10'),
// privateKey only needed for EVM networks
privateKey: process.env.PRIVATE_KEY,
});
// Protected endpoint
app.post('/api/data', async (req, res) => {
const result = await executor.execute(
req,
res,
'/api/data',
'Get Protected Data',
'$0.10',
async () => {
// Your business logic here
return {
success: true,
data: {
message: 'This is protected data',
timestamp: new Date().toISOString(),
},
};
}
);
if (result) {
res.json(result);
}
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});npm run build # If using TypeScript
node dist/server.js
# Or with tsx
npx tsx server.tsconst executor = new PaymentExecutor({
payToAddress: '6Cu2CyJpBXsKFpaBSXpD5wfypJn4pnzPn97oHanD6tTa',
network: 'solana-devnet',
price: 0.10,
// No private key needed for Solana!
});const executor = new PaymentExecutor({
payToAddress: '0xYourWalletAddress',
network: 'base-sepolia',
price: 0.10,
privateKey: process.env.PRIVATE_KEY, // Required for EVM
rpcUrl: 'https://sepolia.base.org', // Optional, uses default if not set
});The execute() method handles the entire payment flow:
app.post('/api/your-endpoint', async (req, res) => {
const result = await executor.execute(
req, // Express request object
res, // Express response object
'/api/your-endpoint', // Resource path
'Service Description', // Human-readable description
'$0.10', // Price display string
async () => {
// Your protected business logic
const data = await fetchProtectedData();
return { success: true, data };
}
);
if (result) {
res.json(result);
}
});-
First Request (No Payment)
- User requests protected endpoint
- Backend returns 402 Payment Required with payment requirements
- Frontend receives payment requirements
-
Payment Creation
- Frontend prompts user's wallet (Phantom, MetaMask, etc.)
- User signs payment authorization
- Frontend receives signed payment
-
Second Request (With Payment)
- Frontend sends same request with
x-402-paymentheader - Backend verifies payment signature
- Backend settles payment on-chain
- Backend executes your business logic
- Backend returns protected data
- Frontend sends same request with
-
Transaction Confirmation
- Payment is confirmed on blockchain
- Transaction hash is logged
- User receives protected data
interface PaymentExecutorOptions {
payToAddress: string; // Your wallet address (required)
network: Network; // Blockchain network (required)
price: number; // Price in USD (required)
resourceUrl?: string; // Full URL of protected resource
description?: string; // Service description
mimeType?: string; // Response MIME type (default: 'application/json')
maxTimeoutSeconds?: number; // Payment timeout (default: 600)
privateKey?: string; // Private key for EVM settlement
rpcUrl?: string; // Custom RPC URL
assetAddress?: string; // Custom token address
assetName?: string; // Custom token name
chainId?: number; // Custom chain ID
}Executes a payment-protected handler.
Parameters:
req- HTTP request objectres- HTTP response objectresourcePath- Path of the protected resourcedescription- Human-readable descriptionpriceDisplay- Display price (e.g., "$0.10")handler- Async function to execute after payment verification
Returns: Result from handler or null if payment required/failed
Verifies a payment payload.
const result = await executor.verifyPayment(paymentPayload);
if (result.isValid) {
console.log('Payment verified from:', result.payer);
}Settles a verified payment on-chain.
const result = await executor.settlePayment(paymentPayload);
if (result.success) {
console.log('Transaction:', result.transaction);
}Returns the payment requirements for this executor.
const requirements = executor.getPaymentRequirements();
console.log('Network:', requirements.network);
console.log('Amount:', requirements.maxAmountRequired);| Network | Value | Description |
|---|---|---|
| Solana Devnet | solana-devnet |
Testing network |
| Solana Mainnet | solana |
Production network |
USDC Addresses:
- Devnet:
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU - Mainnet:
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
| Network | Value | Chain ID | Description |
|---|---|---|---|
| Base Mainnet | base |
8453 | Production |
| Base Sepolia | base-sepolia |
84532 | Testing |
| Polygon Mainnet | polygon |
137 | Production |
| Polygon Amoy | polygon-amoy |
80002 | Testing |
| Avalanche C-Chain | avalanche |
43114 | Production |
| Avalanche Fuji | avalanche-fuji |
43113 | Testing |
| IoTeX Mainnet | iotex |
4689 | Production |
| Sei Mainnet | sei |
1329 | Production |
| Sei Testnet | sei-testnet |
1328 | Testing |
| Peaq Network | peaq |
3338 | Production |
npm install x402 ethers@^6.15.0import { useState } from 'react';
import { createPayment } from 'x402';
import { ethers } from 'ethers';
export function useX402Payment() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchWithPayment = async (url: string, options: RequestInit = {}) => {
setLoading(true);
setError(null);
try {
// 1. Try request without payment
const response = await fetch(url, {
...options,
method: 'POST',
headers: { 'Content-Type': 'application/json', ...options.headers },
});
// If not 402, return response
if (response.status !== 402) {
return await response.json();
}
// 2. Get payment requirements
const { x402 } = await response.json();
const requirements = x402.accepts[0];
// 3. Connect wallet and sign payment
const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = await provider.getSigner();
const payment = await createPayment(signer, requirements);
// 4. Retry with payment
const paidResponse = await fetch(url, {
...options,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-402-payment': JSON.stringify(payment),
...options.headers,
},
});
if (!paidResponse.ok) {
const error = await paidResponse.json();
throw new Error(error.reason || 'Payment failed');
}
return await paidResponse.json();
} catch (err) {
const msg = err instanceof Error ? err.message : 'Unknown error';
setError(msg);
throw err;
} finally {
setLoading(false);
}
};
return { fetchWithPayment, loading, error };
}import { useX402Payment } from './hooks/useX402Payment';
function MyComponent() {
const { fetchWithPayment, loading, error } = useX402Payment();
const [data, setData] = useState(null);
const handleFetch = async () => {
try {
const result = await fetchWithPayment('http://localhost:3000/api/data');
setData(result);
} catch (err) {
console.error('Payment failed:', err);
}
};
return (
<div>
<button onClick={handleFetch} disabled={loading}>
{loading ? 'Processing Payment...' : 'Fetch Data ($0.10)'}
</button>
{error && <div className="error">{error}</div>}
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}curl -X POST http://localhost:3000/api/data \
-H "Content-Type: application/json" \
-d '{}'Expected Response:
{
"success": false,
"error": "Payment Required",
"x402": {
"x402Version": 1,
"accepts": [
{
"scheme": "exact",
"network": "solana-devnet",
"asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
"payTo": "6Cu2CyJpBXsKFpaBSXpD5wfypJn4pnzPn97oHanD6tTa",
"maxAmountRequired": "100000",
"resource": "/api/data",
"description": "Get Protected Data"
}
]
}
}- Start your backend server
- Open your frontend application
- Click the payment button
- Approve transaction in wallet (Phantom, MetaMask, etc.)
- Verify transaction on blockchain explorer
- Receive protected data
Solana Devnet:
https://explorer.solana.com/tx/[TRANSACTION_HASH]?cluster=devnet
Base Sepolia:
https://sepolia.basescan.org/tx/[TRANSACTION_HASH]
const executor = new PaymentExecutor({
payToAddress: '0xYourAddress',
network: 'custom-network' as any,
price: 0.10,
assetAddress: '0xCustomTokenAddress',
assetName: 'Custom Token',
chainId: 12345,
rpcUrl: 'https://custom-rpc.example.com',
});// Different prices for different endpoints
const cheapExecutor = new PaymentExecutor({
payToAddress: process.env.PAY_TO_ADDRESS!,
network: 'solana-devnet',
price: 0.01,
});
const expensiveExecutor = new PaymentExecutor({
payToAddress: process.env.PAY_TO_ADDRESS!,
network: 'solana-devnet',
price: 1.00,
});
app.post('/api/cheap', async (req, res) => {
const result = await cheapExecutor.execute(
req, res, '/api/cheap', 'Cheap Service', '$0.01',
async () => ({ data: 'cheap data' })
);
if (result) res.json(result);
});
app.post('/api/expensive', async (req, res) => {
const result = await expensiveExecutor.execute(
req, res, '/api/expensive', 'Premium Service', '$1.00',
async () => ({ data: 'premium data' })
);
if (result) res.json(result);
});Error: Access to fetch blocked by CORS policy
Solution: Update CORS configuration:
app.use(cors({
origin: 'http://localhost:5173', // Your frontend URL
credentials: true,
allowedHeaders: ['Content-Type', 'x-402-payment', 'x402-payment'],
}));Error: Payment verification failed
Possible causes:
- Network mismatch (frontend and backend using different networks)
- Insufficient balance in user's wallet
- Invalid signature
- Expired authorization
Solution: Check backend logs for specific error message.
Error: Payment settlement failed
Possible causes:
- Missing
PRIVATE_KEYin environment - Insufficient gas (ETH) in merchant wallet
- RPC endpoint issues
Solution:
- Ensure
PRIVATE_KEYis set in.env - Check merchant wallet has ETH for gas
- Verify RPC URL is correct
For Solana:
- Check transaction on explorer:
https://explorer.solana.com/tx/[HASH]?cluster=devnet - Wait 1-2 seconds for confirmation
- Verify network (devnet vs mainnet)
For EVM:
- Check transaction on explorer:
https://sepolia.basescan.org/tx/[HASH] - Wait for block confirmation (5-15 seconds)
- Check gas price wasn't too low
✅ DO:
- Store private keys in
.envfiles - Add
.envto.gitignore - Use different keys for dev/staging/prod
- Rotate keys periodically
❌ DON'T:
- Commit private keys to git
- Share private keys in chat/email
- Use production keys in development
- Hard-code private keys in source code
For Development:
PRIVATE_KEY=your_test_key_hereFor Production:
- Use secret management services (AWS Secrets Manager, HashiCorp Vault)
- Use hardware wallets for key storage
- Set up monitoring and alerts
- Keep backup keys in secure offline storage
Monitor these metrics:
- Payment success rate
- Failed payment attempts
- Transaction gas costs
- Wallet balance
- Unusual activity patterns
// Main class
import { PaymentExecutor } from 'x402-payment-executor';
// Types
import type {
PaymentExecutorOptions,
VerifyResult,
SettlementResult,
Network,
PaymentPayload,
PaymentRequirements,
SettlementMode,
} from 'x402-payment-executor';
// Network utilities
import {
BUILT_IN_NETWORKS,
getDefaultRpcUrl,
isSolanaNetwork,
isSupportedEvmNetwork,
} from 'x402-payment-executor';type Network =
| 'solana' | 'solana-devnet'
| 'base' | 'base-sepolia'
| 'polygon' | 'polygon-amoy'
| 'avalanche' | 'avalanche-fuji'
| 'iotex' | 'sei' | 'sei-testnet' | 'peaq';
type SettlementMode = 'facilitator' | 'direct';
interface VerifyResult {
isValid: boolean;
payer?: string;
invalidReason?: string;
}
interface SettlementResult {
success: boolean;
transaction?: string;
network: string;
payer?: string;
errorReason?: string;
}A: No! Solana transactions are pre-signed by users. Private keys are only needed for EVM networks.
A:
- Solana: User pays transaction fee (~$0.00001)
- EVM: Merchant pays gas fee (~$0.01-0.05 on Base)
A: Yes! The package is production-ready. Just switch from testnet to mainnet networks.
A:
- Solana Devnet: Use Solana faucet or testnet DEX
- Base Sepolia: Use Coinbase faucet, then swap for USDC
A: The backend returns an error response, no data is provided, and no transaction is recorded.
A: Yes! Create multiple PaymentExecutor instances with different prices, or pass dynamic prices to the constructor.
Package Version: 1.0.0
Repository: https://github.com/0xstealthnode/x402-package
License: MIT
For issues or questions:
- Open an issue on GitHub
- Contact your package maintainer
- Multi-chain support (Solana + 10 EVM networks)
- Direct settlement mode
- Payment verification and settlement
- Full TypeScript support
- Production-ready code