Skip to content

@stablecoin.xyz/x402 SDK

npm install @stablecoin.xyz/x402
npm →

:::tip Prerequisites


I'm building a server (gate my API)

Express
import express from 'express'
import { x402Middleware } from '@stablecoin.xyz/x402/middleware/express'
 
const app = express()
 
app.get('/premium',
  x402Middleware({
    payTo: '0xYourAddress',
    amount: '1000000',         // 1 SBC (6 decimals on Base Sepolia)
    network: 'base-sepolia',   // testnet — no API key required
  }),
  (req, res) => {
    res.json({ data: 'you paid for this' })
  }
)

For mainnet, add apiKey:

x402Middleware({
  payTo: '0xYourAddress',
  amount: '1000000000000000',  // 0.001 SBC (18 decimals on Base mainnet)
  network: 'base',
  apiKey: process.env.API_KEY,
})

Accept multiple networks

Let clients pay in any network you support:

x402Middleware([
  { payTo: '0xYourEVMAddress',  amount: '1000000000000000', network: 'base',   apiKey: process.env.API_KEY },
  { payTo: 'YourSolanaAddress', amount: '1000000000',       network: 'solana', apiKey: process.env.API_KEY },
])

The client picks whichever network it supports. Your server accepts either.


I'm building a client (pay for APIs)

EVM

viem
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { createX402Client, viemSignerAdapter } from '@stablecoin.xyz/x402/evm'
 
const walletClient = createWalletClient({
  account: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`),
  chain: base,
  transport: http(),
})
 
const client = createX402Client({
  signer: viemSignerAdapter(walletClient),
  network: 'base',
  apiKey: process.env.API_KEY,
})
 
// drop-in for fetch — handles 402, signs off-chain, retries automatically
const response = await client.fetch('https://api.example.com/premium')
const data = await response.json()
 
if (response.paymentResult) {
  console.log('tx:', response.paymentResult.txHash)
  console.log('paid:', response.paymentResult.amountPaid)
}

Solana

npm install @stablecoin.xyz/x402 @solana/web3.js
import { createSolanaX402Client, keypairSignerAdapter } from '@stablecoin.xyz/x402/solana'
import { Keypair } from '@solana/web3.js'
 
const keypair = Keypair.fromSecretKey(/* your key bytes */)
 
const client = createSolanaX402Client({
  signer: keypairSignerAdapter(keypair),
  network: 'solana-devnet',  // testnet — no API key required
})
 
const response = await client.fetch('https://api.example.com/premium')

React hook

npm install @stablecoin.xyz/x402 viem react
import { useX402, viemSignerAdapter } from '@stablecoin.xyz/x402/react'
 
function PremiumButton({ walletClient }) {
  const { fetch, paying } = useX402({
    signer: walletClient ? viemSignerAdapter(walletClient) : null,
    network: 'base-sepolia',
  })
 
  async function loadData() {
    const res = await fetch('https://api.example.com/premium')
    console.log(await res.json())
  }
 
  return (
    <button onClick={loadData} disabled={paying}>
      {paying ? 'Paying...' : 'Get Premium Data'}
    </button>
  )
}

The useX402 hook integrates directly with SBC AppKit — walletClient from useSbcApp() is a standard viem WalletClient and works as-is.


API Reference

createX402Client(options)

OptionTypeDefaultDescription
signerEvmSignerrequiredAdapter wrapping your wallet (see below)
networkNetworkKey'base-sepolia'Network to pay on
apiKeystringRequired for mainnet
facilitatorUrlstringhttps://x402.stablecoin.xyzSelf-hosted override
rpcUrlstringRPC for balance pre-check
skipBalanceCheckbooleanfalseSkip pre-flight balance check

createSolanaX402Client(options)

OptionTypeDefaultDescription
signerSolanaSignerrequiredAdapter wrapping your keypair
networkNetworkKey'solana-devnet''solana' or 'solana-devnet'
apiKeystringRequired for mainnet

x402Middleware(options) / withX402(options, handler)

OptionTypeDefaultDescription
payTostringrequiredRecipient address
amountstringrequiredAmount in atomic units (see Networks for decimals)
networkNetworkKeyrequirede.g. 'base', 'solana'
apiKeystringRequired for mainnet
settlebooleantrueSet false to verify only (no on-chain settlement)
maxTimeoutSecondsnumber300Seconds the client has to submit payment
facilitatorUrlstringhttps://x402.stablecoin.xyzSelf-hosted override

EVM signer adapters

import { viemSignerAdapter, ethersSignerAdapter } from '@stablecoin.xyz/x402/evm'
 
viemSignerAdapter(walletClient)    // viem WalletClient (EOA, smart account, embedded wallet)
ethersSignerAdapter(signer)        // ethers.js v5 or v6 Signer

Solana signer adapters

import { keypairSignerAdapter, walletAdapterSignerAdapter } from '@stablecoin.xyz/x402/solana'
 
keypairSignerAdapter(keypair)               // @solana/web3.js Keypair (server-side)
walletAdapterSignerAdapter(walletAdapter)   // @solana/wallet-adapter-base (browser)

Troubleshooting

My endpoint returned 402 but the client didn't pay

You're using native fetch. Replace with client.fetch from createX402Client or createSolanaX402Client — it handles the 402 response, signs the payment, and retries automatically.


401 Unauthorized from the facilitator

apiKey is missing. Mainnet networks (base, solana) require an API key. Get one at dashboard.stablecoin.xyz and pass it as apiKey in your options.


403 Forbidden from the facilitator

Your API key is invalid, inactive, or revoked. Verify it in the dashboard.


Payment rejected: insufficient balance

Your wallet doesn't have enough SBC. For testnet, get free tokens at faucet.stablecoin.xyz (Base Sepolia) or radiustech.xyz (Radius). For mainnet, acquire SBC on the respective network.


Solana: payment rejected immediately

The payer wallet hasn't approved the facilitator as an SPL token delegate. This is a one-time step per wallet. See Facilitator → Solana setup.


Payment rejected: amount mismatch

Check decimals. Base mainnet uses 18 decimals1000000000000000000 = 1 SBC. All other networks use 6 or 9 decimals. See Networks.


Testnet quickstart

No API key, no real funds. Start here:

  1. Get testnet SBC from faucet.stablecoin.xyz (Base Sepolia) or radiustech.xyz (Radius)
  2. Use network: 'base-sepolia', network: 'radius-testnet', or network: 'solana-devnet'
  3. Run the examples in examples/