Best Practices
Guidelines and recommendations for using Shade402 effectively and securely.
Configuration
Environment Variables
Store all sensitive configuration in environment variables:
// .env
PAYMENT_WALLET_ADDRESS=YourWalletAddress
TOKEN_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
SOLANA_NETWORK=solana-mainnet
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
WALLET_SECRET_KEY=YourBase64EncodedSecretKeyInitialize Once
Initialize X402 configuration once at application startup:
// config/x402.ts
import { initX402 } from '@shade402/express';
let initialized = false;
export function initializeX402() {
if (initialized) return;
initX402({
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: process.env.TOKEN_MINT!,
network: process.env.SOLANA_NETWORK!,
rpcUrl: process.env.SOLANA_RPC_URL,
autoVerify: true,
});
initialized = true;
}Default Values
Set reasonable defaults but allow overrides:
initX402({
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: process.env.TOKEN_MINT!,
network: process.env.SOLANA_NETWORK || 'solana-devnet',
defaultAmount: '0.01',
paymentTimeout: 300, // 5 minutes
autoVerify: process.env.NODE_ENV === 'production',
});Payment Amounts
Set Appropriate Amounts
Choose payment amounts based on:
Resource value
Cost of providing the resource
Market rates
User experience
// Low-value resource
paymentRequired({ amount: '0.001' });
// Medium-value resource
paymentRequired({ amount: '0.01' });
// High-value resource
paymentRequired({ amount: '0.1' });Maximum Payment Limits
Always set maximum payment limits for clients:
const client = new X402AutoClient(wallet, rpcUrl, {
maxPaymentAmount: '1.0', // Safety limit
});Payment Tiers
Implement different payment tiers:
// Basic tier
app.get('/api/basic', paymentRequired({ amount: '0.001' }), handler);
// Premium tier
app.get('/api/premium', paymentRequired({ amount: '0.01' }), handler);
// Enterprise tier
app.get('/api/enterprise', paymentRequired({ amount: '0.1' }), handler);Error Handling
Comprehensive Error Handling
Handle all error types appropriately:
import {
PaymentRequiredError,
PaymentExpiredError,
InsufficientFundsError,
} from '@shade402/core';
try {
const response = await client.get(url);
} catch (error) {
if (error instanceof PaymentRequiredError) {
// Handle payment required
} else if (error instanceof PaymentExpiredError) {
// Handle expired payment
} else if (error instanceof InsufficientFundsError) {
// Handle insufficient funds
} else {
// Handle other errors
}
}Server-side Error Middleware
Always add error middleware:
import { x402ErrorMiddleware } from '@shade402/express';
// Add after all routes
app.use(x402ErrorMiddleware({
logErrors: true,
includeStack: process.env.NODE_ENV === 'development',
}));Client Management
Always Close Clients
Always close clients to cleanup connections:
const client = new X402AutoClient(wallet);
try {
// Use client
} finally {
await client.close(); // Always cleanup
}Reuse Clients When Possible
For multiple requests, reuse the same client:
const client = new X402AutoClient(wallet);
try {
const response1 = await client.get(url1);
const response2 = await client.get(url2);
const response3 = await client.get(url3);
} finally {
await client.close();
}Wallet Management
Load wallets securely:
// Load from environment variable
const secretKey = Buffer.from(process.env.WALLET_SECRET_KEY!, 'base64');
const wallet = Keypair.fromSecretKey(secretKey);
// Or use key management service
const wallet = await loadWalletFromKMS();Logging and Monitoring
Log Payment Transactions
Log all payment transactions for audit:
app.get('/api/data',
paymentRequired({ amount: '0.01' }),
(req, res) => {
const payment = req.payment;
// Log payment
logger.info('Payment received', {
paymentId: payment?.paymentId,
amount: payment?.actualAmount,
payer: payment?.publicKey,
transactionHash: payment?.transactionHash,
timestamp: new Date(),
});
res.json({ data: 'Protected data' });
}
);Monitor Payment Metrics
Track payment metrics:
Total payments received
Average payment amount
Payment success rate
Failed payment attempts
Payment expiration rate
Error Monitoring
Monitor errors and set up alerts:
app.use(x402ErrorMiddleware({
logErrors: true,
onError: (error, req, res) => {
// Send to monitoring service
monitoringService.captureException(error, {
tags: { type: 'x402_error' },
extra: { path: req.path },
});
},
}));Performance
Connection Pooling
Reuse Solana connections:
// Create processor once
const processor = new SolanaPaymentProcessor(rpcUrl, wallet);
// Reuse for multiple payments
for (const request of requests) {
const tx = await processor.createPaymentTransaction(request, amount, wallet);
// ...
}
await processor.close();Async Operations
Use async/await properly:
// GOOD: Proper async handling
async function processPayments(requests: PaymentRequest[]) {
const results = await Promise.all(
requests.map(request => client.createPayment(request))
);
return results;
}
// BAD: Sequential processing when parallel is possible
async function processPayments(requests: PaymentRequest[]) {
const results = [];
for (const request of requests) {
results.push(await client.createPayment(request));
}
return results;
}Testing
Test with Devnet
Always test with devnet before production:
// Test configuration
const testConfig = {
network: 'solana-devnet',
rpcUrl: 'https://api.devnet.solana.com',
tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // Devnet USDC
};Test Payment Flows
Test all payment scenarios:
Successful payment
Payment expired
Insufficient funds
Invalid payment
Network errors
Transaction failures
Integration Tests
Write integration tests for payment flows:
describe('Payment Flow', () => {
it('should handle payment required', async () => {
const response = await client.get(url);
expect(client.paymentRequired(response)).toBe(true);
});
it('should create and verify payment', async () => {
const paymentRequest = client.parsePaymentRequest(response);
const authorization = await client.createPayment(paymentRequest);
const retryResponse = await client.get(url, { payment: authorization });
expect(retryResponse.status).toBe(200);
});
});Documentation
Code Documentation
Document payment-protected endpoints:
/**
* Premium data endpoint
*
* Requires payment: 0.01 USDC
* Payment expiration: 5 minutes
*
* @route GET /api/premium-data
* @requires X-Payment-Authorization header
* @returns {Object} Premium data
*/
app.get('/api/premium-data',
paymentRequired({ amount: '0.01', expiresIn: 300 }),
handler
);API Documentation
Document payment requirements in API docs:
Payment amounts
Payment expiration
Supported tokens
Network requirements
Error responses
Code Organization
Separate Concerns
Separate payment logic from business logic:
// payment-service.ts
export class PaymentService {
async verifyPayment(authorization: PaymentAuthorization): Promise<boolean> {
// Payment verification logic
}
}
// business-logic.ts
export class DataService {
constructor(private paymentService: PaymentService) {}
async getPremiumData(req: Request): Promise<Data> {
// Business logic
}
}Use Middleware
Use middleware for payment verification:
// Don't mix payment and business logic
app.get('/api/data', paymentRequired({ amount: '0.01' }), handler);
// Not:
app.get('/api/data', async (req, res) => {
// Payment logic mixed with business logic
});Summary
Key best practices:
Store sensitive config in environment variables
Initialize X402 once at startup
Set appropriate payment amounts
Always set maximum payment limits
Handle all error types
Always close clients
Log payment transactions
Monitor payment metrics
Test with devnet first
Document payment requirements
Separate payment and business logic
Use middleware for payment verification
Next Steps
Review Security practices
Check out Troubleshooting guide
See Examples for reference implementations
Last updated
