@shade402/express
v0.0.1
Published
Express.js middleware and decorators for X402 payment protocol
Downloads
6
Maintainers
Readme
@shade402/express
Express.js middleware and utilities for implementing the X402 payment protocol in your web applications.
Overview
The Express package provides middleware and helper functions to easily add X402 payment requirements to your Express.js routes. It handles payment verification, 402 response generation, and integrates seamlessly with Solana blockchain payments.
Features
- Payment middleware for protecting routes with automatic verification
- Centralized error handling middleware for X402 payment errors
- Automatic payment verification using Solana blockchain
- Flexible configuration with global or per-route options
- 402 response builder for creating proper payment requests
- Full TypeScript support with Express request extensions
- Integration with Express error handling
Installation
npm install @shade402/express
# or
pnpm add @shade402/express
# or
yarn add @shade402/expressDependencies
@shade402/core: Core X402 protocol implementationexpress: Express.js web framework
Usage
Basic Setup with Global Configuration
import express from 'express';
import { initX402, paymentRequired } from '@shade402/express';
const app = express();
app.use(express.json());
// Initialize X402 with global configuration
initX402({
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
network: 'solana-devnet',
rpcUrl: process.env.SOLANA_RPC_URL,
autoVerify: true
});
// Protect a route with payment requirement
app.get('/premium-content',
paymentRequired({
amount: '1000000', // 1 USDC (6 decimals)
description: 'Access to premium content'
}),
(req, res) => {
// This code only runs after payment is verified
res.json({
content: 'Premium content here',
paidBy: req.payment?.publicKey
});
}
);
app.listen(3000, () => console.log('Server running on port 3000'));Per-Route Configuration
You can override global configuration for specific routes:
import { paymentRequired } from '@shade402/express';
// Override global config for specific routes
app.get('/api/data',
paymentRequired({
amount: '500000',
paymentAddress: 'SpecificWalletAddress',
tokenMint: 'So11111111111111111111111111111111111111112', // Native SOL
network: 'solana-mainnet',
description: 'API data access',
expiresIn: 300, // 5 minutes
autoVerify: true
}),
(req, res) => {
res.json({ data: 'Your data here' });
}
);Error Handling Middleware
Add the error middleware at the end of your middleware stack to handle X402 payment errors:
import express from 'express';
import { x402ErrorMiddleware } from '@shade402/express';
const app = express();
// ... your routes ...
// Add error middleware at the end (after all routes)
app.use(x402ErrorMiddleware({
logErrors: true,
includeStack: process.env.NODE_ENV === 'development'
}));The error middleware automatically handles:
PaymentRequiredError: Returns 402 status with payment requestPaymentExpiredError: Returns 410 Gone statusInsufficientFundsError: Returns 402 statusPaymentVerificationError: Returns 403 Forbidden statusTransactionBroadcastError: Returns 502 Bad Gateway statusInvalidPaymentRequestError: Returns 400 Bad Request status- Generic errors: Returns 500 Internal Server Error
Manual Payment Verification
For custom verification logic, disable automatic verification:
import { paymentRequired } from '@shade402/express';
import { X402Request } from '@shade402/express';
app.post('/custom-endpoint',
paymentRequired({
amount: '2000000',
autoVerify: false // Disable automatic verification
}),
(req: X402Request, res) => {
// Payment authorization is available but not verified
const payment = req.payment;
// Do custom verification logic
if (payment && customVerificationLogic(payment)) {
res.json({ success: true });
} else {
res.status(403).json({ error: 'Payment verification failed' });
}
}
);Building 402 Responses Manually
For custom payment flow control, build 402 responses manually:
import { build402Response } from '@shade402/express';
app.get('/custom-protected', (req, res) => {
// Check some condition
if (!userHasPaid) {
const response = build402Response({
amount: '1000000',
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
network: 'solana-devnet',
resource: req.path,
description: 'Custom payment required'
});
// Optional: Set X402 headers for better client compatibility
return res
.status(402)
.set({
'X-Payment-Required': 'true',
'X-Payment-Protocol': 'x402',
'X-Payment-Amount': '1000000',
'X-Payment-Asset': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
})
.json(response);
}
res.json({ content: 'Protected content' });
});Configuration Options
import { initX402, X402ConfigOptions } from '@shade402/express';
const config: X402ConfigOptions = {
paymentAddress: string; // Your Solana wallet address
tokenMint: string; // Token mint address (SPL token or SOL)
network: string; // 'solana-devnet' or 'solana-mainnet'
rpcUrl?: string; // Optional custom RPC endpoint
autoVerify?: boolean; // Auto-verify payments (default: true)
expiresIn?: number; // Payment expiration in seconds (default: 600)
};
initX402(config);Accessing Payment Information
Payment information is available on the request object after verification:
import { X402Request } from '@shade402/express';
app.get('/payment-info',
paymentRequired({ amount: '1000000' }),
(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
});
}
);Multiple Payment Tiers
You can implement different payment tiers for different routes:
// Basic tier
app.get('/basic',
paymentRequired({ amount: '100000', description: 'Basic access' }),
handler
);
// Premium tier
app.get('/premium',
paymentRequired({ amount: '1000000', description: 'Premium access' }),
handler
);
// Enterprise tier
app.get('/enterprise',
paymentRequired({ amount: '10000000', description: 'Enterprise access' }),
handler
);API Reference
Middleware
paymentRequired(options: PaymentRequiredOptions)
Express middleware that requires payment to access a route.
Options:
interface PaymentRequiredOptions {
amount: string; // Required payment amount in token base units
paymentAddress?: string; // Override global payment address
tokenMint?: string; // Override global token mint
network?: string; // Override global network
description?: string; // Payment description
expiresIn?: number; // Payment expiration in seconds
autoVerify?: boolean; // Enable automatic verification
}Returns: Express middleware function
Configuration
initX402(config: X402ConfigOptions)
Initialize global X402 configuration. Must be called before using payment middleware.
getConfig()
Get current global configuration.
Returns: X402ConfigOptions | undefined
isInitialized()
Check if X402 is initialized.
Returns: boolean
Error Middleware
x402ErrorMiddleware(options?: X402ErrorMiddlewareOptions)
Error handling middleware for X402 payment errors and general application errors.
Options:
interface X402ErrorMiddlewareOptions {
includeStack?: boolean; // Include stack traces (default: NODE_ENV === 'development')
logErrors?: boolean; // Log errors to console (default: true)
onError?: (error: Error, req: Request, res: Response) => void; // Custom error handler
}Note: This middleware should be added after all your routes and middleware.
Response Builders
build402Response(options: Build402ResponseOptions)
Build a 402 Payment Required response object.
Options:
interface Build402ResponseOptions {
amount: string;
paymentAddress: string;
tokenMint: string;
network: string;
resource: string;
description?: string;
expiresIn?: number;
}Returns: Payment request object that should be sent with status 402.
Type Definitions
X402Request
Extended Express Request interface with payment information:
interface X402Request extends Request {
payment?: PaymentAuthorization;
}TypeScript Support
The package includes full TypeScript definitions. Use the X402Request type for request objects in protected routes:
import { X402Request } from '@shade402/express';
app.get('/protected',
paymentRequired({ amount: '1000000' }),
(req: X402Request, res: Response) => {
// req.payment is available here
const payment = req.payment;
// ...
}
);Error Responses
The error middleware returns standardized error responses:
402 Payment Required
{
"error": "Payment required",
"code": "PAYMENT_REQUIRED",
"payment_request": { ... }
}403 Forbidden (Payment Verification Failed)
{
"error": "Payment verification failed",
"code": "PAYMENT_VERIFICATION_FAILED"
}410 Gone (Payment Expired)
{
"error": "Payment request expired",
"code": "PAYMENT_EXPIRED"
}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
Examples
Complete Express Server
import express from 'express';
import { initX402, paymentRequired, x402ErrorMiddleware } from '@shade402/express';
const app = express();
app.use(express.json());
// Initialize X402
initX402({
paymentAddress: process.env.PAYMENT_WALLET_ADDRESS!,
tokenMint: process.env.TOKEN_MINT!,
network: 'solana-devnet',
rpcUrl: process.env.SOLANA_RPC_URL,
autoVerify: true,
expiresIn: 600
});
// Public route
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Protected route
app.get('/api/data',
paymentRequired({
amount: '1000000',
description: 'API data access'
}),
(req, res) => {
res.json({ data: 'Protected data' });
}
);
// Error handling
app.use(x402ErrorMiddleware({
logErrors: true,
includeStack: process.env.NODE_ENV === 'development'
}));
app.listen(3000, () => {
console.log('Server running on port 3000');
});Custom Payment Verification
import { paymentRequired, X402Request } from '@shade402/express';
import { verifyPaymentInDatabase } from './database';
app.post('/api/process',
paymentRequired({
amount: '1000000',
autoVerify: false
}),
async (req: X402Request, res) => {
const payment = req.payment;
if (!payment) {
return res.status(402).json({ error: 'Payment required' });
}
// Custom verification against database
const isVerified = await verifyPaymentInDatabase(payment.transactionHash);
if (!isVerified) {
return res.status(403).json({ error: 'Payment not verified' });
}
// Process request
res.json({ success: true });
}
);Testing
pnpm testContributing
See CONTRIBUTING.md
License
MIT - See LICENSE
