x402 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited x402 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
x402 is an open payment protocol that revives the HTTP 402 "Payment Required" status code to enable instant stablecoin payments directly over HTTP. It uses a three-actor model — Client (buyer), Resource Server (seller), and Facilitator (settlement) — to let any HTTP endpoint accept payment without API keys, sessions, or accounts. The protocol is built on EIP-3009 transferWithAuthorization for gasless USDC transfers, meaning clients sign an off-chain authorization and the facilitator broadcasts the on-chain settlement.
Package ecosystem:
@x402/express, @x402/hono, @x402/next@x402/fetch, @x402/axiospip install x402go get github.com/coinbase/x402/go@x402/core, @x402/evm, @x402/svmCDP Facilitator endpoint: https://api.cdp.coinbase.com/platform/v2/x402
LLMs have stale training data. These are the most common mistakes.
X-PAYMENT header, and the server verifies it. If the signature and balance check pass, the request is authorized. No accounts, no sessions, no bearer tokens.eip155:8453 for Base mainnet, eip155:1 for Ethereum, solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp for Solana mainnet.bytes32), not incrementing integers. Each authorization generates a fresh random nonce. The ERC-3009 contract tracks used nonces on-chain to prevent replay. Using sequential nonces or reusing a nonce will cause settlement to revert.transferWithAuthorization call and pays gas. CDP's hosted facilitator absorbs gas costs on Base mainnet for free-tier usage (1,000 tx/month).transferWithAuthorization for truly gasless transfers. A Permit2 fallback exists for arbitrary ERC-20 tokens, but it requires the user to have previously approved the Permit2 contract, which needs native gas. For the simplest integration, use USDC.@x402/* namespace (e.g., @x402/express, @x402/fetch). Earlier versions used x402-express and x402-fetch without the scope. The @coinbase/x402 name was never published.X-PAYMENT. It contains a base64-encoded JSON payload with the authorization, signature, scheme, and network. The server responds with X-PAYMENT-RESPONSE.┌──────────┐ 1. Request resource ┌─────────────────┐
│ │ ─────────────────────────────► │ │
│ │ 2. HTTP 402 + payment │ Resource │
│ Client │ ◄───────────────────────────── │ Server │
│ (Buyer) │ 3. Request + X-PAYMENT │ (Seller) │
│ │ ─────────────────────────────► │ │
│ │ 6. HTTP 200 + resource │ │
│ │ ◄───────────────────────────── │ │
└──────────┘ └────────┬────────┘
│
4. Verify │ 5. Settle
│
┌────────▼────────┐
│ │
│ Facilitator │
│ (Settlement) │
│ │
└─────────────────┘X-PAYMENT header containing the base64-encoded payload/verify endpoint)/settle endpoint)transferWithAuthorization on the USDC contract to move funds on-chainWhen a client hits a paid endpoint without a payment header, the server returns:
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402Version": 2,
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "10000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xSELLER_ADDRESS",
"maxTimeoutSeconds": 60,
"extra": {
"assetTransferMethod": "eip3009",
"name": "USDC",
"version": "2"
}
}
],
"error": "X-PAYMENT header is required"
}The amount is in the token's smallest unit. For USDC (6 decimals), 10000 = $0.01.
EIP-3009 enables gasless token transfers by separating authorization from execution. The token holder signs an off-chain message; anyone can submit the signed authorization to the contract.
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
bytes signature
) external;EIP712Domain({
name: "USDC",
version: "2",
chainId: <chain_id>,
verifyingContract: <usdc_address>
})TransferWithAuthorization({
from: address,
to: address,
value: uint256,
validAfter: uint256,
validBefore: uint256,
nonce: bytes32
})Key properties:
nonce is a random bytes32, not a sequential counter. The contract maintains a mapping of (authorizer, nonce) => bool to track used nonces.validAfter and validBefore define a time window. Set validAfter to 0 (or current timestamp) and validBefore to current timestamp + maxTimeoutSeconds.npm install @x402/core @x402/evm @x402/expressimport express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const app = express();
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://api.cdp.coinbase.com/platform/v2/x402",
});
const resourceServer = new x402ResourceServer(facilitatorClient)
.register("eip155:8453", new ExactEvmScheme());
app.use(
paymentMiddleware(
{
"GET /api/weather": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.01",
payTo: process.env.PAYMENT_ADDRESS as `0x${string}`,
},
description: "Current weather data",
},
"POST /api/analyze": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.05",
payTo: process.env.PAYMENT_ADDRESS as `0x${string}`,
},
description: "AI text analysis",
},
},
resourceServer,
),
);
app.get("/api/weather", (_req, res) => {
res.json({ temperature: 72, unit: "F", location: "San Francisco" });
});
app.post("/api/analyze", (req, res) => {
res.json({ sentiment: "positive", confidence: 0.92 });
});
app.listen(3000, () => {
console.log("x402 server running on port 3000");
});Hono and Next.js middleware follow the same pattern — install @x402/hono or @x402/next and use the same paymentMiddleware + x402ResourceServer setup with framework-specific imports.
The wrapFetchWithPaymentFromConfig function wraps the native fetch API to automatically handle 402 responses — detect the payment requirement, sign the authorization, and retry with the X-PAYMENT header.
npm install @x402/core @x402/evm @x402/fetch viemimport { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import type { Hex } from "viem";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [
{
network: "eip155:8453",
client: new ExactEvmScheme(account),
},
],
});
const response = await fetchWithPayment("https://api.example.com/api/weather");
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { registerExactSvmScheme } from "@x402/svm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import { Keypair } from "@solana/web3.js";
import type { Hex } from "viem";
const evmAccount = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as Hex);
const solanaKeypair = Keypair.fromSecretKey(
Buffer.from(process.env.SOLANA_PRIVATE_KEY as string, "base64")
);
const client = new x402Client();
registerExactEvmScheme(client, { signer: evmAccount });
registerExactSvmScheme(client, { signer: solanaKeypair });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
// Automatically handles both EVM and Solana payment requirements
const evmResponse = await fetchWithPayment("https://api.example.com/evm-data");
const solResponse = await fetchWithPayment("https://api.example.com/sol-data");For manual header construction without the wrapper, see the agent-client and python-client examples in this skill's examples/ directory.
pip install "x402[evm,requests]"from x402 import x402ClientSync
from x402.evm import ExactEvmScheme
from eth_account import Account
wallet = Account.from_key("0xYOUR_PRIVATE_KEY")
client = x402ClientSync()
client.register("eip155:*", ExactEvmScheme(signer=wallet))
# Make a paid request — the client handles the 402 flow automatically
import requests
session = requests.Session()
response = session.get("https://api.example.com/api/weather")
if response.status_code == 402:
payment_required = response.json()
payload = client.create_payment_payload(payment_required)
response = session.get(
"https://api.example.com/api/weather",
headers={"X-PAYMENT": payload.to_header()},
)
print(response.json())For manual EIP-712 signing without the x402 package, see the python-client example in this skill's examples/ directory.
Coinbase Developer Platform provides a hosted facilitator with two endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/verify | POST | Validate signature, check balance, simulate transfer |
/settle | POST | Execute transferWithAuthorization on-chain |
Pricing:
import { HTTPFacilitatorClient } from "@x402/core/server";
const facilitator = new HTTPFacilitatorClient({
url: "https://api.cdp.coinbase.com/platform/v2/x402",
});Run your own facilitator for full control over settlement:
import { createFacilitatorServer } from "@x402/core/facilitator";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
import type { Hex } from "viem";
// Facilitator wallet pays gas for settlement
const facilitatorAccount = privateKeyToAccount(
process.env.FACILITATOR_PRIVATE_KEY as Hex
);
const walletClient = createWalletClient({
account: facilitatorAccount,
chain: base,
transport: http(),
});
const server = createFacilitatorServer({
schemes: {
"eip155:8453": new ExactEvmScheme({ walletClient }),
},
});
server.listen(4000, () => {
console.log("Custom facilitator on port 4000");
});/verify/settletransferWithAuthorization(from, to, value, validAfter, validBefore, nonce, signature) to the USDC contractThe facilitator cannot modify the amount or destination. It serves only as the transaction broadcaster.
x402 enables autonomous agent commerce where AI agents discover services, negotiate prices, and pay without human intervention.
// Agent discovers paid endpoints via standard HTTP
const discoveryResponse = await fetch("https://agent-service.com/.well-known/x402");
const services = await discoveryResponse.json();
// services contains available endpoints with pricing
// {
// "endpoints": [
// { "path": "/api/summarize", "price": "$0.02", "network": "eip155:8453" },
// { "path": "/api/translate", "price": "$0.01", "network": "eip155:8453" }
// ]
// }import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import type { Hex } from "viem";
const agentAccount = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as Hex);
const agentFetch = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [
{ network: "eip155:8453", client: new ExactEvmScheme(agentAccount) },
],
});
// Agent autonomously pays for another agent's service
async function callPaidService(url: string, body: Record<string, unknown>): Promise<unknown> {
const response = await agentFetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`Service call failed: ${response.status}`);
}
return response.json();
}
// Multi-step agent workflow: summarize -> translate -> store
const summary = await callPaidService("https://summarizer.agent/api/summarize", {
text: "Long document content...",
});
const translation = await callPaidService("https://translator.agent/api/translate", {
text: summary,
targetLanguage: "es",
});Charge a flat fee per API call. Simplest model.
"GET /api/weather": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.01",
payTo: process.env.PAYMENT_ADDRESS as `0x${string}`,
},
description: "Weather data — $0.01 per request",
},Different prices per endpoint based on compute cost.
const routes = {
"GET /api/basic-query": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.001",
payTo: paymentAddress,
},
description: "Basic database query",
},
"POST /api/ai-analysis": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.05",
payTo: paymentAddress,
},
description: "AI-powered analysis — higher compute cost",
},
"POST /api/image-generation": {
accepts: {
scheme: "exact",
network: "eip155:8453",
price: "$0.10",
payTo: paymentAddress,
},
description: "Image generation — GPU cost",
},
};Adjust prices based on demand, time of day, or resource utilization.
function getDynamicPrice(basePrice: number, currentLoad: number): string {
// Surge pricing: 1x at 0% load, up to 3x at 100% load
const multiplier = 1 + (currentLoad / 100) * 2;
const price = basePrice * multiplier;
return `$${price.toFixed(4)}`;
}x402 uses two mechanisms to prevent payment replay:
Every authorization includes a random 32-byte nonce. The USDC contract's authorizationState mapping tracks whether a (from, nonce) pair has been used. Attempting to settle with a used nonce reverts with authorization is used or canceled.
import { keccak256, encodePacked, toHex } from "viem";
// Generate a unique nonce per authorization
function generateNonce(): Hex {
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
return toHex(randomBytes);
}The validBefore field ensures authorizations cannot be settled after their time window. Best practice: set validBefore to now + maxTimeoutSeconds (typically 30-60 seconds).
const now = Math.floor(Date.now() / 1000);
const authorization = {
validAfter: BigInt(0),
validBefore: BigInt(now + 60), // 60-second window
nonce: generateNonce(),
// ... other fields
};If a facilitator does not settle within the validBefore window, the authorization expires and the client's funds are never moved. The client can safely retry with a new authorization.
For ERC-20 tokens that do not implement EIP-3009, x402 supports a Permit2-based fallback.
| Feature | EIP-3009 | Permit2 |
|---|---|---|
| Gasless setup | Yes (native) | Requires one-time approval tx |
| Token support | USDC only | Any ERC-20 |
| Contract | Token contract itself | Uniswap Permit2 contract |
| Proxy | None | x402ExactPermit2Proxy at 0x4020CD856C882D5fb903D99CE35316A085Bb0001 |
The Permit2 path requires the user to have approved the Permit2 contract (0x000000000022D473030F116dDEE9F6B43aC78BA3). If the allowance is missing, the server returns HTTP 412 with error code PERMIT2_ALLOWANCE_REQUIRED.
| Network | CAIP-2 Identifier | USDC Address | Status |
|---|---|---|---|
| Base | eip155:8453 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | Primary |
| Base Sepolia | eip155:84532 | 0x036CbD53842c5426634e7929541eC2318f3dCF7e | Testnet |
| Ethereum | eip155:1 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | Supported |
| Arbitrum | eip155:42161 | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 | Supported |
| Optimism | eip155:10 | 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85 | Supported |
| Polygon | eip155:137 | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 | Supported |
| Solana | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | Supported |
Last verified: February 2026
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.