SDK for the x402 HTTP Payment Protocol — monetize APIs with crypto payments.
x402 uses HTTP status code 402 Payment Required to enable pay-per-request APIs:
- No accounts needed — just sign and pay
- Instant settlement — ~300ms on Fast, ~15s on EVM
- Multi-chain — Fast plus Arbitrum, Base, and Ethereum variants
- Auto-bridge — Fast → EVM USDC when needed
| Package | Description | Install |
|---|---|---|
| @fastxyz/x402-client | Pay for 402-protected content | npm i @fastxyz/x402-client |
| @fastxyz/x402-server | Protect routes with payments | npm i @fastxyz/x402-server |
| @fastxyz/x402-facilitator | Verify signatures, settle on-chain | npm i @fastxyz/x402-facilitator |
import { x402Pay } from '@fastxyz/x402-client';
const result = await x402Pay({
url: 'https://api.example.com/premium',
wallet: {
type: 'evm',
privateKey: '0x...',
address: '0x...',
},
});
console.log(result.body); // Your paid contentimport express from 'express';
import { paymentMiddleware } from '@fastxyz/x402-server';
const app = express();
app.use(paymentMiddleware(
'0xYourAddress...',
{
'GET /api/premium/*': { price: '$0.10', network: 'base' },
},
{ url: 'http://localhost:4020' }
));
app.get('/api/premium/data', (req, res) => {
res.json({ data: 'Premium content!' });
});
app.listen(3000);import express from 'express';
import { createFacilitatorServer } from '@fastxyz/x402-facilitator';
const app = express();
app.use(express.json());
app.use(createFacilitatorServer({
evmPrivateKey: process.env.FACILITATOR_KEY as `0x${string}`,
}));
app.listen(4020);Uses EIP-3009 transferWithAuthorization — client signs, facilitator settles on-chain.
Client Server Facilitator
│ │ │
│ Sign EIP-3009 authorization │ │
│──────────────────────────────>│ /verify │
│ │────────────────────────────────>│
│ │ { isValid: true } │
│ │<────────────────────────────────│
│ │ /settle │
│ │────────────────────────────────>│
│ │ { txHash: 0x... } │
│ 200 OK + content │<────────────────────────────────│
│<──────────────────────────────│ │
Client submits transaction directly, sends certificate as proof. No settlement step needed.
Client Server Facilitator
│ │ │
│ Submit tx to Fast network │ │
│ (already on-chain!) │ │
│──────────────────────────────>│ /verify │
│ │────────────────────────────────>│
│ │ { isValid: true } │
│ 200 OK + content │<────────────────────────────────│
│<──────────────────────────────│ │
const result = await x402Pay({
url: 'https://api.example.com/data',
wallet: {
type: 'evm',
privateKey: '0x...',
address: '0x...',
},
});const result = await x402Pay({
url: 'https://api.example.com/data',
wallet: {
type: 'fast',
privateKey: '...', // 32-byte Ed25519 key (hex, no 0x)
publicKey: '...', // 32-byte pubkey (hex)
address: 'fast1...', // bech32m address
},
});Provide both wallets to automatically bridge from Fast to EVM USDC when paying EVM endpoints:
const result = await x402Pay({
url: 'https://api.example.com/data', // EVM endpoint
wallet: [
{
type: 'fast',
privateKey: '...',
publicKey: '...',
address: 'fast1...',
},
{
type: 'evm',
privateKey: '0x...',
address: '0x...',
},
],
verbose: true, // See bridge progress
});
// Flow:
// 1. Detects EVM endpoint requires USDC
// 2. Checks EVM wallet balance → insufficient
// 3. Bridges Fast → EVM USDC via AllSet (~3-4s)
// 4. Signs EIP-3009 authorization
// 5. Sends payment → 200 OKinterface X402PayParams {
url: string; // URL to pay for
wallet: Wallet | Wallet[]; // Wallet(s) to use
method?: string; // HTTP method (default: 'GET')
headers?: Record<string, string>;
body?: string;
verbose?: boolean; // Log progress
}interface X402PayResult {
success: boolean;
statusCode: number;
headers: Record<string, string>;
body: unknown;
payment?: {
network: string;
amount: string;
txHash?: string;
bridged?: boolean;
bridgeTxHash?: string;
};
note: string;
logs?: string[]; // If verbose: true
}app.use(paymentMiddleware(
'0xYourAddress...', // EVM or fast1...
routes,
{ url: 'http://localhost:4020' }
));Accept payments on both EVM and Fast:
app.use(paymentMiddleware(
{
evm: '0xYourEvmAddress...',
fast: 'fast1YourFastAddress...',
},
{
'GET /api/evm/*': { price: '$0.10', network: 'base' },
'GET /api/fast/*': { price: '$0.01', network: 'fast-testnet' },
},
{ url: 'http://localhost:4020' }
));const routes = {
// Exact match
'GET /api/premium': { price: '$0.10', network: 'base' },
// Wildcard
'GET /api/premium/*': { price: '$0.05', network: 'fast-testnet' },
// POST with higher price
'POST /api/generate': { price: '$1.00', network: 'arbitrum' },
};interface RouteConfig {
price: string; // '$0.10', '0.1', or '100000' (raw units)
network: string; // Network identifier
config?: {
description?: string; // Human-readable description
mimeType?: string; // Response MIME type hint
asset?: string; // Custom token address (default: USDC)
};
}All of these are equivalent ($0.10 USDC):
{ price: '$0.10' } // Dollar notation
{ price: '0.1' } // Decimal USDC
{ price: '100000' } // Raw units (6 decimals)import express from 'express';
import { createFacilitatorServer } from '@fastxyz/x402-facilitator';
const app = express();
app.use(express.json());
app.use(createFacilitatorServer({
evmPrivateKey: process.env.FACILITATOR_KEY as `0x${string}`,
}));
app.listen(4020);import { verify, settle } from '@fastxyz/x402-facilitator';
// Verify a payment
const verifyResult = await verify(paymentPayload, paymentRequirement);
if (!verifyResult.isValid) {
console.error('Invalid:', verifyResult.invalidReason);
}
// Settle an EVM payment
const settleResult = await settle(paymentPayload, paymentRequirement, {
evmPrivateKey: '0x...',
});
console.log('Settled:', settleResult.txHash);| Endpoint | Method | Description |
|---|---|---|
/verify |
POST | Verify payment signature/certificate |
/settle |
POST | Settle payment on-chain (EVM only) |
/supported |
GET | List supported payment kinds |
The facilitator wallet needs:
- ETH for gas on each supported EVM chain
- No USDC needed (it calls
transferWithAuthorization, nottransfer)
| Network | Type | Chain ID | Token | Settlement |
|---|---|---|---|---|
fast-mainnet |
Fast | — | USDC | ~300ms |
arbitrum |
EVM | 42161 | USDC | ~15s |
base |
EVM | 8453 | USDC | ~15s |
| Network | Type | Chain ID | Token | Settlement |
|---|---|---|---|---|
fast-testnet |
Fast | — | testUSDC | ~300ms |
ethereum-sepolia |
EVM | 11155111 | USDC | ~15s |
arbitrum-sepolia |
EVM | 421614 | USDC | ~15s |
Network and token configurations are imported from the canonical Fast SDKs:
- @fastxyz/allset-sdk — Bridge configs, USDC addresses, Fast token IDs
- @fastxyz/sdk — Fast RPC endpoints, transaction encoding
EIP-3009 metadata (usdcName, usdcVersion) is maintained locally in x402-sdk.
| Error | Cause | Fix |
|---|---|---|
INSUFFICIENT_BALANCE |
Not enough USDC | Check balance, or use auto-bridge |
INVALID_SIGNATURE |
Key mismatch | Verify private key matches address |
BRIDGE_TIMEOUT |
Bridge took too long | Retry, check Fast wallet balance |
| Error | Cause | Fix |
|---|---|---|
Payment verification failed |
Bad signature/certificate | Check facilitator logs |
Settlement failed |
On-chain issue | Check facilitator has gas |
| Error | Cause | Fix |
|---|---|---|
authorization_already_used |
Nonce reused | Client must use fresh nonce |
insufficient_funds |
Payer has no USDC | Client needs to fund wallet |
facilitator_not_configured |
Missing private key | Set evmPrivateKey in config |
const result = await x402Pay({
url: '...',
wallet: myWallet,
verbose: true, // Logs each step
});
result.logs?.forEach(console.log);npm install
npm run build --workspaces
npm test --workspaces- SKILL.md — Workflow-oriented guide for AI agents
- RELEASING.md — npm release workflow
MIT