Documentation Index
Fetch the complete documentation index at: https://docs.x402x.ai/llms.txt
Use this file to discover all available pages before exploring further.
1. Warm up cache
Warm up token detection cache at app startup:
const server = new X402Server({ client, facilitator });
// Warm up common tokens (non-blocking)
server.initialize([
"0x25d066c4C68C8A6332DfDB4230263608305Ca991", // USDC
"0xcea4eaef42afd4d6e12660b59018e90fa3ab28f4", // DAI
]).then(result => {
if (result.success) {
console.log(`✅ Cached ${result.detected} tokens`);
}
});
2. Fast mode
Skip detection for tokens with a known payment type:
const requirements = await server.createRequirements({
asset: "0x25d066c4C68C8A6332DfDB4230263608305Ca991",
maxAmountRequired: "1000000",
paymentType: "permit",
autoDetect: false, // <1ms
});
3. Reuse requirements
For fixed-amount APIs, reuse the requirements object:
// Create once, reuse many times
const cachedRequirements = await server.createRequirements({
asset: "0xUSDC",
maxAmountRequired: "1000000",
});
app.post("/api", async (req, res) => {
const result = await server.process(
req.headers["x-payment"] as string,
cachedRequirements // reuse
);
// ...
});
Security
1. Amount cap
The client must set a maximum payment amount:
const fetchWithPay = wrapFetchWithPayment(
fetch,
client,
"10000000" // pay up to 10 USDC
);
Multi-chain deployment
Create separate instances per chain
// BSC
const bscClient = createPublicClient({
chain: bsc,
transport: http(),
});
const bscServer = new X402Server({
client: bscClient,
facilitator
});
// Polygon
const polygonClient = createPublicClient({
chain: polygon,
transport: http(),
});
const polygonServer = new X402Server({
client: polygonClient,
facilitator
});
// Choose by request
app.post("/api", async (req, res) => {
const network = req.headers["x-network"];
const server = network === "polygon" ? polygonServer : bscServer;
const requirements = await server.createRequirements({
asset: 'xxx',
maxAmountRequired: "1000000",
});
const result = await server.process(
req.headers["x-payment"] as string,
requirements
);
// ...
});
Error handling
Handle by error stage
Provide different handling strategies for different stages:
const result = await server.process(paymentHeader, requirements);
if (!result.success) {
switch (result.errorStage) {
case "parse":
// Payment header format error - client issue
console.error("Invalid payment header");
return res.status(402).json({
...result.response,
userMessage: "Invalid payment info, please retry",
});
case "verify":
// Signature verification failed - client issue
console.error("Signature verification failed");
return res.status(402).json({
...result.response,
userMessage: "Payment verification failed, please re-sign",
});
case "settle":
// On-chain settlement failed - server issue
console.error("Settlement failed:", result.error);
// Consider alerting or retry mechanisms
return res.status(500).json({
...result.response,
userMessage: "Service temporarily unavailable, please try again later",
});
}
}
Unified error handling
app.post("/api", async (req, res) => {
try {
const requirements = await server.createRequirements({
asset: "0xUSDC",
maxAmountRequired: "1000000",
});
const result = await server.process(
req.headers["x-payment"] as string,
requirements
);
if (!result.success) {
// Log by error stage
switch (result.errorStage) {
case "parse":
console.error("Payment header parse failed:", result.response.error);
break;
case "verify":
console.error("Signature verification failed:", result.response.error);
break;
case "settle":
console.error("On-chain settlement failed:", result.error);
break;
}
// Return corresponding status
return res.status(result.status).json(result.response);
}
res.json({ data: "success" });
} catch (error) {
console.error("Unexpected error:", error);
res.status(500).json({
error: "Internal server error",
message: error instanceof Error ? error.message : "Unknown error"
});
}
});
Client-side error handling
try {
const response = await fetchWithPay(apiUrl, {
method: "POST",
});
if (!response.ok) {
if (response.status === 402) {
const error = await response.json();
console.error("Payment required:", error);
} else {
console.error("Request failed:", response.status);
}
return;
}
const data = await response.json();
console.log("Success:", data);
} catch (error) {
console.error("Error:", error);
}
Server-side error handling
app.post("/api", async (req, res) => {
try {
const result = await server.process(
req.headers["x-payment"] as string,
requirements
);
if (!result.success) {
return res.status(result.status).json(result.response);
}
res.json({ data: "content" });
} catch (error) {
console.error("Payment processing error:", error);
res.status(500).json({
error: "Internal server error",
message: error instanceof Error ? error.message : "Unknown error",
});
}
});
FAQs
Q1: How to choose the right authorization type?
A: Use auto detection (default) or follow suggestions:
- EIP-3009: Token-native support (e.g., USDC) — most efficient
- EIP-2612 Permit: Standard ERC-20 — great compatibility
- Permit2: Need generalized authorization management — most flexible
Q2: How to handle tokens with different decimals?
A: Be careful with units (wei):
// USDC (6 decimals): 1 USDC = 1000000
const usdcAmount = "1000000"; // 1 USDC
// DAI (18 decimals): 1 DAI = 1000000000000000000
const daiAmount = "1000000000000000000"; // 1 DAI
Q3: How to handle payment failures?
A: Use different strategies for different stages:
const result = await server.process(paymentHeader, requirements);
if (!result.success) {
// Inspect error stage
switch (result.errorStage) {
case "parse":
// Payment header format error - client issue
console.error("Invalid payment header format");
return res.status(402).json(result.response);
case "verify":
// Signature verification failed - client issue
console.error("Signature verification failed");
return res.status(402).json(result.response);
case "settle":
// On-chain settlement failed - server issue
console.error("Settlement failed:", result.error);
// Likely needs alerting or retry
return res.status(500).json(result.response);
}
}
Q4: When should I return 500 instead of 402?
A:
- 402: Client-side issues (parse/verify failed) — the client must fix
- 500: Server-side issues (settle failed) — may be transient (network, gas, etc.)
Settlement failures are typically temporary server issues; return 500 and suggest retrying later.
What’s next
Examples
See more real-world examples
Server SDK
Learn the server SDK in depth