@primersystems/x402
v0.7.0
Published
TypeScript SDK for x402 payments - pay and charge for APIs with stablecoins. OpenClaw compatible.
Maintainers
Readme
@primersystems/x402
TypeScript SDK for x402 HTTP payments by Primer. OpenClaw compatible 🦞
Easily add pay-per-request monetization to your JavaScript/TypeScript APIs using the x402 protocol. Accept stablecoin payments (USDC, EURC) or any ERC-20 token with gasless transactions—payers never pay gas fees.
Quick Start (CLI)
# Create a new wallet
npx @primersystems/x402 wallet create
# Check balance
npx @primersystems/x402 wallet balance 0xYourAddress
# Probe a URL for x402 support
npx @primersystems/x402 probe https://api.example.com/paid
# Set up for OpenClaw
npx @primersystems/x402 openclaw initWhy x402?
- HTTP-native payments - Uses the standard HTTP 402 Payment Required status code
- Gasless for payers - Payments are authorized via EIP-712 signatures; facilitators handle gas
- Stablecoin support - Native support for USDC/EURC via EIP-3009
transferWithAuthorization - Any ERC-20 token - Support for other tokens via Primer's Prism settlement contract
- Multi-chain - Base, Ethereum, Arbitrum, Optimism, Polygon (mainnet + testnet)
- Framework integrations - Express, Hono, Next.js middleware included
- Testing utilities - Mock facilitator for integration testing
Installation
npm install @primersystems/x402Payer (Client)
Wrap fetch or axios to automatically handle 402 responses:
const { createSigner, x402Fetch, x402Axios } = require('@primersystems/x402');
// Create a signer (use CAIP-2 network format)
const signer = await createSigner('eip155:8453', process.env.PRIVATE_KEY);
// Wrap fetch
const fetch402 = x402Fetch(fetch, signer, { maxAmount: '1.00' });
const response = await fetch402('https://example.com/api/paywall');
// Or wrap axios
const axios402 = x402Axios(axios.create(), signer, { maxAmount: '1.00' });
const response = await axios402.get('https://example.com/api/paywall');Options
| Option | Required | Description |
|--------|----------|-------------|
| maxAmount | Yes | Maximum payment per request (e.g., '1.00') |
| facilitator | No | Custom facilitator URL |
| verify | No | Verify payment before sending (default: true) |
Payee (Server)
Middleware for Express, Hono, and Next.js:
Express
const { x402Express } = require('@primersystems/x402');
app.use(x402Express('0xYourAddress', {
'/api/paywall': {
amount: '0.01',
asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
network: 'eip155:8453' // CAIP-2 format
},
'/api/data/*': {
amount: '0.001',
asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
network: 'eip155:8453' // CAIP-2 format
}
}));Hono
const { x402Hono } = require('@primersystems/x402');
app.use('*', x402Hono('0xYourAddress', {
'/api/paywall': { amount: '0.01', asset: '0x...', network: 'eip155:8453' }
}));Next.js
// App Router (Next.js 13+)
import { x402Next } from '@primersystems/x402';
async function handler(req) {
return Response.json({ data: 'paywall' });
}
export const GET = x402Next(handler, {
payTo: '0xYourAddress',
amount: '0.01',
asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
network: 'eip155:8453' // CAIP-2 format
});Token Types
EIP-3009 Tokens (USDC, EURC)
These tokens support gasless transfers natively via transferWithAuthorization. The payer signs an authorization, and the facilitator executes the transfer—payer pays zero gas.
Standard ERC-20 Tokens
For other ERC-20 tokens, Primer's Prism contract enables gasless payments:
- One-time approval - Approve the Prism contract to spend your tokens
- Gasless payments - Sign authorizations; Prism handles the transfers
const { createSigner, approveToken } = require('@primersystems/x402');
const signer = await createSigner('eip155:8453', process.env.PRIVATE_KEY);
// One-time approval (this transaction requires gas)
await approveToken(signer, '0xTokenAddress');
// Now you can make gasless payments with this tokenNetworks
Networks use CAIP-2 identifiers (e.g., eip155:8453 for Base).
| Network (CAIP-2) | Chain ID | Legacy Name | Default Facilitator | |------------------|----------|-------------|---------------------| | eip155:8453 | 8453 | base | ✓ Primer | | eip155:84532 | 84532 | base-sepolia | ✓ Primer | | eip155:1 | 1 | ethereum | Custom required | | eip155:11155111 | 11155111 | sepolia | Custom required | | eip155:42161 | 42161 | arbitrum | Custom required | | eip155:421614 | 421614 | arbitrum-sepolia | Custom required | | eip155:10 | 10 | optimism | Custom required | | eip155:11155420 | 11155420 | optimism-sepolia | Custom required | | eip155:137 | 137 | polygon | Custom required | | eip155:80002 | 80002 | polygon-amoy | Custom required | | eip155:1187947933 | 1187947933 | skale-base | ✓ Primer | | eip155:324705682 | 324705682 | skale-base-sepolia | ✓ Primer |
Note: Legacy network names (e.g.,
'base') are still accepted for backward compatibility but CAIP-2 format is recommended.
Custom Facilitator
For non-Base networks, you must provide your own facilitator:
// Payee
app.use(x402Express('0xYourAddress', {
'/api/paywall': { amount: '0.01', asset: '0x...', network: 'eip155:1' }
}, { facilitator: 'https://your-facilitator.com' }));
// Payer
const fetch402 = x402Fetch(fetch, signer, {
maxAmount: '1.00',
facilitator: 'https://your-facilitator.com'
});Debug Logging
DEBUG=x402:* node app.js
DEBUG=x402:payer,x402:payee node app.jsTesting Your Integration
The SDK provides testing utilities to help you test your x402 integration without making real payments. Import from @primersystems/x402/testing:
const {
createMockFacilitator,
createTestPayment,
createTest402Response,
fixtures
} = require('@primersystems/x402/testing');What's Provided
| Utility | Purpose |
|---------|---------|
| createMockFacilitator() | Fake payment server that approves/rejects without blockchain |
| createTestPayment() | Generate valid X-PAYMENT headers without a real wallet |
| createTest402Response() | Generate 402 responses for testing client code |
| fixtures | Pre-built test addresses, route configs, and sample payloads |
Testing a Payee (Server)
If you've built an API with x402 payments, test it like this:
// my-api.test.js
const request = require('supertest');
const app = require('./my-app');
const { createMockFacilitator, createTestPayment } = require('@primersystems/x402/testing');
describe('My Paid API', () => {
let mockFac;
beforeAll(async () => {
// Start a fake facilitator on port 3001
mockFac = await createMockFacilitator({ port: 3001 });
});
afterAll(async () => {
await mockFac.close();
});
test('returns 402 when no payment provided', async () => {
const res = await request(app).get('/api/premium');
expect(res.status).toBe(402);
expect(res.headers['payment-required']).toBeDefined();
});
test('returns 200 when valid payment provided', async () => {
const payment = createTestPayment({ amount: '10000' }); // 0.01 USDC
const res = await request(app)
.get('/api/premium')
.set('X-PAYMENT', payment);
expect(res.status).toBe(200);
});
test('rejects insufficient payment', async () => {
const payment = createTestPayment({ amount: '1' }); // way too little
const res = await request(app)
.get('/api/premium')
.set('X-PAYMENT', payment);
expect(res.status).toBe(402);
});
});Important: Point your middleware at the mock facilitator during tests:
// In your app setup for tests
const middleware = x402Express(payTo, routes, {
facilitator: 'http://127.0.0.1:3001' // Mock facilitator URL
});Testing a Payer (Client)
If you've built a client that pays for APIs, test the 402 handling:
const { createTest402Response, fixtures } = require('@primersystems/x402/testing');
test('client handles 402 response', async () => {
// Create a mock server that returns 402
const server = setupMockServer((req, res) => {
const body = createTest402Response({
amount: '10000',
payTo: fixtures.TEST_ADDRESSES.payee,
resource: '/api/data'
});
res.status(402)
.set('PAYMENT-REQUIRED', Buffer.from(JSON.stringify(body)).toString('base64'))
.end();
});
// Test your client handles it correctly
const result = await myClient.fetchWithPayment('/api/data');
expect(result).toBeDefined();
});Mock Facilitator Options
// Auto-approve all payments (default)
const mock = await createMockFacilitator({ mode: 'approve' });
// Reject all payments
const mock = await createMockFacilitator({ mode: 'reject' });
// Custom logic
const mock = await createMockFacilitator({
mode: 'custom',
handler: (payload) => {
const amount = payload.paymentRequirements?.maxAmountRequired;
if (parseInt(amount) > 1000000) {
return { success: false, error: 'Amount too high for test' };
}
return { success: true, transaction: '0x' + 'f'.repeat(64) };
}
});
// Add artificial latency (for timeout testing)
const mock = await createMockFacilitator({ latencyMs: 5000 });Inspecting Requests
The mock facilitator records all requests for assertions:
const mock = await createMockFacilitator();
// ... run your test ...
// Check what was sent to the facilitator
expect(mock.requests.length).toBe(1);
expect(mock.lastRequest().payload.paymentRequirements.network).toBe('eip155:8453');
// Clear between tests
mock.clearRequests();createTestPayment Options
createTestPayment({
amount: '10000', // Amount in smallest units (default: '10000')
from: '0x...', // Payer address (default: test address)
to: '0x...', // Payee address (default: test address)
network: 'eip155:84532', // Network in CAIP-2 format (default: 'eip155:8453')
validForSeconds: 7200 // Validity window (default: 3600)
});Available Fixtures
const { fixtures } = require('@primersystems/x402/testing');
fixtures.TEST_ADDRESSES.payer // '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
fixtures.TEST_ADDRESSES.payee // '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'
fixtures.USDC_ADDRESSES['eip155:8453'] // USDC on Base mainnet
fixtures.USDC_ADDRESSES['eip155:84532'] // USDC on Base Sepolia
fixtures.sampleRouteConfig // Example route config for middleware
fixtures.sample402ResponseBody // Example 402 response structure
fixtures.samplePaymentPayload // Example payment payload structureCLI Reference
The SDK includes a command-line interface for wallet management, probing, and OpenClaw integration.
npx @primersystems/x402 <command> [options]Commands
| Command | Description |
|---------|-------------|
| wallet create | Create a new wallet (address, private key, mnemonic) |
| wallet balance <address> | Check USDC/ETH balance |
| wallet from-mnemonic | Restore wallet from mnemonic phrase |
| probe <url> | Check if URL supports x402 payments |
| pay <url> | Make a payment to a 402 endpoint |
| pay <url> --dry-run | Preview payment without paying |
| create <template> [dir] | Scaffold a project from template |
| networks | List supported networks |
| facilitator | Show facilitator info |
| openclaw init | Set up x402 for OpenClaw agents |
| openclaw status | Check OpenClaw x402 status |
Environment Variables
| Variable | Description |
|----------|-------------|
| X402_PRIVATE_KEY | Wallet private key |
| X402_NETWORK | Default network (default: base) |
| X402_MAX_AMOUNT | Default max payment amount |
| X402_FACILITATOR | Facilitator URL override |
Examples
# Create wallet and save output
npx @primersystems/x402 wallet create --json > wallet.json
# Check balance on Arbitrum
npx @primersystems/x402 wallet balance 0x... --network arbitrum
# Preview payment (dry run)
npx @primersystems/x402 pay https://api.example.com/data --dry-run
# Pay for an API
X402_PRIVATE_KEY=0x... npx @primersystems/x402 pay https://api.example.com/data --max-amount 0.10Wallet Utilities
New in v0.5.0 - programmatic wallet management:
const {
createWallet,
walletFromMnemonic,
getBalance,
x402Probe
} = require('@primersystems/x402');
// Create a new wallet
const wallet = createWallet();
console.log(wallet.address); // 0x...
console.log(wallet.privateKey); // 0x...
console.log(wallet.mnemonic); // "word1 word2 ..."
// Restore from mnemonic
const restored = walletFromMnemonic('word1 word2 ...');
// Check balance
const balance = await getBalance('0x...', 'base', 'USDC');
console.log(balance.balance); // "100.50"
// Probe a URL for x402 support
const probe = await x402Probe('https://api.example.com/paid');
if (probe.supports402) {
console.log('Payment required:', probe.requirements);
}Error Handling
The SDK provides structured errors for programmatic handling:
const { X402Error, ErrorCodes } = require('@primersystems/x402');
try {
const response = await x402Fetch(url, signer, { maxAmount: '0.10' });
} catch (error) {
if (error instanceof X402Error) {
switch (error.code) {
case ErrorCodes.INSUFFICIENT_FUNDS:
console.log('Need more funds:', error.details);
break;
case ErrorCodes.AMOUNT_EXCEEDS_MAX:
console.log('Payment too expensive:', error.details);
break;
case ErrorCodes.SETTLEMENT_FAILED:
console.log('Settlement failed:', error.details);
break;
}
}
}Error Codes
| Code | Description |
|------|-------------|
| INVALID_CONFIG | Missing or invalid configuration |
| MISSING_PRIVATE_KEY | Private key not provided |
| UNSUPPORTED_NETWORK | Network not supported |
| INSUFFICIENT_FUNDS | Wallet balance too low |
| AMOUNT_EXCEEDS_MAX | Payment exceeds maxAmount |
| PAYMENT_FAILED | Payment transaction failed |
| SETTLEMENT_FAILED | Facilitator settlement failed |
| INVALID_RESPONSE | Malformed 402 response |
| MISSING_PAYMENT_HEADER | No payment requirements header |
OpenClaw Integration
This SDK is compatible with OpenClaw (formerly Moltbot/Clawdbot) AI agents.
Quick Setup
npx @primersystems/x402 openclaw initThis will:
- Create a new wallet (or use existing)
- Configure network settings
- Install the skill to
~/.openclaw/skills/primer-x402/
Manual Installation
Install the skill from ClawHub:
clawhub install primer/x402Or via npm and then init:
npm install -g @primersystems/x402
x402 openclaw initBittensor Integration
The SDK includes templates for integrating with Bittensor subnets, enabling pay-per-inference access to decentralized AI.
Chutes Proxy
Scaffold a pay-per-inference proxy for Chutes AI (Bittensor Subnet 64):
npx @primersystems/x402 create chutes-proxy my-ai-proxy
cd my-ai-proxy
npm installThis creates a ready-to-deploy proxy that:
- Accepts USDC payments on Base via x402
- Forwards requests to Chutes API for AI inference
- Supports all Chutes models (DeepSeek, Qwen, Llama, etc.)
- Deploys free on Cloudflare Workers
Configure and run:
cp .env.example .env
# Edit .env with your Chutes API key and wallet address
npm run devDeploy to Cloudflare Workers:
wrangler login
wrangler secret put CHUTES_API_KEY
wrangler secret put WALLET_ADDRESS
npm run deploySee the generated README.md for full documentation.
Why Bittensor + x402?
- No TAO required: Users pay with USDC instead of needing Bittensor tokens
- No subscriptions: True pay-per-request micropayments
- Decentralized AI: Access Bittensor's distributed inference network
- Your margin: Set your own pricing above Chutes' cost
Changelog
v0.7.0
- Bittensor integration: New
x402 create chutes-proxycommand to scaffold a pay-per-inference proxy for Bittensor Chutes AI - Project scaffolding: CLI now supports project templates via
x402 create <template> - Deploy to Cloudflare Workers (free tier) or any Node.js host
v0.6.0
- SKALE network support: Added SKALE Base (
eip155:1187947933) and SKALE Base Sepolia (eip155:324705682) networks - Gas-free transactions on SKALE with Primer facilitator support
v0.5.0
- CLI: New command-line interface (
npx @primersystems/x402 ...) - Wallet utilities:
createWallet(),walletFromMnemonic(),getBalance(),x402Probe() - Structured errors:
X402Errorclass with error codes for programmatic handling - OpenClaw integration:
openclaw initandopenclaw statuscommands - Environment variables: Support for
X402_PRIVATE_KEY,X402_NETWORK,X402_MAX_AMOUNT,X402_FACILITATOR
v0.4.3
- Cleaned up legacy v1 protocol remnants for pure v2 compliance
v0.4.2
- Bug fix: 402 Payment Required responses now include JSON body (
{}) instead of empty body - Fixes Chrome content script injection blocking issue
- Aligns with Coinbase x402 specification
v0.4.0
- x402 v2 protocol: Full upgrade to x402 v2 specification with
x402Version: 2 - CAIP-2 network identifiers: All networks now use CAIP-2 format (e.g.,
'eip155:8453'instead of'base') - Multi-chain support: Base, Ethereum, Arbitrum, Optimism, and Polygon (mainnets + testnets)
- Network utilities: New functions
toCaipNetwork(),fromCaipNetwork(),chainIdToCaip(),caipToChainId() - Facilitator validation: SDK requires custom facilitator for non-Base networks
- Legacy compatibility: Legacy network names still accepted as input but CAIP-2 is used internally
- Testing fixtures: Updated to v2 format with CAIP-2 network identifiers
v0.3.0
- Testing utilities: New
@primersystems/x402/testingmodule with mock facilitator, test payment generator, and fixtures - Internal test suite: 112 tests covering utils, signer, payer, payee, and testing modules
- CI pipeline: GitHub Actions runs tests on all pushes and PRs
v0.2.0
- Initial public release
- Payer:
createSigner,x402Fetch,x402Axios - Payee:
x402Express,x402Hono,x402Nextmiddleware - Support for Base mainnet and Base Sepolia
- EIP-3009 (USDC/EURC) and standard ERC-20 tokens
Links
License
MIT - Primer Systems
