Skip to content

A payment execution library for x402 protocol supporting Base and Solana networks

License

Notifications You must be signed in to change notification settings

0xstealthnode/x402-package

Repository files navigation

x402 Payment Executor

A production-ready TypeScript library for integrating x402 payment protocol with support for Solana and EVM networks (Base, Polygon, Avalanche, etc.).

Features

  • Multi-chain Support: Solana, Base, Polygon, Avalanche, and more
  • Solana Payments: Pre-signed transactions, no backend private key needed
  • EVM Payments: EIP-3009 transferWithAuthorization support
  • Type-safe: Full TypeScript support with type definitions
  • Production Ready: Clean, minimal, and battle-tested code

Installation

Method 1: Install from GitHub (Recommended)

# Install latest version
npm install git+https://github.com/0xstealthnode/x402-package.git

# Install specific version/tag
npm install git+https://github.com/0xstealthnode/x402-package.git#v1.0.0

# Or with SSH
npm install git+ssh://git@github.com:0xstealthnode/x402-package.git

In your package.json:

{
  "dependencies": {
    "x402-payment-executor": "git+https://github.com/0xstealthnode/x402-package.git#v1.0.0"
  }
}

Method 2: Install from Tarball

# Install from local tarball file
npm install ./x402-payment-executor-1.0.0.tgz

Dependencies

The package will automatically install:

  • ethers - For EVM network support
  • x402 - Core x402 protocol
  • @solana/web3.js - For Solana network support
  • @solana/spl-token - For Solana token operations

Quick Start

Step 1: Install the Package

cd your-project

# Install from GitHub
npm install git+https://github.com/0xstealthnode/x402-package.git

# Or from tarball
npm install ./x402-payment-executor-1.0.0.tgz

Step 2: Set Up Environment Variables

Create a .env file in your project root:

# Your merchant wallet address
PAY_TO_ADDRESS=6Cu2CyJpBXsKFpaBSXpD5wfypJn4pnzPn97oHanD6tTa

# Network (solana-devnet for testing, solana for production)
NETWORK=solana-devnet

# Price in USD
PRICE=0.10

# Private key (ONLY needed for EVM networks, leave empty for Solana)
PRIVATE_KEY=

# Server configuration
PORT=3000
FRONTEND_URL=http://localhost:5173

Step 3: Create Your Server

import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { PaymentExecutor } from 'x402-payment-executor';

dotenv.config();

const app = express();

// Enable CORS for your frontend
app.use(cors({
  origin: process.env.FRONTEND_URL || 'http://localhost:5173',
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'x-402-payment', 'x402-payment'],
}));

app.use(express.json());

// Initialize payment executor
const executor = new PaymentExecutor({
  payToAddress: process.env.PAY_TO_ADDRESS!,
  network: process.env.NETWORK as any,
  price: parseFloat(process.env.PRICE || '0.10'),
  // privateKey only needed for EVM networks
  privateKey: process.env.PRIVATE_KEY,
});

// Protected endpoint
app.post('/api/data', async (req, res) => {
  const result = await executor.execute(
    req,
    res,
    '/api/data',
    'Get Protected Data',
    '$0.10',
    async () => {
      // Your business logic here
      return {
        success: true,
        data: {
          message: 'This is protected data',
          timestamp: new Date().toISOString(),
        },
      };
    }
  );

  if (result) {
    res.json(result);
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Step 4: Run Your Server

npm run build  # If using TypeScript
node dist/server.js

# Or with tsx
npx tsx server.ts

Complete Usage Guide

Basic Configuration

Solana Network (Recommended for Testing)

const executor = new PaymentExecutor({
  payToAddress: '6Cu2CyJpBXsKFpaBSXpD5wfypJn4pnzPn97oHanD6tTa',
  network: 'solana-devnet',
  price: 0.10,
  // No private key needed for Solana!
});

EVM Network (Base, Polygon, etc.)

const executor = new PaymentExecutor({
  payToAddress: '0xYourWalletAddress',
  network: 'base-sepolia',
  price: 0.10,
  privateKey: process.env.PRIVATE_KEY, // Required for EVM
  rpcUrl: 'https://sepolia.base.org', // Optional, uses default if not set
});

Protecting API Endpoints

The execute() method handles the entire payment flow:

app.post('/api/your-endpoint', async (req, res) => {
  const result = await executor.execute(
    req,                          // Express request object
    res,                          // Express response object
    '/api/your-endpoint',         // Resource path
    'Service Description',        // Human-readable description
    '$0.10',                      // Price display string
    async () => {
      // Your protected business logic
      const data = await fetchProtectedData();
      return { success: true, data };
    }
  );

  if (result) {
    res.json(result);
  }
});

Payment Flow Explained

  1. First Request (No Payment)

    • User requests protected endpoint
    • Backend returns 402 Payment Required with payment requirements
    • Frontend receives payment requirements
  2. Payment Creation

    • Frontend prompts user's wallet (Phantom, MetaMask, etc.)
    • User signs payment authorization
    • Frontend receives signed payment
  3. Second Request (With Payment)

    • Frontend sends same request with x-402-payment header
    • Backend verifies payment signature
    • Backend settles payment on-chain
    • Backend executes your business logic
    • Backend returns protected data
  4. Transaction Confirmation

    • Payment is confirmed on blockchain
    • Transaction hash is logged
    • User receives protected data

API Reference

PaymentExecutor Class

Constructor Options

interface PaymentExecutorOptions {
  payToAddress: string;           // Your wallet address (required)
  network: Network;                // Blockchain network (required)
  price: number;                   // Price in USD (required)
  resourceUrl?: string;            // Full URL of protected resource
  description?: string;            // Service description
  mimeType?: string;               // Response MIME type (default: 'application/json')
  maxTimeoutSeconds?: number;      // Payment timeout (default: 600)
  privateKey?: string;             // Private key for EVM settlement
  rpcUrl?: string;                 // Custom RPC URL
  assetAddress?: string;           // Custom token address
  assetName?: string;              // Custom token name
  chainId?: number;                // Custom chain ID
}

Methods

execute(req, res, resourcePath, description, priceDisplay, handler)

Executes a payment-protected handler.

Parameters:

  • req - HTTP request object
  • res - HTTP response object
  • resourcePath - Path of the protected resource
  • description - Human-readable description
  • priceDisplay - Display price (e.g., "$0.10")
  • handler - Async function to execute after payment verification

Returns: Result from handler or null if payment required/failed

verifyPayment(payload)

Verifies a payment payload.

const result = await executor.verifyPayment(paymentPayload);
if (result.isValid) {
  console.log('Payment verified from:', result.payer);
}
settlePayment(payload)

Settles a verified payment on-chain.

const result = await executor.settlePayment(paymentPayload);
if (result.success) {
  console.log('Transaction:', result.transaction);
}
getPaymentRequirements()

Returns the payment requirements for this executor.

const requirements = executor.getPaymentRequirements();
console.log('Network:', requirements.network);
console.log('Amount:', requirements.maxAmountRequired);

Supported Networks

Solana Networks

Network Value Description
Solana Devnet solana-devnet Testing network
Solana Mainnet solana Production network

USDC Addresses:

  • Devnet: 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
  • Mainnet: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v

EVM Networks

Network Value Chain ID Description
Base Mainnet base 8453 Production
Base Sepolia base-sepolia 84532 Testing
Polygon Mainnet polygon 137 Production
Polygon Amoy polygon-amoy 80002 Testing
Avalanche C-Chain avalanche 43114 Production
Avalanche Fuji avalanche-fuji 43113 Testing
IoTeX Mainnet iotex 4689 Production
Sei Mainnet sei 1329 Production
Sei Testnet sei-testnet 1328 Testing
Peaq Network peaq 3338 Production

Frontend Integration

Install x402 Client

npm install x402 ethers@^6.15.0

Create Payment Hook (React)

import { useState } from 'react';
import { createPayment } from 'x402';
import { ethers } from 'ethers';

export function useX402Payment() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const fetchWithPayment = async (url: string, options: RequestInit = {}) => {
    setLoading(true);
    setError(null);

    try {
      // 1. Try request without payment
      const response = await fetch(url, {
        ...options,
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...options.headers },
      });

      // If not 402, return response
      if (response.status !== 402) {
        return await response.json();
      }

      // 2. Get payment requirements
      const { x402 } = await response.json();
      const requirements = x402.accepts[0];

      // 3. Connect wallet and sign payment
      const provider = new ethers.BrowserProvider(window.ethereum);
      await provider.send('eth_requestAccounts', []);
      const signer = await provider.getSigner();

      const payment = await createPayment(signer, requirements);

      // 4. Retry with payment
      const paidResponse = await fetch(url, {
        ...options,
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-402-payment': JSON.stringify(payment),
          ...options.headers,
        },
      });

      if (!paidResponse.ok) {
        const error = await paidResponse.json();
        throw new Error(error.reason || 'Payment failed');
      }

      return await paidResponse.json();
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Unknown error';
      setError(msg);
      throw err;
    } finally {
      setLoading(false);
    }
  };

  return { fetchWithPayment, loading, error };
}

Use in Component

import { useX402Payment } from './hooks/useX402Payment';

function MyComponent() {
  const { fetchWithPayment, loading, error } = useX402Payment();
  const [data, setData] = useState(null);

  const handleFetch = async () => {
    try {
      const result = await fetchWithPayment('http://localhost:3000/api/data');
      setData(result);
    } catch (err) {
      console.error('Payment failed:', err);
    }
  };

  return (
    <div>
      <button onClick={handleFetch} disabled={loading}>
        {loading ? 'Processing Payment...' : 'Fetch Data ($0.10)'}
      </button>
      
      {error && <div className="error">{error}</div>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

Testing

Test Without Payment (Should Return 402)

curl -X POST http://localhost:3000/api/data \
  -H "Content-Type: application/json" \
  -d '{}'

Expected Response:

{
  "success": false,
  "error": "Payment Required",
  "x402": {
    "x402Version": 1,
    "accepts": [
      {
        "scheme": "exact",
        "network": "solana-devnet",
        "asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
        "payTo": "6Cu2CyJpBXsKFpaBSXpD5wfypJn4pnzPn97oHanD6tTa",
        "maxAmountRequired": "100000",
        "resource": "/api/data",
        "description": "Get Protected Data"
      }
    ]
  }
}

Test With Frontend

  1. Start your backend server
  2. Open your frontend application
  3. Click the payment button
  4. Approve transaction in wallet (Phantom, MetaMask, etc.)
  5. Verify transaction on blockchain explorer
  6. Receive protected data

Verify Transaction

Solana Devnet:

https://explorer.solana.com/tx/[TRANSACTION_HASH]?cluster=devnet

Base Sepolia:

https://sepolia.basescan.org/tx/[TRANSACTION_HASH]

Advanced Configuration

Custom Network Configuration

const executor = new PaymentExecutor({
  payToAddress: '0xYourAddress',
  network: 'custom-network' as any,
  price: 0.10,
  assetAddress: '0xCustomTokenAddress',
  assetName: 'Custom Token',
  chainId: 12345,
  rpcUrl: 'https://custom-rpc.example.com',
});

Multiple Executors

// Different prices for different endpoints
const cheapExecutor = new PaymentExecutor({
  payToAddress: process.env.PAY_TO_ADDRESS!,
  network: 'solana-devnet',
  price: 0.01,
});

const expensiveExecutor = new PaymentExecutor({
  payToAddress: process.env.PAY_TO_ADDRESS!,
  network: 'solana-devnet',
  price: 1.00,
});

app.post('/api/cheap', async (req, res) => {
  const result = await cheapExecutor.execute(
    req, res, '/api/cheap', 'Cheap Service', '$0.01',
    async () => ({ data: 'cheap data' })
  );
  if (result) res.json(result);
});

app.post('/api/expensive', async (req, res) => {
  const result = await expensiveExecutor.execute(
    req, res, '/api/expensive', 'Premium Service', '$1.00',
    async () => ({ data: 'premium data' })
  );
  if (result) res.json(result);
});

Troubleshooting

CORS Errors

Error: Access to fetch blocked by CORS policy

Solution: Update CORS configuration:

app.use(cors({
  origin: 'http://localhost:5173', // Your frontend URL
  credentials: true,
  allowedHeaders: ['Content-Type', 'x-402-payment', 'x402-payment'],
}));

Payment Verification Failed

Error: Payment verification failed

Possible causes:

  • Network mismatch (frontend and backend using different networks)
  • Insufficient balance in user's wallet
  • Invalid signature
  • Expired authorization

Solution: Check backend logs for specific error message.

Settlement Failed (EVM)

Error: Payment settlement failed

Possible causes:

  • Missing PRIVATE_KEY in environment
  • Insufficient gas (ETH) in merchant wallet
  • RPC endpoint issues

Solution:

  1. Ensure PRIVATE_KEY is set in .env
  2. Check merchant wallet has ETH for gas
  3. Verify RPC URL is correct

Transaction Not Appearing

For Solana:

  • Check transaction on explorer: https://explorer.solana.com/tx/[HASH]?cluster=devnet
  • Wait 1-2 seconds for confirmation
  • Verify network (devnet vs mainnet)

For EVM:

  • Check transaction on explorer: https://sepolia.basescan.org/tx/[HASH]
  • Wait for block confirmation (5-15 seconds)
  • Check gas price wasn't too low

Security Best Practices

Environment Variables

DO:

  • Store private keys in .env files
  • Add .env to .gitignore
  • Use different keys for dev/staging/prod
  • Rotate keys periodically

DON'T:

  • Commit private keys to git
  • Share private keys in chat/email
  • Use production keys in development
  • Hard-code private keys in source code

Private Key Management

For Development:

PRIVATE_KEY=your_test_key_here

For Production:

  • Use secret management services (AWS Secrets Manager, HashiCorp Vault)
  • Use hardware wallets for key storage
  • Set up monitoring and alerts
  • Keep backup keys in secure offline storage

Monitoring

Monitor these metrics:

  • Payment success rate
  • Failed payment attempts
  • Transaction gas costs
  • Wallet balance
  • Unusual activity patterns

TypeScript Types

Available Exports

// Main class
import { PaymentExecutor } from 'x402-payment-executor';

// Types
import type {
  PaymentExecutorOptions,
  VerifyResult,
  SettlementResult,
  Network,
  PaymentPayload,
  PaymentRequirements,
  SettlementMode,
} from 'x402-payment-executor';

// Network utilities
import {
  BUILT_IN_NETWORKS,
  getDefaultRpcUrl,
  isSolanaNetwork,
  isSupportedEvmNetwork,
} from 'x402-payment-executor';

Type Definitions

type Network = 
  | 'solana' | 'solana-devnet'
  | 'base' | 'base-sepolia'
  | 'polygon' | 'polygon-amoy'
  | 'avalanche' | 'avalanche-fuji'
  | 'iotex' | 'sei' | 'sei-testnet' | 'peaq';

type SettlementMode = 'facilitator' | 'direct';

interface VerifyResult {
  isValid: boolean;
  payer?: string;
  invalidReason?: string;
}

interface SettlementResult {
  success: boolean;
  transaction?: string;
  network: string;
  payer?: string;
  errorReason?: string;
}

FAQ

Q: Do I need a private key for Solana?

A: No! Solana transactions are pre-signed by users. Private keys are only needed for EVM networks.

Q: How much does it cost to settle payments?

A:

  • Solana: User pays transaction fee (~$0.00001)
  • EVM: Merchant pays gas fee (~$0.01-0.05 on Base)

Q: Can I use this in production?

A: Yes! The package is production-ready. Just switch from testnet to mainnet networks.

Q: How do I get testnet USDC?

A:

  • Solana Devnet: Use Solana faucet or testnet DEX
  • Base Sepolia: Use Coinbase faucet, then swap for USDC

Q: What happens if payment fails?

A: The backend returns an error response, no data is provided, and no transaction is recorded.

Q: Can I customize the payment amount per request?

A: Yes! Create multiple PaymentExecutor instances with different prices, or pass dynamic prices to the constructor.


Support

Package Version: 1.0.0
Repository: https://github.com/0xstealthnode/x402-package
License: MIT

For issues or questions:

  • Open an issue on GitHub
  • Contact your package maintainer

Changelog

v1.0.0 (Initial Release)

  • Multi-chain support (Solana + 10 EVM networks)
  • Direct settlement mode
  • Payment verification and settlement
  • Full TypeScript support
  • Production-ready code

About

A payment execution library for x402 protocol supporting Base and Solana networks

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published