Unified x402 payment SDK for Solana, Base, Avalanche, and SKALE Base.
x402 is a protocol for HTTP-native micropayments. When a server returns HTTP 402 Payment Required, it includes payment details in the response. The client signs a payment, retries the request, and the server settles the payment and returns the protected content.
This SDK handles the entire flow automatically — call fetch() and payments happen transparently.
Multi-chain. Solana, Base, Avalanche, and SKALE Base with a single API. Connect your wallets and the SDK picks the right chain and signing method automatically.
Zero gas fees. The RelAI facilitator sponsors gas — users only pay for content (USDC).
Auto-detects signing method. EIP-3009 transferWithAuthorization for all EVM networks (Base, Avalanche, SKALE Base), native SPL transfer for Solana — all handled internally.
Works out of the box. Uses the RelAI facilitator by default.
npm install @relai-fi/x402import { createX402Client } from '@relai-fi/x402/client';
const client = createX402Client({
wallets: {
solana: solanaWallet, // @solana/wallet-adapter compatible
evm: evmWallet, // wagmi/viem compatible
},
});
// 402 responses are handled automatically
const response = await client.fetch('https://api.example.com/protected');
const data = await response.json();Works with @solana/wallet-adapter-react and wagmi:
import { useRelaiPayment } from '@relai-fi/x402/react';
import { useWallet } from '@solana/wallet-adapter-react';
import { useAccount, useSignTypedData } from 'wagmi';
function PayButton() {
const solanaWallet = useWallet();
const { address } = useAccount();
const { signTypedDataAsync } = useSignTypedData();
const {
fetch,
isLoading,
status,
transactionUrl,
transactionNetworkLabel,
} = useRelaiPayment({
wallets: {
solana: solanaWallet,
evm: address ? { address, signTypedData: signTypedDataAsync } : undefined,
},
});
return (
<div>
<button onClick={() => fetch('/api/protected')} disabled={isLoading}>
{isLoading ? 'Paying...' : 'Access API'}
</button>
{transactionUrl && (
<a href={transactionUrl} target="_blank">
View on {transactionNetworkLabel}
</a>
)}
</div>
);
}| Network | Identifier | CAIP-2 | Signing Method | USDC Contract |
|---|---|---|---|---|
| Solana | solana |
solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
SPL transfer + fee payer | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| Base | base |
eip155:8453 |
EIP-3009 transferWithAuthorization | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Avalanche | avalanche |
eip155:43114 |
EIP-3009 transferWithAuthorization | 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E |
| SKALE Base | skale-base |
eip155:1187947933 |
EIP-3009 transferWithAuthorization | 0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20 |
All networks use USDC with 6 decimals. Gas fees are sponsored by the RelAI facilitator.
// Client — browser & Node.js fetch wrapper with automatic 402 handling
import { createX402Client } from '@relai-fi/x402/client';
// React hook — state management + wallet integration
import { useRelaiPayment } from '@relai-fi/x402/react';
// Server — Express middleware for protecting endpoints
import Relai from '@relai-fi/x402/server';
// Utilities — payload conversion, unit helpers
import {
convertV1ToV2,
convertV2ToV1,
networkV1ToV2,
networkV2ToV1,
toAtomicUnits,
fromAtomicUnits,
} from '@relai-fi/x402/utils';
// Types & constants
import {
RELAI_NETWORKS,
CHAIN_IDS,
USDC_ADDRESSES,
NETWORK_CAIP2,
EXPLORER_TX_URL,
type RelaiNetwork,
type SolanaWallet,
type EvmWallet,
type WalletSet,
} from '@relai-fi/x402';Creates a fetch wrapper that automatically handles 402 Payment Required responses.
| Option | Type | Default | Description |
|---|---|---|---|
wallets |
{ solana?, evm? } |
{} |
Wallet adapters for each chain |
facilitatorUrl |
string |
RelAI facilitator | Custom facilitator endpoint |
preferredNetwork |
RelaiNetwork |
— | Prefer this network when multiple accepts |
solanaRpcUrl |
string |
https://api.mainnet-beta.solana.com |
Solana RPC (use Helius/Quicknode for production) |
evmRpcUrls |
Record<string, string> |
Built-in defaults | RPC URLs per network name |
maxAmountAtomic |
string |
— | Safety cap on payment amount |
verbose |
boolean |
false |
Log payment flow to console |
Wallet interfaces:
// Solana — compatible with @solana/wallet-adapter-react useWallet()
interface SolanaWallet {
publicKey: { toString(): string } | null;
signTransaction: ((tx: unknown) => Promise<unknown>) | null;
}
// EVM — pass address + signTypedData from wagmi
interface EvmWallet {
address: string;
signTypedData: (params: {
domain: Record<string, unknown>;
types: Record<string, unknown[]>;
message: Record<string, unknown>;
primaryType: string;
}) => Promise<string>;
}React hook wrapping createX402Client with state management.
Config — same as createX402Client (see above).
Returns:
| Property | Type | Description |
|---|---|---|
fetch |
(input, init?) => Promise<Response> |
Payment-aware fetch |
isLoading |
boolean |
Payment in progress |
status |
'idle' | 'pending' | 'success' | 'error' |
Current state |
error |
Error | null |
Error details on failure |
transactionId |
string | null |
Tx hash/signature on success |
transactionNetwork |
RelaiNetwork | null |
Network used for payment |
transactionNetworkLabel |
string | null |
Human-readable label (e.g. "Base") |
transactionUrl |
string | null |
Block explorer link |
connectedChains |
{ solana: boolean, evm: boolean } |
Which wallets are connected |
isConnected |
boolean |
Any wallet connected |
reset |
() => void |
Reset state to idle |
import Relai from '@relai-fi/x402/server';
const relai = new Relai({
network: 'base', // or 'solana', 'avalanche', 'skale-base'
});
// Protect any Express route with micropayments
app.get('/api/data', relai.protect({
payTo: '0xYourWallet',
price: 0.01, // $0.01 USDC
description: 'Premium data access',
}), (req, res) => {
// req.payment = { verified, transactionId, payer, network, amount }
res.json({ data: 'Protected content', payment: req.payment });
});
// Dynamic pricing
app.get('/api/premium', relai.protect({
payTo: '0xYourWallet',
price: (req) => req.query.tier === 'pro' ? 0.10 : 0.01,
}), handler);
// Per-endpoint network override
app.get('/api/solana-data', relai.protect({
payTo: 'SolanaWalletAddress',
price: 0.005,
network: 'solana', // overrides the default 'base'
}), handler);Flow:
- Request without payment → 402 with
acceptsarray - Client signs payment (SDK handles this) → retries with
X-PAYMENTheader - Server calls RelAI facilitator
/settle→ gas sponsored by RelAI - Settlement success →
PAYMENT-RESPONSEheader set,req.paymentpopulated,next()called
req.payment fields:
| Field | Type | Description |
|---|---|---|
verified |
boolean |
Always true after settlement |
transactionId |
string |
On-chain transaction hash |
payer |
string |
Payer wallet address |
network |
string |
Network name (e.g., base) |
amount |
number |
Price in USD |
import { toAtomicUnits, fromAtomicUnits } from '@relai-fi/x402/utils';
toAtomicUnits(0.05, 6); // '50000' ($0.05 USDC)
toAtomicUnits(1.50, 6); // '1500000' ($1.50 USDC)
fromAtomicUnits('50000', 6); // 0.05
fromAtomicUnits('1500000', 6); // 1.5import { convertV1ToV2, convertV2ToV1, networkV1ToV2 } from '@relai-fi/x402/utils';
networkV1ToV2('solana'); // 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'
networkV1ToV2('base'); // 'eip155:8453'
networkV1ToV2('avalanche'); // 'eip155:43114'
networkV1ToV2('skale-base'); // 'eip155:1187947933'
const v2Payload = convertV1ToV2(v1Payload);
const v1Payload = convertV2ToV1(v2Payload);Client Server Facilitator
| | |
|── GET /api/data ──────────>| |
|<── 402 Payment Required ───| |
| (accepts: network, amount, asset) |
| | |
| SDK signs payment | |
| (EIP-3009/SPL) | |
| | |
|── GET /api/data ──────────>| |
| X-PAYMENT: <signed> |── settle ─────────────────>|
| |<── tx hash ────────────────|
|<── 200 OK + data ─────────| |
| PAYMENT-RESPONSE: <tx> | |
npm run build # Build ESM + CJS bundles
npm run dev # Watch mode
npm run type-check # TypeScript checks
npm test # Run testsMIT