Next.js Integration

The @shade402/nextjs package provides middleware and utilities for implementing X402 payment-protected routes in Next.js applications.

Installation

pnpm add @shade402/nextjs @shade402/core

Setup

Initialize Configuration

Initialize X402 in your Next.js application. Create a configuration file:

// lib/x402-config.ts
import { initX402 } from '@shade402/nextjs';

export function configureX402() {
  if (typeof window === 'undefined') {
    // Server-side only
    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,
    });
  }
}

Call this in your API routes or middleware:

// middleware.ts or api route
import { configureX402 } from '@/lib/x402-config';

configureX402();

API Routes

Protect API routes using the withPayment wrapper:

// app/api/premium-data/route.ts (App Router)
import { NextRequest, NextResponse } from 'next/server';
import { withPayment, X402HandlerContext } from '@shade402/nextjs';

export const GET = withPayment(
  {
    amount: '0.01',
    description: 'Premium data access',
  },
  async (req: NextRequest, context: X402HandlerContext) => {
    const payment = context.payment;
    
    return NextResponse.json({
      data: 'Premium content',
      paymentId: payment?.paymentId,
    });
  }
);

Pages Router

For Pages Router (app/pages directory):

// pages/api/premium-data.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { withPayment, X402HandlerContext } from '@shade402/nextjs';

export default withPayment(
  {
    amount: '0.01',
    description: 'Premium data access',
  },
  async (
    req: NextApiRequest,
    res: NextApiResponse,
    context: X402HandlerContext
  ) => {
    const payment = context.payment;
    
    res.json({
      data: 'Premium content',
      paymentId: payment?.paymentId,
    });
  }
);

Configuration Options

interface PaymentRequiredOptions {
  amount: string;                    // Required payment amount
  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
}

Accessing Payment Information

Payment information is available in the handler context:

import { withPayment, X402HandlerContext } from '@shade402/nextjs';

export const GET = withPayment(
  { amount: '0.01' },
  async (req: NextRequest, context: X402HandlerContext) => {
    const payment = context.payment;
    
    if (!payment) {
      return NextResponse.json(
        { error: 'Payment required' },
        { status: 402 }
      );
    }

    return NextResponse.json({
      paymentId: payment.paymentId,
      amount: payment.actualAmount,
      payer: payment.publicKey,
      transactionHash: payment.transactionHash,
    });
  }
);

Error Handling

The withPayment wrapper automatically handles errors and returns appropriate HTTP status codes:

  • 402: Payment required

  • 403: Payment verification failed

  • 410: Payment expired

  • 500: Internal server error

For custom error handling, you can catch errors in your handler:

export const GET = withPayment(
  { amount: '0.01' },
  async (req: NextRequest, context: X402HandlerContext) => {
    try {
      // Your logic here
      return NextResponse.json({ success: true });
    } catch (error) {
      return NextResponse.json(
        { error: 'Internal error' },
        { status: 500 }
      );
    }
  }
);

Multiple Payment Tiers

Implement different payment tiers:

// app/api/basic/route.ts
export const GET = withPayment(
  { amount: '0.001', description: 'Basic access' },
  handler
);

// app/api/premium/route.ts
export const GET = withPayment(
  { amount: '0.01', description: 'Premium access' },
  handler
);

Environment Variables

Set up environment variables in .env.local:

PAYMENT_WALLET_ADDRESS=YourSolanaWalletAddress
TOKEN_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
SOLANA_NETWORK=solana-devnet
SOLANA_RPC_URL=https://api.devnet.solana.com

Complete Example

// app/api/premium-data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withPayment, X402HandlerContext } from '@shade402/nextjs';
import { configureX402 } from '@/lib/x402-config';

// Configure X402
configureX402();

export const GET = withPayment(
  {
    amount: '0.01',
    description: 'Premium data access',
  },
  async (req: NextRequest, context: X402HandlerContext) => {
    const payment = context.payment;
    
    // Payment has been verified at this point
    return NextResponse.json({
      data: {
        message: 'Premium data',
        timestamp: new Date().toISOString(),
        paymentId: payment?.paymentId,
      },
    });
  }
);

export const POST = withPayment(
  {
    amount: '0.05',
    description: 'Premium data write',
  },
  async (req: NextRequest, context: X402HandlerContext) => {
    const body = await req.json();
    const payment = context.payment;
    
    // Process the request
    return NextResponse.json({
      success: true,
      paymentId: payment?.paymentId,
      processed: body,
    });
  }
);

Client-side Integration

For client-side requests, use the @shade402/client package:

// app/components/PremiumData.tsx
'use client';

import { useEffect, useState } from 'react';
import { X402AutoClient } from '@shade402/client';
import { Keypair } from '@solana/web3.js';

export function PremiumData() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      const wallet = Keypair.generate(); // In production, load from secure storage
      const client = new X402AutoClient(wallet);

      try {
        const response = await client.get('/api/premium-data');
        setData(response.data);
      } catch (error) {
        console.error('Error:', error);
      } finally {
        await client.close();
        setLoading(false);
      }
    }

    fetchData();
  }, []);

  if (loading) return <div>Loading...</div>;
  return <div>{JSON.stringify(data)}</div>;
}

Best Practices

  1. Initialize X402 configuration in a shared utility file

  2. Store sensitive configuration in environment variables

  3. Use appropriate payment amounts for different routes

  4. Enable automatic verification in production

  5. Use TypeScript types for better type safety

  6. Handle errors gracefully in your handlers

  7. Consider rate limiting for payment-protected routes

  8. Log payment transactions for audit purposes

Next Steps

Last updated