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

solana-pay-x402

v0.1.0

Published

HTTP 402 payment middleware for Solana (Express + Next.js). Supports x402 v2 (programmatic, facilitator-managed) and Solana Pay (QR code, on-chain) from a single integration.

Downloads

14

Readme

Solana Pay x402

Middleware that puts any API endpoint behind a Solana payment. Works with Express and Next.js. Drop it into a route and your endpoint returns 402 Payment Required until the client pays.

Two payment flows, one middleware — the server figures out which one the client is using:

  • x402 v2: Client signs a transaction, facilitator submits it and pays gas. Good for dApps, APIs, and anything programmatic.
  • Solana Pay: Client scans a QR code with a mobile wallet, pays on-chain directly. Good for mobile, retail, POS.

Quick Start

npm install solana-pay-x402

Express

import express from 'express'
import { solanaPay402 } from 'solana-pay-x402/express'

const app = express()

app.get('/api/premium',
  solanaPay402({
    rpcUrl: 'https://api.devnet.solana.com',
    recipient: 'YOUR_WALLET_ADDRESS',
    network: 'devnet',
    getPaymentAmount: () => 0.01 * 1e9, // 0.01 SOL in lamports
  }),
  (req, res) => {
    res.json({ content: 'You paid for this.' })
  }
)

app.listen(3000)

Next.js (App Router)

// app/api/premium/route.ts
import { withSolanaPay402 } from 'solana-pay-x402/nextjs'

export const GET = withSolanaPay402(async (req, { payment }) => {
  return Response.json({
    content: 'You paid for this.',
    txSignature: payment?.signature,
  })
}, {
  rpcUrl: process.env.SOLANA_RPC_URL!,
  recipient: process.env.MERCHANT_WALLET!,
  network: 'devnet',
  getPaymentAmount: () => 0.01 * 1e9,
})

That's it. The middleware handles the 402 response, payment verification, and settlement.

How It Works

x402 v2 Flow (Programmatic)

Client                    Server                   Facilitator
  |-- GET /api/premium ----->|                          |
  |<---- 402 + requirements -|                          |
  |                          |                          |
  | (build tx, sign with wallet, don't submit)          |
  |                          |                          |
  |-- GET /api/premium ----->|                          |
  |   + PAYMENT-SIGNATURE    |-- verify + settle ------>|
  |                          |<---- ok, tx submitted ---|
  |<---- 200 + content ------|                          |

The client signs a transaction but never submits it. The facilitator submits it, verifies payment, and covers gas fees. This is the x402 protocol — the client just uses createX402Client from x402-solana/client and it handles everything behind a fetch() call.

Solana Pay Flow (QR Code)

Client                    Server                   Solana
  |-- GET /api/premium ----->|                        |
  |<---- 402 + QR URL ------|                        |
  |                          |                        |
  | (scan QR, wallet submits tx on-chain)             |
  |                          |                   tx on-chain
  |-- GET /api/premium ----->|                        |
  |   + PAYMENT-SIGNATURE    |-- getTransaction ----->|
  |     (tx signature)       |<---- tx details -------|
  |<---- 200 + content ------|                        |

The client scans a QR code, their wallet submits the transaction directly on-chain, and the client sends the transaction signature back. The server verifies on-chain.

402 Response

When a request hits a gated endpoint without payment, the response includes both payment options:

{
  "x402Version": 2,
  "accepts": [{
    "scheme": "exact",
    "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
    "amount": "10",
    "payTo": "YOUR_WALLET",
    "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "extra": { "feePayer": "..." }
  }],
  "solanaPay": {
    "url": "solana:YOUR_WALLET?amount=0.00001&spl-token=EPjFW...",
    "reference": "Bx7j8K..."
  }
}

x402 clients read the accepts array. Solana Pay clients use the solanaPay.url for QR codes. The middleware auto-detects which flow the client used when they come back with the PAYMENT-SIGNATURE header.

Configuration

Express

solanaPay402({
  // Required
  rpcUrl: 'https://api.devnet.solana.com',
  recipient: 'YOUR_WALLET_ADDRESS',
  getPaymentAmount: (req) => 1000000,  // return null to skip payment

  // Optional
  network: 'devnet',                    // default: 'mainnet-beta'
  label: 'My API',                      // shown in wallet
  message: 'Thanks for paying',         // memo field
  autoSettle: true,                     // default: true
  facilitatorUrl: 'https://...',        // default: PayAI Network

  // SPL token (defaults to native SOL)
  splToken: {
    mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
    decimals: 6,
  },

  // Callbacks
  onPaymentVerified: (req, verification) => {
    console.log('Paid:', verification.signature, verification.amount)
  },
  onPaymentFailed: (req, error) => {
    console.error('Failed:', error)
  },
})

Next.js

withSolanaPay402(handler, {
  // Same config as Express, except:
  // - getPaymentAmount receives a web standard Request (not Express req)
  // - Payment info is passed via context, not attached to req

  rpcUrl: process.env.SOLANA_RPC_URL!,
  recipient: process.env.MERCHANT_WALLET!,
  getPaymentAmount: (req) => 1000000,

  // Callbacks receive Request instead of Express req
  onPaymentVerified: (req, verification) => {
    console.log('Paid:', verification.signature)
  },
})

The handler receives a PaymentContext as the second argument:

export const GET = withSolanaPay402(async (req, { payment }) => {
  // payment is undefined if no payment was required (amount was null/0)
  // payment.valid, payment.signature, payment.amount when paid
  return Response.json({ data: 'content' })
}, options)

Examples

Dynamic Pricing

const pricing = { small: 1000000, medium: 5000000, large: 10000000 }

app.get('/api/data/:size',
  solanaPay402({
    rpcUrl: process.env.SOLANA_RPC_URL,
    recipient: process.env.MERCHANT_WALLET,
    getPaymentAmount: (req) => pricing[req.params.size] || null,
  }),
  (req, res) => {
    res.json({ data: `Content for ${req.params.size}` })
  }
)

USDC Payments

app.get('/api/premium',
  solanaPay402({
    rpcUrl: process.env.SOLANA_RPC_URL,
    recipient: process.env.MERCHANT_WALLET,
    splToken: {
      mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
      decimals: 6,
    },
    getPaymentAmount: () => 100, // $0.0001 USDC
  }),
  (req, res) => {
    res.json({ content: 'Paid with USDC' })
  }
)

Multi-Token Pricing

Accept multiple tokens on the same endpoint. The 402 response includes one entry per token in the accepts array, and the client picks which one to pay with.

app.get('/api/premium',
  solanaPay402({
    rpcUrl: process.env.SOLANA_RPC_URL,
    recipient: process.env.MERCHANT_WALLET,
    getPaymentAmount: () => 100, // base amount in USDC atomic units
    acceptedTokens: [
      { mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', decimals: 6, label: 'USDC' },
      {
        mint: 'So11111111111111111111111111111111111111112',
        decimals: 9,
        label: 'SOL',
        // Convert USDC amount to SOL using live exchange rate
        amount: async (baseAmount) => {
          const price = await fetchSolPrice() // your price fetcher
          return Math.ceil(Number(baseAmount) / price)
        },
      },
    ],
  }),
  (req, res) => {
    res.json({ content: 'Paid with USDC or SOL' })
  }
)

The same acceptedTokens option works in the Next.js adapter.

Reading Payment Info After Verification

import { getPaymentInfo } from 'solana-pay-x402/express'

app.get('/api/premium',
  solanaPay402({ /* config */ }),
  (req, res) => {
    const payment = getPaymentInfo(req)
    res.json({
      content: 'Premium stuff',
      paidAmount: payment?.amount,
      txSignature: payment?.signature,
    })
  }
)

Client-Side: x402 v2

Use x402-solana/client for automatic payment handling. It wraps fetch() — if the server returns 402, it builds a transaction, asks the wallet to sign, and retries with the payment header. One line:

import { createX402Client } from 'x402-solana/client'

const client = createX402Client({
  wallet: phantomWallet, // any Solana wallet adapter
  network: 'solana-devnet',
})

// This handles 402 → sign → retry automatically
const response = await client.fetch('http://localhost:3000/api/premium')
const data = await response.json()

Client-Side: Solana Pay

For QR-based payments, the 402 response includes a solanaPay.url that you display as a QR code. After the user pays with their mobile wallet, send the transaction signature back:

// 1. Request endpoint, get 402
const res = await fetch('/api/premium')
const { solanaPay } = await res.json()

// 2. Show QR code with solanaPay.url
displayQR(solanaPay.url)

// 3. After user pays and you have the tx signature
const proof = btoa(JSON.stringify({ signature: txSig, scheme: 'exact' }))
const content = await fetch('/api/premium', {
  headers: { 'PAYMENT-SIGNATURE': proof }
})

Demo

The repo includes a working demo with both flows:

cd demo
SOLANA_NETWORK=devnet npx tsx server.ts

Open http://localhost:3000 — pick an endpoint, try x402 with Phantom browser extension or Solana Pay with the QR code.

How This Compares to x402-solana

| | solana-pay-x402 | x402-solana | |---|---|---| | What it is | Express + Next.js middleware | Protocol implementation | | Solana Pay QR | Built-in | Not included | | x402 v2 | Built-in | Built-in | | Multi-token | Built-in with async price converters | Manual | | Frameworks | Express, Next.js App Router | Bring your own | | Setup | One-line middleware | Wire it up yourself | | Best for | Ship fast | Custom setups |

This package uses x402-solana under the hood. If you're on Express or Next.js and want both payment flows without the plumbing, this is the shortcut.

Development

npm install
npm run build
npm test

Environment Variables

SOLANA_RPC_URL=https://api.devnet.solana.com
MERCHANT_WALLET=YOUR_WALLET_ADDRESS
SOLANA_NETWORK=devnet
PORT=3000

Disclaimer

This software is provided as-is, without warranty. It has not been independently audited. Use at your own risk, especially on mainnet with real funds.

This package relies on the PayAI Network facilitator for x402 v2 payment verification and settlement. The authors are not responsible for facilitator downtime, failed transactions, or lost funds.

Always test on devnet before deploying to mainnet. The authors are not liable for any financial losses resulting from the use of this software. See LICENSE for full terms.

License

MIT

Links