One-liner paywalls and auto-paying fetch for the x402 protocol. Supports Base (EVM) and Stellar (Soroban) with built-in budget controls for AI agents.
// Server — one line to paywall any route
app.get('/api/premium', paywall({ price: '0.01', payTo: '0x...', network: 'base' }), handler)
// Client — auto-pays 402 responses transparently
const res = await client.fetch('https://api.example.com/premium')- Install
- Quick Start
- Server Middleware
- Client
- How Payment Validation Works
- Supported Networks
- Budget System
- Dynamic Pricing
- Cart / E-commerce Example
- Subscription with Wallet Auth
- USDC Amount Handling
- API Reference
- Optional Dependencies
npm install @402md/x402Chain SDKs are optional — install only what you need:
# For EVM networks (Base, Base Sepolia)
npm install viem
# For Stellar networks (Stellar, Stellar Testnet)
npm install @stellar/stellar-sdkIf you try to use a network without its SDK installed, you get a clear error:
Error: viem is required for EVM networks. Install it: npm install viem
import express from 'express'
import { paywall } from '@402md/x402'
const app = express()
app.get(
'/api/weather',
paywall({
price: '0.001', // $0.001 USDC per request
payTo: '0xYourAddress', // your wallet
network: 'base' // Base mainnet
}),
(req, res) => {
// req.x402.payment contains { settled, txHash, payer, network }
res.json({ temperature: 22, unit: 'celsius' })
}
)
app.listen(3000)import { createPaymentClient } from '@402md/x402'
const client = await createPaymentClient({
evmPrivateKey: process.env.PRIVATE_KEY,
network: 'base',
budget: { maxPerCall: '0.01', maxPerDay: '1.00' }
})
const res = await client.fetch('https://api.example.com/api/weather')
const data = await res.json()
console.log(data) // { temperature: 22, unit: 'celsius' }All middleware functions accept a PaywallConfig object:
interface PaywallConfig {
price: string // USDC amount, e.g. '0.001'
payTo: string // recipient address (EVM or Stellar)
network: PaymentNetwork // 'base' | 'base-sepolia' | 'stellar' | 'stellar-testnet'
facilitatorUrl?: string // override default facilitator
description?: string // human-readable resource description
maxTimeoutSeconds?: number // payment validity window (default: 300)
onPayment?: (payment: VerifiedPayment) => void // post-payment callback
}import express from 'express'
import { paywall } from '@402md/x402'
const app = express()
// Simple — one config object
app.get(
'/api/data',
paywall({ price: '0.01', payTo: '0xABC...', network: 'base' }),
(req, res) => {
// Payment verified and settled. Access details:
const { settled, txHash, payer, network } = req.x402.payment
res.json({ data: 'premium content', payer })
}
)
// With callback — log every payment
app.post(
'/api/generate',
paywall({
price: '0.05',
payTo: '0xABC...',
network: 'base',
description: 'AI text generation',
onPayment: (payment) => {
console.log(`Received payment from ${payment.payer}: ${payment.txHash}`)
}
}),
(req, res) => {
res.json({ text: 'generated content' })
}
)
// Testnet — same API, just change network
app.get(
'/api/test',
paywall({ price: '0.001', payTo: '0xABC...', network: 'base-sepolia' }),
handler
)Works with any Express-compatible framework (Connect, Polka, etc.).
import { Hono } from 'hono'
import { paywallHono, getPayment } from '@402md/x402'
const app = new Hono()
app.get(
'/api/data',
paywallHono({ price: '0.01', payTo: '0xABC...', network: 'base' }),
(c) => {
const payment = getPayment(c)
return c.json({ data: 'premium content', payer: payment?.payer })
}
)
export default appgetPayment(c) is a typed helper that retrieves the VerifiedPayment from Hono's context.
// app/api/premium/route.ts
import { paywallNextjs } from '@402md/x402'
export const GET = paywallNextjs(
{ price: '0.01', payTo: '0xABC...', network: 'base' },
async (req) => {
// req.x402.payment is available here
return Response.json({ data: 'premium content' })
}
)
export const POST = paywallNextjs(
{ price: '0.05', payTo: '0xABC...', network: 'base' },
async (req) => {
const body = await req.json()
return Response.json({ result: 'processed', input: body })
}
)Wraps your route handler — if no valid payment is present, returns 402 before your handler runs.
Best for agents or services that make multiple paid requests:
import { createPaymentClient } from '@402md/x402'
const client = await createPaymentClient({
evmPrivateKey: process.env.PRIVATE_KEY,
network: 'base',
budget: {
maxPerCall: '0.10',
maxPerDay: '5.00',
maxPerSession: '2.00'
}
})
// Auto-paying fetch — handles 402 transparently
const res = await client.fetch('https://api.example.com/premium')
// Check balance
const balance = await client.getBalance() // '42.50'
// Get wallet address
const address = client.getAddress() // '0x...'
// Manual payment (advanced)
const token = await client.pay(paymentRequirement)What client.fetch() does internally:
- Makes the HTTP request normally
- If server returns
200— returns the response as-is - If server returns
402— parses the payment requirements, checks budget, signs payment, retries withX-PAYMENTheader - Returns the paid response
For single requests where you don't need to reuse the client:
import { x402Fetch } from '@402md/x402'
const res = await x402Fetch('https://api.example.com/premium', {
method: 'POST',
body: JSON.stringify({ prompt: 'hello' }),
headers: { 'Content-Type': 'application/json' },
paymentConfig: {
evmPrivateKey: process.env.PRIVATE_KEY,
network: 'base',
budget: { maxPerCall: '0.10' }
}
})Pass skipPayment: true to get the raw 402 response without auto-paying:
const res = await x402Fetch('https://api.example.com/premium', {
skipPayment: true
})
// res.status === 402The x402 protocol uses HTTP status 402 Payment Required to create a challenge-response payment flow. Every payment is gasless for the payer — the facilitator pays on-chain gas fees.
Agent Server Facilitator
| | |
| GET /api/data | |
|----------------------------->| |
| | |
| 402 + PaymentRequired JSON | |
|<-----------------------------| |
| | |
| [sign authorization] | |
| | |
| GET /api/data | |
| X-PAYMENT: <base64 token> | |
|----------------------------->| |
| | POST /verify |
| | {paymentPayload, reqs} |
| |----------------------------->|
| | { isValid: true } |
| |<-----------------------------|
| | |
| | POST /settle |
| | {paymentPayload, reqs} |
| |----------------------------->|
| | { success, txHash } |
| |<-----------------------------|
| | |
| 200 + response data | |
|<-----------------------------| |
When a request arrives without a valid X-PAYMENT header, the middleware returns:
{
"x402Version": 2,
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "10000",
"payTo": "0xRecipientAddress",
"maxTimeoutSeconds": 300,
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"extra": {
"facilitator": "https://facilitator.x402.org",
"name": "USD Coin",
"version": "2"
}
}
],
"resource": {
"url": "/api/data",
"description": "Premium data access",
"mimeType": "application/json"
}
}Key fields:
amount— USDC in atomic units (6 decimals)."10000"= $0.01network— CAIP-2 chain identifierasset— USDC contract address on that networkextra.facilitator— URL of the service that will verify and settle
The client signs a gasless authorization (not a transaction). The signing mechanism differs by chain:
- EVM: EIP-712
TransferWithAuthorization(ERC-3009) - Stellar: Soroban
SorobanAuthorizationEntry
The signed proof is base64-encoded and sent as the X-PAYMENT header.
The server middleware:
- Decodes the
X-PAYMENTheader (base64 → JSON) - Sends the payment payload to the facilitator for verification (
POST /verify) - If valid, sends it for settlement (
POST /settle) - The facilitator submits the on-chain transaction and pays gas
- Returns
{ settled: true, txHash, payer, network }to your handler
On Base networks, the client signs an ERC-3009 TransferWithAuthorization using EIP-712 typed data:
EIP-712 Domain:
name: "USD Coin" (mainnet) / "USDC" (testnet)
version: "2"
chainId: 8453 (mainnet) / 84532 (testnet)
verifyingContract: USDC contract address
Message (TransferWithAuthorization):
from: agent's address
to: payTo (recipient)
value: amount in atomic units
validAfter: 0
validBefore: now + maxTimeoutSeconds
nonce: random 32 bytes (ERC-3009 uses random nonces)
This signature authorizes a USDC transfer without submitting a transaction. The facilitator takes this signature and calls transferWithAuthorization() on the USDC contract, paying the gas itself.
Why this is gasless: The agent only signs data (free). The facilitator submits the on-chain transaction and covers gas fees.
Dependencies: Requires viem for private key management and EIP-712 signing.
On Stellar networks, the client signs a Soroban authorization entry — not a full transaction:
SorobanAuthorizedInvocation:
function: USDC contract "transfer"
args: [from (agent), to (recipient), amount (i128)]
SorobanAuthorizationEntry:
credentials:
address: agent's public key
nonce: random i64 (positive)
signatureExpirationLedger: current ledger + timeout/5
signature: ed25519 signature
rootInvocation: the invocation above
The signed auth entry is serialized to XDR (base64) and sent to the facilitator.
What the facilitator does:
- Receives the signed auth entry
- Builds a Stellar transaction with its own source account (pays gas ~$0.00001)
- Attaches the agent's auth entry to the transaction
- Submits to the Soroban network
Why this is gasless: The agent signs only the authorization to move USDC. The facilitator wraps it in a transaction and pays all fees.
Ledger-based expiration: Stellar doesn't use wall-clock time for auth expiration. Instead, it uses ledger numbers. With ~5 seconds per ledger, maxTimeoutSeconds: 300 translates to ~60 ledgers from the current sequence.
Dependencies: Requires @stellar/stellar-sdk for Keypair management, XDR encoding, and authorizeEntry().
The facilitator is a third-party service that acts as the on-chain settlement layer:
| Network | Facilitator | Operator |
|---|---|---|
| Base, Base Sepolia | https://facilitator.x402.org |
Coinbase |
| Stellar | https://channels.openzeppelin.com/x402 |
OpenZeppelin |
| Stellar Testnet | https://channels.openzeppelin.com/x402/testnet |
OpenZeppelin |
Verify (POST /verify):
- Validates the cryptographic signature
- Checks the authorization hasn't expired
- Checks the payer has sufficient USDC balance
- Returns
{ isValid: true, payer: '0x...' }or{ isValid: false, invalidReason: '...' }
Settle (POST /settle):
- Submits the on-chain transaction (calling
transferWithAuthorizationon EVM or the Soroban USDC contract on Stellar) - Pays all gas fees
- Returns
{ success: true, transaction: '0x...', network: 'eip155:8453' }
You can override the facilitator URL per route:
paywall({
price: '0.01',
payTo: '0x...',
network: 'base',
facilitatorUrl: 'https://my-custom-facilitator.com'
})Or use the FacilitatorClient directly:
import { FacilitatorClient } from '@402md/x402'
const facilitator = new FacilitatorClient('https://facilitator.x402.org')
const verifyResult = await facilitator.verify(paymentPayload, requirements)
const settleResult = await facilitator.settle(paymentPayload, requirements)| Property | Value |
|---|---|
| Network key | 'base' |
| CAIP-2 | eip155:8453 |
| Chain ID | 8453 |
| USDC address | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Facilitator | https://facilitator.x402.org |
| EIP-712 name | USD Coin |
| SDK | viem |
paywall({ price: '0.01', payTo: '0x...', network: 'base' })| Property | Value |
|---|---|
| Network key | 'base-sepolia' |
| CAIP-2 | eip155:84532 |
| Chain ID | 84532 |
| USDC address | 0x036CbD53842c5426634e7929541eC2318f3dCF7e |
| Facilitator | https://facilitator.x402.org |
| EIP-712 name | USDC |
| SDK | viem |
// Use for development and testing — faucet USDC available
paywall({ price: '0.01', payTo: '0x...', network: 'base-sepolia' })| Property | Value |
|---|---|
| Network key | 'stellar' |
| CAIP-2 | stellar:pubnet |
| USDC contract | CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA |
| Facilitator | https://channels.openzeppelin.com/x402 |
| Signing | Soroban auth entry (gasless) |
| SDK | @stellar/stellar-sdk |
paywall({ price: '0.01', payTo: 'GABC...XYZ', network: 'stellar' })| Property | Value |
|---|---|
| Network key | 'stellar-testnet' |
| CAIP-2 | stellar:testnet |
| USDC contract | CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA |
| Facilitator | https://channels.openzeppelin.com/x402/testnet |
| Signing | Soroban auth entry (gasless) |
| SDK | @stellar/stellar-sdk |
paywall({ price: '0.01', payTo: 'GABC...XYZ', network: 'stellar-testnet' })Server and client networks are independent. A server on Base can coexist with a server on Stellar:
app.get('/api/base', paywall({ price: '0.01', payTo: '0x...', network: 'base' }), handler)
app.get('/api/stellar', paywall({ price: '0.01', payTo: 'GABC...', network: 'stellar' }), handler)AI agents need spending limits. The BudgetTracker enforces three types of caps:
const client = await createPaymentClient({
evmPrivateKey: '0x...',
network: 'base',
budget: {
maxPerCall: '0.10', // rejects any single payment > $0.10
maxPerDay: '5.00', // rejects if cumulative daily spend would exceed $5.00
maxPerSession: '2.00' // rejects if cumulative session spend would exceed $2.00
}
})| Limit | Scope | Resets |
|---|---|---|
maxPerCall |
Single payment | Per request |
maxPerDay |
Calendar day (midnight local time) | Daily at 00:00 |
maxPerSession |
Lifetime of this PaymentClient instance |
Never (create a new client) |
All limits are optional. If no budget is configured, there are no spending restrictions.
Budget is checked before signing — if a payment would exceed any limit, an error is thrown and no signature is created. After successful signing, the amount is recorded.
try {
const res = await client.fetch('https://expensive-api.com/data')
} catch (e) {
// "Would exceed daily budget. Spent: $4.50, requested: $1.00, limit: $5.00"
console.error(e.message)
}paywall() already supports dynamic prices — just compute the amount at request time:
import express from 'express'
import { paywall } from '@402md/x402'
const app = express()
app.use(express.json())
app.post('/v1/checkout', async (req, res, next) => {
const cart = await getCart(req.body.cartId)
const shipping = await calculateShipping(req.body.address)
const total = (cart.subtotal + shipping).toFixed(6)
paywall({
price: total,
payTo: '0x...',
network: 'base',
description: `Order: $${total} USDC`
})(req, res, next)
}, async (req, res) => {
const order = await processOrder(req.body)
res.json({ orderId: order.id, status: 'confirmed' })
})The client doesn't need any changes — client.fetch() reads the actual price from the 402 response and pays whatever the server requires. Budget limits still apply.
A full e-commerce flow with free endpoints (search, cart, shipping) and a dynamic-priced checkout:
import express from 'express'
import { paywall } from '@402md/x402'
const app = express()
app.use(express.json())
// Free — product search
app.get('/v1/products', async (req, res) => {
const results = await searchProducts(req.query.q as string)
res.json(results)
})
// Free — shipping quote
app.post('/v1/shipping/quote', async (req, res) => {
const quote = await calculateShipping(req.body.address, req.body.items)
res.json({ shipping: quote.toFixed(6), estimatedDays: 3 })
})
// Dynamic price — checkout
app.post('/v1/orders', async (req, res, next) => {
const { items, shipping, address } = req.body
const subtotal = items.reduce((sum, i) => sum + i.price * i.qty, 0)
const total = (subtotal + shipping).toFixed(6)
paywall({
price: total,
payTo: '0xYourAddress',
network: 'base',
description: `Order: ${items.length} items, $${total} USDC`,
onPayment: (payment) => {
console.log(`Order paid: ${payment.txHash} from ${payment.payer}`)
}
})(req, res, next)
}, async (req, res) => {
const order = await createOrder(req.body, req.x402.payment)
res.json({ orderId: order.id, status: 'confirmed' })
})import { createPaymentClient } from '@402md/x402'
const client = await createPaymentClient({
evmPrivateKey: process.env.PRIVATE_KEY,
network: 'base',
budget: { maxPerCall: '200.00', maxPerDay: '500.00' }
})
// 1. Search products (free)
const products = await client.fetch('https://shop.example.com/v1/products?q=keyboard')
const items = [
{ id: 'kb-01', name: 'Mechanical Keyboard', price: 89.99, qty: 1 },
{ id: 'usbc-01', name: 'USB-C Cable', price: 12.99, qty: 1 }
]
// 2. Get shipping quote (free)
const quoteRes = await client.fetch('https://shop.example.com/v1/shipping/quote', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address: '123 Main St', items })
})
const { shipping } = await quoteRes.json() // "7.500000"
// 3. Checkout (auto-pays $110.48 via x402)
const orderRes = await client.fetch('https://shop.example.com/v1/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items, shipping: 7.50, address: '123 Main St' })
})
const order = await orderRes.json() // { orderId: '...', status: 'confirmed' }The agent never calculates the total — the server computes it, returns a 402 with the exact price, and client.fetch() pays it automatically.
For subscription-based services, combine x402 payment with wallet-signature authentication:
- Subscribe — agent pays via x402, server records the wallet
- Login — agent proves wallet ownership with a signed message
- Access — protected routes check the subscription via
walletAuth()middleware
import express from 'express'
import { paywall, verifyWalletSignature, walletAuth } from '@402md/x402'
const app = express()
app.use(express.json())
// 1. Subscription payment (x402)
app.post('/v1/subscribe',
paywall({
price: '10.00',
payTo: '0xYourAddress',
network: 'base',
description: '30-day subscription'
}),
async (req, res) => {
const { payer } = req.x402.payment
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
await db.subscriptions.upsert({ wallet: payer, expiresAt })
res.json({ subscribedUntil: expiresAt.toISOString(), wallet: payer })
}
)
// 2. Wallet auth login (free)
app.post('/v1/auth', async (req, res) => {
const { message, signature, address } = req.body
const valid = await verifyWalletSignature({ message, signature, address })
if (!valid) return res.status(401).json({ error: 'Invalid signature' })
const sub = await db.subscriptions.findByWallet(address)
if (!sub || sub.expiresAt < new Date())
return res.status(403).json({ error: 'No active subscription' })
const token = signJwt({ wallet: address, exp: sub.expiresAt })
res.json({ token })
})
// 3. Protected route with walletAuth middleware
app.get('/v1/data',
walletAuth({
verifyAccess: async (addr) => {
const sub = await db.subscriptions.findByWallet(addr)
return !!sub && sub.expiresAt > new Date()
}
}),
(req, res) => {
res.json({ data: 'premium content', wallet: req.x402.wallet.address })
}
)import { createPaymentClient } from '@402md/x402'
const client = await createPaymentClient({
evmPrivateKey: process.env.PRIVATE_KEY,
network: 'base',
budget: { maxPerCall: '10.00' }
})
// Step 1: Subscribe (auto-pays $10.00 via x402)
await client.fetch('https://api.example.com/v1/subscribe', { method: 'POST' })
// Step 2: Login with wallet signature
const message = `Login: ${Date.now()}`
const signature = await client.signMessage(message)
const loginRes = await fetch('https://api.example.com/v1/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature, address: client.getAddress() })
})
const { token } = await loginRes.json()
// Step 3: Use protected endpoints with JWT
const data = await fetch('https://api.example.com/v1/data', {
headers: { Authorization: `Bearer ${token}` }
})The walletAuth() middleware can also be used directly (without JWT) by reading the Authorization: WalletAuth <base64> header. The base64 payload is a JSON WalletSignature object:
// Client sends WalletAuth header directly
const sig = await client.signMessage(`Login: ${Date.now()}`)
const payload = btoa(JSON.stringify({
message: `Login: ${Date.now()}`,
signature: sig,
address: client.getAddress()
}))
const res = await fetch('https://api.example.com/v1/data', {
headers: { Authorization: `WalletAuth ${payload}` }
})USDC has 6 decimals. The x402 protocol uses atomic units (integers) internally, but this package lets you use readable decimal strings everywhere:
| You write | Internal atomic value | USDC amount |
|---|---|---|
'1' |
1000000 |
$1.00 |
'0.01' |
10000 |
$0.01 |
'0.001' |
1000 |
$0.001 |
'0.000001' |
1 |
$0.000001 |
Conversion uses string math only — no floating-point arithmetic:
import { usdcToAtomic, atomicToUsdc } from '@402md/x402'
usdcToAtomic('0.01') // '10000'
usdcToAtomic('1') // '1000000'
atomicToUsdc('10000') // '0.01'
atomicToUsdc('1000000') // '1'Express/Connect middleware. Returns a 402 response with payment requirements if no valid X-PAYMENT header is present. On valid payment, sets req.x402.payment and calls next().
Hono middleware. Same behavior as paywall, but uses Hono's context API. Sets x402Payment in context.
Retrieves the verified payment from a Hono context after paywallHono runs.
Next.js App Router wrapper. Returns a route handler that validates payment before calling your handler. Sets req.x402.payment on the request object.
Builds a 402 response body manually. Useful if you need custom middleware logic.
verifyPaymentHeader(header: string, network: PaymentNetwork, facilitatorUrl?: string): Promise<VerifiedPayment>
Verifies and settles a payment header against the facilitator. Throws on failure.
Decodes a base64 X-PAYMENT header into a JSON object.
Verifies a wallet signature. Detects network from address format (0x → EVM via EIP-191, G → Stellar via ed25519). Returns false on invalid signature (never throws).
Express middleware for wallet-signature authentication. Reads Authorization: WalletAuth <base64> header, verifies the signature, and checks access via config.verifyAccess(). Sets req.x402.wallet.address on success. Returns 401 on missing/invalid auth, 403 on access denied.
Converts a USDC decimal string to atomic units (string math, no floats).
Converts atomic USDC units to a decimal string.
Creates a payment client with auto-paying fetch, budget tracking, and balance queries.
One-shot auto-paying fetch. Creates an ephemeral client per request.
Class for tracking spending limits. Used internally by createPaymentClient, but can be used standalone:
import { BudgetTracker } from '@402md/x402'
const budget = new BudgetTracker({ maxPerCall: '0.10', maxPerDay: '5.00' })
budget.check('0.05') // ok
budget.record('0.05') // records the spend
budget.check('4.96') // throws: would exceed daily budgetLow-level client for communicating with x402 facilitator services:
import { FacilitatorClient } from '@402md/x402'
// Auto-resolve URL from network name
const client = new FacilitatorClient('base')
// Or use a custom URL
const custom = new FacilitatorClient('https://my-facilitator.com')
const { isValid, payer } = await client.verify(payload, requirements)
const { success, transaction } = await client.settle(payload, requirements)Returns the chain-specific provider for signing payments and checking balances. Automatically selects Base or Stellar based on the network.
import {
USDC_DECIMALS, // 6
USDC_ADDRESSES, // { base: '0x833...', 'base-sepolia': '0x036...', ... }
CHAIN_IDS, // { base: 8453, 'base-sepolia': 84532, stellar: null, ... }
CAIP2_NETWORKS, // { base: 'eip155:8453', stellar: 'stellar:pubnet', ... }
FACILITATOR_URLS, // { base: 'https://facilitator.x402.org', ... }
X402_SCHEME, // 'exact'
X402_VERSION, // 2
X402_PAYMENT_HEADER, // 'X-PAYMENT'
X402_DEFAULT_TIMEOUT_SECONDS, // 300
isEvmNetwork, // (network) => boolean
isStellarNetwork // (network) => boolean
} from '@402md/x402'import type {
PaymentNetwork, // 'base' | 'base-sepolia' | 'stellar' | 'stellar-testnet'
PaywallConfig, // server middleware config
PaymentClientConfig, // client config
BudgetConfig, // { maxPerCall?, maxPerDay?, maxPerSession? }
PaymentClient, // { pay, signMessage, getBalance, getAddress, fetch }
VerifiedPayment, // { settled, txHash?, payer?, network }
X402FetchOptions, // RequestInit + paymentConfig + skipPayment
WalletAuthConfig, // { verifyAccess, messageFormat? }
WalletSignature, // { message, signature, address }
ChainProvider, // { signPayment, signMessage, getBalance, getAddress }
// Re-exported from @x402/core
PaymentPayload,
PaymentRequired,
PaymentRequirements,
SettleResponse,
VerifyResponse
} from '@402md/x402'| Dependency | Required for | What it does |
|---|---|---|
viem |
base, base-sepolia |
EIP-712 signing, balance queries via JSON-RPC |
@stellar/stellar-sdk |
stellar, stellar-testnet |
Keypair management, Soroban auth entry signing, XDR encoding |
Both are loaded dynamically (await import(...)) on first use. If you only use Base, you never load the Stellar SDK and vice versa. Bundle size stays minimal.
MIT