Express Integration
The @shade402/express package provides middleware and utilities for implementing X402 payment-protected endpoints in Express.js applications.
Installation
pnpm add @shade402/express @shade402/coreBasic Setup
Initialize Configuration
Initialize X402 with your payment configuration:
import { initX402 } from '@shade402/express';
initX402({
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
network: 'solana-devnet',
rpcUrl: process.env.SOLANA_RPC_URL,
defaultAmount: '0.01',
paymentTimeout: 300, // 5 minutes
autoVerify: true,
});Protect Routes
Use the paymentRequired middleware to protect routes:
import express from 'express';
import { paymentRequired } from '@shade402/express';
const app = express();
app.get('/api/premium-data',
paymentRequired({
amount: '0.01',
description: 'Premium data access',
}),
(req, res) => {
res.json({ data: 'Premium content' });
}
);Configuration Options
Global Configuration
interface X402ConfigOptions {
paymentAddress: string; // Your Solana wallet address
tokenMint: string; // Token mint address
network: string; // 'solana-devnet' or 'solana-mainnet'
rpcUrl?: string; // Optional custom RPC endpoint
autoVerify?: boolean; // Auto-verify payments (default: true)
defaultAmount?: string; // Default payment amount
paymentTimeout?: number; // Payment expiration in seconds (default: 600)
encryptionPublicKey?: string; // RSA public key for resource encryption
encryptionPrivateKey?: string; // RSA private key for resource decryption
}Per-Route Configuration
You can override global configuration for specific routes:
app.get('/api/data',
paymentRequired({
amount: '0.05',
paymentAddress: 'DifferentWalletAddress',
tokenMint: 'DifferentTokenMint',
network: 'solana-mainnet',
description: 'Custom pricing',
expiresIn: 600,
autoVerify: true,
}),
handler
);Accessing Payment Information
Payment information is available on the request object after verification:
import { X402Request } from '@shade402/express';
app.get('/api/payment-info',
paymentRequired({ amount: '0.01' }),
(req: X402Request, res) => {
const payment = req.payment;
if (!payment) {
return res.status(402).json({ error: 'Payment required' });
}
res.json({
paymentId: payment.paymentId,
amount: payment.actualAmount,
payer: payment.publicKey,
transactionHash: payment.transactionHash,
timestamp: payment.timestamp,
});
}
);Error Handling
Add the error middleware at the end of your middleware stack:
import { x402ErrorMiddleware } from '@shade402/express';
// ... your routes ...
app.use(x402ErrorMiddleware({
logErrors: true,
includeStack: process.env.NODE_ENV === 'development',
}));The error middleware handles:
PaymentRequiredError: Returns 402 with payment requestPaymentExpiredError: Returns 410 GoneInsufficientFundsError: Returns 402PaymentVerificationError: Returns 403 ForbiddenTransactionBroadcastError: Returns 502 Bad GatewayInvalidPaymentRequestError: Returns 400 Bad Request
Resource Encryption
Enable resource encryption for enhanced privacy:
import { generateKeyPair } from '@shade402/core';
// Generate key pair once
const { publicKey, privateKey } = generateKeyPair();
// Store keys securely (environment variables, key management service)
initX402({
// ... other config ...
encryptionPublicKey: publicKey,
encryptionPrivateKey: privateKey,
});With encryption enabled, the server will:
Include public key in 402 response headers
Automatically decrypt encrypted resource from client requests
Make decrypted resource available via
req.decryptedResource
Multiple Payment Tiers
Implement different payment tiers:
// Basic tier
app.get('/api/basic',
paymentRequired({ amount: '0.001', description: 'Basic access' }),
handler
);
// Premium tier
app.get('/api/premium',
paymentRequired({ amount: '0.01', description: 'Premium access' }),
handler
);
// Enterprise tier
app.get('/api/enterprise',
paymentRequired({ amount: '0.1', description: 'Enterprise access' }),
handler
);Manual Payment Verification
For custom verification logic, disable automatic verification:
import { paymentRequired, X402Request } from '@shade402/express';
app.post('/api/custom',
paymentRequired({
amount: '0.01',
autoVerify: false, // Disable automatic verification
}),
async (req: X402Request, res) => {
const payment = req.payment;
if (!payment) {
return res.status(402).json({ error: 'Payment required' });
}
// Custom verification logic
const isVerified = await verifyPaymentInDatabase(payment.transactionHash);
if (!isVerified) {
return res.status(403).json({ error: 'Payment not verified' });
}
res.json({ success: true });
}
);Building 402 Responses Manually
For custom payment flow control:
import { build402Response } from '@shade402/express';
app.get('/api/custom-protected', (req, res) => {
if (!userHasPaid) {
const response = build402Response({
amount: '0.01',
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
network: 'solana-devnet',
resource: req.path,
description: 'Custom payment required',
});
return res.status(402).json(response);
}
res.json({ content: 'Protected content' });
});Complete Example
import express from 'express';
import { initX402, paymentRequired, x402ErrorMiddleware } from '@shade402/express';
import { generateKeyPair } from '@shade402/core';
const app = express();
app.use(express.json());
// Generate encryption keys
const { publicKey, privateKey } = generateKeyPair();
// Initialize X402
initX402({
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: process.env.TOKEN_MINT!,
network: process.env.SOLANA_NETWORK || 'solana-devnet',
rpcUrl: process.env.SOLANA_RPC_URL,
autoVerify: true,
defaultAmount: '0.01',
paymentTimeout: 300,
encryptionPublicKey: publicKey,
encryptionPrivateKey: privateKey,
});
// Public route
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Protected route
app.get('/api/data',
paymentRequired({
amount: '0.01',
description: 'API data access',
}),
(req, res) => {
res.json({
data: 'Protected data',
paymentId: req.payment?.paymentId,
});
}
);
// Error handling
app.use(x402ErrorMiddleware({
logErrors: true,
includeStack: process.env.NODE_ENV === 'development',
}));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});Best Practices
Initialize X402 configuration once at application startup
Store sensitive configuration in environment variables
Use appropriate payment amounts for different tiers
Set reasonable expiration times for payment requests
Enable automatic verification in production
Add error middleware after all routes
Use TypeScript types for better type safety
Monitor payment verification failures
Consider rate limiting for payment-protected endpoints
Log payment transactions for audit purposes
Next Steps
Learn about Client Usage for making requests
Check out Security best practices
See Examples for more use cases
Last updated
