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/core

Basic 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 request

  • PaymentExpiredError: Returns 410 Gone

  • InsufficientFundsError: Returns 402

  • PaymentVerificationError: Returns 403 Forbidden

  • TransactionBroadcastError: Returns 502 Bad Gateway

  • InvalidPaymentRequestError: 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

  1. Initialize X402 configuration once at application startup

  2. Store sensitive configuration in environment variables

  3. Use appropriate payment amounts for different tiers

  4. Set reasonable expiration times for payment requests

  5. Enable automatic verification in production

  6. Add error middleware after all routes

  7. Use TypeScript types for better type safety

  8. Monitor payment verification failures

  9. Consider rate limiting for payment-protected endpoints

  10. Log payment transactions for audit purposes

Next Steps

Last updated