npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@payai/x402-solana

v2.0.0

Published

A framework-agnostic implementation of the x402 payment protocol v2 for Solana clients (browsers) and servers

Downloads

803

Readme

x402-solana

A reusable, framework-agnostic implementation of the x402 payment protocol v2 for Solana clients (browsers) and servers.

Features

x402 Protocol v2 - Full support for the latest x402 v2 specification
CAIP-2 Networks - Uses standardized chain identifiers (solana:chainId)
Client-side - Automatic 402 payment handling with any wallet provider
Server-side - Payment verification and settlement with facilitator
Framework agnostic - Works with any wallet provider (Privy, Phantom, etc.)
HTTP framework agnostic - Works with Next.js, Express, Fastify, etc.
TypeScript - Full type safety with Zod validation
Web3.js - Built on @solana/web3.js and @solana/spl-token

Installation

pnpm add @payai/x402-solana

Or with npm:

npm install @payai/x402-solana

Or with yarn:

yarn add @payai/x402-solana

x402 Protocol v2

This package implements x402 protocol v2. Key features:

| Feature | v2 Specification | | --------------------- | ------------------------------------------------- | | Network Format | CAIP-2 (solana:chainId) | | Payment Header | PAYMENT-SIGNATURE | | Amount Field | amount | | Payload Structure | Includes resource and accepted fields | | Response Body | PaymentRequired with x402Version: 2 |

Usage

Client Side (React/Frontend)

The x402-solana client works with any wallet provider that implements the WalletAdapter interface. Below are examples using both Solana Wallet Adapter and Privy.

Option 1: Using Solana Wallet Adapter (Recommended)

First, install the required packages:

npm install @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/wallet-adapter-wallets @solana/wallet-adapter-base

Setup your wallet provider in your app root (e.g., _app.tsx or layout.tsx):

import { useMemo } from 'react';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import {
  PhantomWalletAdapter,
  SolflareWalletAdapter,
  BackpackWalletAdapter,
} from '@solana/wallet-adapter-wallets';

// Import styles
import '@solana/wallet-adapter-react-ui/styles.css';

export default function App({ Component, pageProps }) {
  const network = WalletAdapterNetwork.Devnet; // or Mainnet
  const endpoint = useMemo(() => 'https://api.devnet.solana.com', []);

  const wallets = useMemo(
    () => [
      new PhantomWalletAdapter(),
      new SolflareWalletAdapter(),
      new BackpackWalletAdapter(),
    ],
    []
  );

  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={wallets} autoConnect>
        <WalletModalProvider>
          <Component {...pageProps} />
        </WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
}

Use in your component:

import { createX402Client } from '@payai/x402-solana/client';
import { useWallet } from '@solana/wallet-adapter-react';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';

function MyComponent() {
  const wallet = useWallet();

  const handlePaidRequest = async () => {
    if (!wallet.connected || !wallet.publicKey) {
      console.error('Wallet not connected');
      return;
    }

    // Create x402 client (v2)
    const client = createX402Client({
      wallet: {
        address: wallet.publicKey.toString(),
        signTransaction: async (tx) => {
          if (!wallet.signTransaction) throw new Error('Wallet does not support signing');
          return await wallet.signTransaction(tx);
        },
      },
      network: 'solana-devnet', // Simple format - automatically converted to CAIP-2
      amount: BigInt(10_000_000), // Optional: max 10 USDC safety limit
    });

    // Make a paid request - automatically handles 402 payments
    const response = await client.fetch('/api/paid-endpoint', {
      method: 'POST',
      body: JSON.stringify({ data: 'your request' }),
    });

    const result = await response.json();
    console.log('Result:', result);
  };

  return (
    <div>
      <WalletMultiButton />
      <button onClick={handlePaidRequest} disabled={!wallet.connected}>
        Make Paid Request
      </button>
    </div>
  );
}

Option 2: Using Privy

import { createX402Client } from '@payai/x402-solana/client';
import { useSolanaWallets } from '@privy-io/react-auth/solana';

function MyComponent() {
  const { wallets } = useSolanaWallets();
  const wallet = wallets[0];

  // Create x402 client (v2)
  const client = createX402Client({
    wallet,
    network: 'solana-devnet',
    amount: BigInt(10_000_000), // Optional: max 10 USDC
  });

  // Make a paid request - automatically handles 402 payments
  const response = await client.fetch('/api/paid-endpoint', {
    method: 'POST',
    body: JSON.stringify({ data: 'your request' }),
  });

  const result = await response.json();
}

Using with a Proxy Server (CORS Bypass)

If you're making requests from a browser to external APIs and encountering CORS issues, you can provide a custom fetch function that routes requests through your proxy server:

import { createX402Client } from '@payai/x402-solana/client';
import { useWallet } from '@solana/wallet-adapter-react';

function MyComponent() {
  const wallet = useWallet();

  // Create a custom fetch function that uses your proxy
  const createProxyFetch = () => {
    const proxyUrl = process.env.NEXT_PUBLIC_PROXY_URL || 'http://localhost:3001/api/proxy';

    return async (url: string | RequestInfo, init?: RequestInit): Promise<Response> => {
      // Send request through proxy server
      const response = await fetch(proxyUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          url: typeof url === 'string' ? url : url.toString(),
          method: init?.method || 'GET',
          headers: init?.headers || {},
          body: init?.body
        })
      });

      const proxyData = await response.json();

      // Reconstruct Response object with original status
      return new Response(
        typeof proxyData.data === 'string' ? proxyData.data : JSON.stringify(proxyData.data),
        {
          status: proxyData.status,
          statusText: proxyData.statusText || '',
          headers: new Headers(proxyData.headers || {})
        }
      );
    };
  };

  const handlePaidRequest = async () => {
    if (!wallet.connected || !wallet.publicKey) {
      console.error('Wallet not connected');
      return;
    }

    // Create x402 client with custom fetch (v2)
    const client = createX402Client({
      wallet: {
        address: wallet.publicKey.toString(),
        signTransaction: async (tx) => {
          if (!wallet.signTransaction) throw new Error('Wallet does not support signing');
          return await wallet.signTransaction(tx);
        },
      },
      network: 'solana-devnet',
      amount: BigInt(10_000_000),
      customFetch: createProxyFetch() // Use proxy for all requests
    });

    // All requests now go through your proxy server
    const response = await client.fetch('https://external-api.com/endpoint', {
      method: 'POST',
      body: JSON.stringify({ data: 'your request' }),
    });

    const result = await response.json();
    console.log('Result:', result);
  };

  return (
    <button onClick={handlePaidRequest} disabled={!wallet.connected}>
      Make Paid Request (via Proxy)
    </button>
  );
}

Benefits of using a proxy:

  • Bypasses browser CORS restrictions
  • Allows requests to any external x402 endpoint
  • Enables custom request/response logging
  • Provides a single point for request monitoring

Note: You need to set up your own proxy server. The customFetch parameter is optional - if not provided, the SDK uses the native fetch function.

Proxy Server Implementation

To use customFetch with a proxy, you need to implement a proxy server endpoint. Here's a complete example:

Next.js API Route (app/api/proxy/route.ts):

import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest) {
  try {
    const { url, method, headers, body } = await req.json();

    // Validate inputs
    if (!url || !method) {
      return NextResponse.json({ error: 'url and method required' }, { status: 400 });
    }

    // Prepare headers (preserve x402 v2 payment headers)
    const requestHeaders: Record<string, string> = {
      'Content-Type': headers?.['Content-Type'] || 'application/json',
      'User-Agent': 'x402-solana-proxy/2.0',
      ...(headers || {}),
    };

    // Remove problematic headers
    delete requestHeaders['host'];
    delete requestHeaders['content-length'];

    // Make request to target endpoint
    const fetchOptions: RequestInit = {
      method: method.toUpperCase(),
      headers: requestHeaders,
    };

    if (method.toUpperCase() !== 'GET' && body) {
      fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
    }

    const response = await fetch(url, fetchOptions);

    // Parse response
    const contentType = response.headers.get('content-type') || '';
    let responseData: unknown;

    if (contentType.includes('application/json')) {
      responseData = await response.json();
    } else {
      responseData = await response.text();
    }

    // Prepare response headers
    const responseHeaders: Record<string, string> = {};
    response.headers.forEach((value, key) => {
      if (
        !['content-encoding', 'transfer-encoding', 'content-length'].includes(key.toLowerCase())
      ) {
        responseHeaders[key] = value;
      }
    });

    // IMPORTANT: Return 200 with real status in body
    // This allows proper x402 402 Payment Required handling
    return NextResponse.json(
      {
        status: response.status,
        statusText: response.statusText,
        headers: responseHeaders,
        data: responseData,
        contentType,
      },
      { status: 200 }
    );
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : 'Unknown error';
    console.error('[Proxy] Error:', message);
    return NextResponse.json(
      {
        error: 'Proxy request failed',
        details: message,
      },
      { status: 500 }
    );
  }
}

Express Server (server.js):

import express from 'express';
import cors from 'cors';

const app = express();
app.use(cors());
app.use(express.json());

app.post('/api/proxy', async (req, res) => {
  try {
    const { url, method, headers, body } = req.body;

    if (!url || !method) {
      return res.status(400).json({ error: 'url and method required' });
    }

    const requestHeaders = {
      'Content-Type': headers?.['Content-Type'] || 'application/json',
      ...(headers || {}),
    };

    delete requestHeaders['host'];
    delete requestHeaders['content-length'];

    const fetchOptions = {
      method: method.toUpperCase(),
      headers: requestHeaders,
    };

    if (method.toUpperCase() !== 'GET' && body) {
      fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
    }

    const response = await fetch(url, fetchOptions);
    const contentType = response.headers.get('content-type') || '';

    let responseData;
    if (contentType.includes('application/json')) {
      responseData = await response.json();
    } else {
      responseData = await response.text();
    }

    const responseHeaders = {};
    response.headers.forEach((value, key) => {
      if (
        !['content-encoding', 'transfer-encoding', 'content-length'].includes(key.toLowerCase())
      ) {
        responseHeaders[key] = value;
      }
    });

    // Return 200 with real status in body for x402 v2 compatibility
    res.status(200).json({
      status: response.status,
      statusText: response.statusText,
      headers: responseHeaders,
      data: responseData,
      contentType,
    });
  } catch (error) {
    console.error('[Proxy] Error:', error.message);
    res.status(500).json({
      error: 'Proxy request failed',
      details: error.message,
    });
  }
});

app.listen(3001, () => console.log('Proxy server running on port 3001'));

Key Points:

  • Always return HTTP 200 from proxy, with real status code in the response body
  • This is critical for x402 v2 402 Payment Required responses to work correctly
  • Preserve x402 v2 headers (PAYMENT-SIGNATURE, PAYMENT-RESPONSE)
  • Remove problematic headers (host, content-length)

Server Side (Next.js API Route)

import { NextRequest, NextResponse } from 'next/server';
import { X402PaymentHandler } from '@payai/x402-solana/server';

const x402 = new X402PaymentHandler({
  network: 'solana-devnet', // Simple format - automatically converted to CAIP-2
  treasuryAddress: process.env.TREASURY_WALLET_ADDRESS!,
  facilitatorUrl: 'https://facilitator.payai.network',
});

export async function POST(req: NextRequest) {
  const resourceUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/api/chat`;

  // 1. Extract payment header (v2 uses PAYMENT-SIGNATURE)
  const paymentHeader = x402.extractPayment(req.headers);

  // 2. Create payment requirements (v2 format)
  const paymentRequirements = await x402.createPaymentRequirements(
    {
      amount: '2500000', // $2.50 USDC (in atomic units, as string)
      asset: {
        address: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // USDC devnet
        decimals: 6,
      },
      description: 'AI Chat Request',
    },
    resourceUrl
  );

  if (!paymentHeader) {
    // Return 402 with v2 payment requirements
    const response = x402.create402Response(paymentRequirements, resourceUrl);
    return NextResponse.json(response.body, { status: response.status });
  }

  // 3. Verify payment
  const verified = await x402.verifyPayment(paymentHeader, paymentRequirements);
  if (!verified.isValid) {
    return NextResponse.json(
      {
        error: 'Invalid payment',
        reason: verified.invalidReason,
      },
      { status: 402 }
    );
  }

  // 4. Process your business logic
  const result = await yourBusinessLogic(req);

  // 5. Settle payment
  const settlement = await x402.settlePayment(paymentHeader, paymentRequirements);
  if (!settlement.success) {
    console.error('Settlement failed:', settlement.errorReason);
  }

  // 6. Return response
  return NextResponse.json(result);
}

Server Side (Express)

import express from 'express';
import { X402PaymentHandler } from '@payai/x402-solana/server';

const app = express();
const x402 = new X402PaymentHandler({
  network: 'solana-devnet',
  treasuryAddress: process.env.TREASURY_WALLET_ADDRESS!,
  facilitatorUrl: 'https://facilitator.payai.network',
});

app.post('/api/paid-endpoint', async (req, res) => {
  const resourceUrl = `${process.env.BASE_URL}/api/paid-endpoint`;
  const paymentHeader = x402.extractPayment(req.headers);

  const paymentRequirements = await x402.createPaymentRequirements(
    {
      amount: '2500000', // $2.50 USDC
      asset: {
        address: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // USDC devnet
        decimals: 6,
      },
      description: 'API Request',
    },
    resourceUrl
  );

  if (!paymentHeader) {
    const response = x402.create402Response(paymentRequirements, resourceUrl);
    return res.status(response.status).json(response.body);
  }

  const verified = await x402.verifyPayment(paymentHeader, paymentRequirements);
  if (!verified.isValid) {
    return res.status(402).json({
      error: 'Invalid payment',
      reason: verified.invalidReason,
    });
  }

  const result = await yourBusinessLogic(req);
  await x402.settlePayment(paymentHeader, paymentRequirements);

  res.json(result);
});

API Reference

Client

createX402Client(config)

Creates a new x402 client instance.

Config:

{
  wallet: WalletAdapter;              // Wallet with signTransaction method
  network: 'solana' | 'solana-devnet'; // Simple network format
  rpcUrl?: string;                    // Optional custom RPC
  amount?: bigint;                    // Optional safety limit (max payment)
  customFetch?: typeof fetch;         // Optional custom fetch for proxy support
  verbose?: boolean;                  // Optional debug logging
}

Methods:

  • client.fetch(input, init) - Make a fetch request with automatic payment handling

Server

new X402PaymentHandler(config)

Creates a new payment handler instance.

Config:

{
  network: 'solana' | 'solana-devnet'; // Simple network format
  treasuryAddress: string;            // Where payments are sent
  facilitatorUrl: string;             // Facilitator service URL
  rpcUrl?: string;                    // Optional custom RPC
  defaultToken?: TokenAsset;          // Optional default token (auto-detected)
  defaultDescription?: string;        // Optional default description
  defaultTimeoutSeconds?: number;     // Optional timeout (default: 300)
}

Methods:

  • extractPayment(headers) - Extract PAYMENT-SIGNATURE header from request
  • createPaymentRequirements(routeConfig, resourceUrl) - Create v2 payment requirements object
  • create402Response(requirements, resourceUrl) - Create v2 402 response body
  • verifyPayment(header, requirements) - Verify payment with facilitator
  • settlePayment(header, requirements) - Settle payment with facilitator
  • getNetwork() - Get the network in CAIP-2 format
  • getTreasuryAddress() - Get the treasury address

RouteConfig Format

The createPaymentRequirements method expects:

{
  amount: string;              // Payment amount in atomic units (string)
  asset: {
    address: string;           // Token mint address (USDC)
    decimals: number;          // Token decimals (6 for USDC)
  },
  description?: string;        // Optional human-readable description
  mimeType?: string;           // Optional, defaults to 'application/json'
  maxTimeoutSeconds?: number;  // Optional, defaults to 300
}

Network Configuration

CAIP-2 Network Identifiers

x402 v2 uses CAIP-2 format for network identifiers:

| Network | Simple Format | CAIP-2 Format | | ------- | --------------- | ----------------------------------------- | | Mainnet | solana | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | | Devnet | solana-devnet | solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 |

The library automatically converts simple network names to CAIP-2 format internally. You can use either format in your configuration.

Network Utility Functions

import {
  toCAIP2Network,
  toSimpleNetwork,
  isSolanaNetwork,
  isSolanaMainnet,
  isSolanaDevnet,
} from '@payai/x402-solana/types';

// Convert between formats
const caip2 = toCAIP2Network('solana-devnet'); // 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1'
const simple = toSimpleNetwork('solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1'); // 'solana-devnet'

// Type guards
isSolanaNetwork('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'); // true
isSolanaMainnet('solana'); // true
isSolanaDevnet('solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1'); // true

Configuration

Environment Variables

# Network (optional, defaults to devnet)
NEXT_PUBLIC_NETWORK=solana-devnet

# Treasury wallet address (where payments are sent)
TREASURY_WALLET_ADDRESS=your_treasury_address

# Optional: Custom RPC URLs
NEXT_PUBLIC_SOLANA_RPC_DEVNET=https://api.devnet.solana.com
NEXT_PUBLIC_SOLANA_RPC_MAINNET=https://api.mainnet-beta.solana.com

# Base URL for resource field
NEXT_PUBLIC_BASE_URL=http://localhost:3000

USDC Mint Addresses

When creating payment requirements, you need to specify the USDC token mint address:

  • Devnet: 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
  • Mainnet: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v

Example with environment-based selection:

const USDC_MINT =
  process.env.NODE_ENV === 'production'
    ? 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // mainnet
    : '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'; // devnet

const paymentRequirements = await x402.createPaymentRequirements(
  {
    amount: '1000000', // $1.00 USDC
    asset: {
      address: USDC_MINT,
      decimals: 6,
    },
    description: 'Payment',
  },
  `${process.env.BASE_URL}/api/endpoint`
);

Wallet Adapter Interface

The package works with any wallet that implements this interface:

interface WalletAdapter {
  // Support for Anza wallet-adapter standard
  publicKey?: { toString(): string };
  // Alternative for custom implementations
  address?: string;
  // Required for signing
  signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
}

Compatible Wallet Providers

Solana Wallet Adapter (@solana/wallet-adapter-react)

The official Solana wallet adapter provides the most flexibility and supports multiple wallets:

import { useWallet } from '@solana/wallet-adapter-react';

const wallet = useWallet();
const walletAdapter = {
  publicKey: wallet.publicKey,
  signTransaction: wallet.signTransaction,
};

Privy (@privy-io/react-auth)

Privy wallets work out of the box:

import { useSolanaWallets } from '@privy-io/react-auth/solana';

const { wallets } = useSolanaWallets();
const wallet = wallets[0]; // Already implements the interface

Direct Wallet SDKs

You can also use wallet SDKs directly:

// Phantom
const phantomProvider = window.phantom?.solana;
const walletAdapter = {
  address: phantomProvider.publicKey.toString(),
  signTransaction: tx => phantomProvider.signTransaction(tx),
};

// Solflare
const solflareProvider = window.solflare;
const walletAdapter = {
  address: solflareProvider.publicKey.toString(),
  signTransaction: tx => solflareProvider.signTransaction(tx),
};

Payment Amounts

Payment amounts are in USDC atomic units (6 decimals) as strings:

  • 1 USDC = "1000000" atomic units
  • $0.01 = "10000" atomic units
  • $2.50 = "2500000" atomic units

Helper functions:

import { toAtomicUnits, fromAtomicUnits } from '@payai/x402-solana/utils';

const atomicUnits = toAtomicUnits(2.5, 6); // "2500000"
const usd = fromAtomicUnits('2500000', 6); // 2.5

Protocol Details

PaymentRequired Response (v2)

When a resource requires payment, the server returns a 402 status with this body:

{
  "x402Version": 2,
  "resource": {
    "url": "https://api.example.com/v1/ai/generate",
    "description": "AI text generation",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
      "amount": "2500000",
      "payTo": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHEBg4",
      "maxTimeoutSeconds": 300,
      "asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
      "extra": {
        "feePayer": "CKPKJWNdJEqa81x7CkZ14BVPiY6y16Sxs7owznqtWYp5"
      }
    }
  ],
  "error": "Payment required"
}

PaymentPayload (v2)

The client sends payment via PAYMENT-SIGNATURE header containing base64-encoded JSON:

{
  "x402Version": 2,
  "resource": {
    "url": "https://api.example.com/v1/ai/generate",
    "description": "AI text generation",
    "mimeType": "application/json"
  },
  "accepted": {
    "scheme": "exact",
    "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
    "amount": "2500000",
    "payTo": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHEBg4",
    "maxTimeoutSeconds": 300,
    "asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
    "extra": {
      "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4"
    }
  },
  "payload": {
    "transaction": "<base64-encoded-signed-transaction>"
  }
}

Testing

Run the test suite:

npm test

The tests verify:

✅ Package imports work correctly
✅ Client can be created with wallet adapter
✅ Automatic 402 payment handling works
✅ Transaction signing and submission succeed
✅ Payment verification and settlement complete

Architecture

src/
├── client/                    # Client-side code
│   ├── transaction-builder.ts # Solana transaction construction
│   ├── payment-interceptor.ts # 402 payment fetch interceptor
│   └── index.ts              # Main client export
├── server/                    # Server-side code
│   ├── facilitator-client.ts # Facilitator API communication
│   ├── payment-handler.ts    # Payment verification & settlement
│   └── index.ts              # Main server export
├── types/                     # TypeScript types
│   ├── x402-protocol.ts      # x402 v2 spec types (CAIP-2 networks)
│   ├── solana-payment.ts     # Solana-specific types
│   └── index.ts
├── utils/                     # Utilities
│   ├── helpers.ts            # Helper functions
│   └── index.ts
└── index.ts                   # Main package export

Development

Running Tests

npm test

Linting

npm run lint
npm run lint:fix  # Auto-fix issues

Type Checking

npm run typecheck

Building

npm run build

Future Enhancements

  • [ ] Add @solana/kit adapter for AI agents
  • [ ] Support for multiple payment tokens
  • [ ] Add transaction retry logic
  • [ ] Support for partial payments
  • [ ] Extensions support (SIWx, Discovery)

License

MIT

Credits

Built on top of:

Support

Version

Current version: 2.0.0 (x402 Protocol v2)