
X402 Facilitator
- 3 installs
- 5 repo stars
- Updated April 11, 2026
- melonask/facilitator
Helps with ai & agent building tasks.
About
x402-facilitator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- x402-facilitator
- AI & Agent Building
- AI-coding skill
X402 Facilitator by the numbers
- 3 all-time installs (skills.sh)
- Ranked #13,674 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/melonask/facilitator --skill x402-facilitatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 5 |
| Last updated | April 11, 2026 |
| Repository | melonask/facilitator ↗ |
What it does
Helps with ai & agent building tasks.
Files
x402 Integration Guide
This skill helps LLM developers integrate on-chain payments into their applications using the x402 protocol and the facilitator packages.
What x402 Does
x402 revives the HTTP 402 Payment Required status code. Instead of API keys or subscriptions, a server returns 402 with payment requirements (amount, token, recipient). The client signs an off-chain authorization and re-sends the request with payment attached. A facilitator (relayer) verifies the signature and submits the on-chain transaction — the buyer never needs ETH for gas.
The key insight: payments happen at the HTTP layer. No accounts, no OAuth, no billing portal. Just: request → 402 → pay → 200.
Two Integration Scenarios
1. You have an API and want to charge per request (Seller)
Your server returns 402 when a request arrives without payment, then verifies and settles the payment before delivering the resource. You can use the official @x402/express (or @x402/hono, @x402/next) middleware for automatic handling, or implement the 402 flow manually for full control.
Read references/building-a-seller.md for the complete flow with code examples.
2. You have an agent that pays other agents/services (Buyer)
Your client wraps fetch with @x402/fetch so 402 responses are handled automatically — the client signs the payment and re-sends the request transparently. For EIP-7702 payments (any ERC-20 or native ETH), you provide a SchemeNetworkClient implementation that signs EIP-712 intents and EIP-7702 authorizations using viem.
Read references/building-a-buyer.md for the complete client setup with code examples.
Choose Your Payment Mechanism
| Mechanism | Scheme | Tokens | When to Use |
|---|---|---|---|
| EIP-7702 | eip7702 | Any ERC-20 (USDT, DAI) + native ETH | You want to accept any token, or sell for ETH |
| ERC-3009 | exact | USDC and tokens with transferWithAuthorization | You only need USDC — simplest setup |
Both mechanisms are gasless for the buyer. The facilitator pays gas and submits the on-chain transaction.
If you want to accept USDC and USDT, register both schemes — the seller lists both in its accepts array and the buyer picks one based on their token balance.
Self-Hosted Facilitator vs Public Facilitator
Public facilitators like Coinbase CDP (https://api.cdp.coinbase.com/platform/v2/x402) support the exact scheme (ERC-3009/USDC) out of the box. If that's all you need, you don't need @facilitator/server.
You need the self-hosted facilitator when:
- You want to accept any ERC-20 token (USDT, DAI, custom tokens) via EIP-7702
- You want to accept native ETH payments
- You want persistent nonce tracking and settlement audit trails (database-backed)
- You want full control over the relayer, delegate contract, and verification logic
Read references/facilitator-server.md for setup instructions, CLI options, database configuration, and the full HTTP API reference.
Deployed Delegate Contract
The EIP-7702 mechanism relies on a Delegate.sol smart contract that is deployed at the same address on all major EVM networks via CREATE2:
| Network | Chain ID | Address |
|---|---|---|
| Ethereum | 1 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Optimism | 10 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| BNB Chain | 56 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Polygon | 137 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Base | 8453 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Arbitrum | 42161 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Avalanche | 43114 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
For chains not listed here, deploy the contract yourself. Read references/delegate-contract.md for deployment instructions and contract details.
Integration Checklist
When helping a developer integrate, walk through these steps in order:
Step 1: Determine the scenario
- Are they building a seller (accepting payments for an API/resource)?
- Are they building a buyer (an agent that pays for resources)?
- Or both (agent-to-agent)?
Step 2: Choose the payment mechanism
- USDC only →
exactscheme (ERC-3009), can use public facilitator - Any ERC-20 or ETH →
eip7702scheme (EIP-7702), needs self-hosted facilitator - Both → register both schemes
Step 3: Verify the Delegate contract is deployed on their target chain
- Check the table above for known networks
- If not listed, guide them through deploying via Foundry (see
references/delegate-contract.md)
Step 4: Set up the facilitator (if using EIP-7702)
- Install and run
@facilitator/serverpointing to their chain RPC - Fund the relayer wallet with ETH for gas
- Configure database for production (see
references/facilitator-server.md)
Step 5: Implement the seller side (if applicable)
- Add 402 response logic or use
@x402/expressmiddleware - Define payment requirements (scheme, network, asset, amount, payTo)
- Wire verify + settle calls to the facilitator
- See
references/building-a-seller.mdfor full code
Step 6: Implement the buyer side (if applicable)
- Install
@x402/fetchandviem - Implement
SchemeNetworkClientfor EIP-7702 (or useExactEvmSchemefrom@x402/evmfor ERC-3009) - Wrap fetch with
wrapFetchWithPaymentFromConfig - See
references/building-a-buyer.mdfor full code
Step 7: Test end-to-end
- Verify the buyer can receive a 402 response, sign payment, and get the resource
- Check the facilitator logs for verification and settlement details
- Verify on-chain that the token transfer completed
Quick Reference: EIP-712 Domain
The Delegate contract uses this EIP-712 domain for signing payment intents:
const domain = {
name: "Delegate",
version: "1.0",
chainId: <chainId>,
verifyingContract: <buyer's EOA address>, // not the delegate contract!
};The verifyingContract is the buyer's own address because under EIP-7702, the delegate code runs _as_ the buyer's account — so the EIP-712 domain must use the buyer's address as the verifying contract.
Common Pitfalls
- Viem `signAuthorization` field mapping: Depending on the
viemversion,signAuthorizationmight returnaddressinstead ofcontractAddress, orvinstead ofyParity. The facilitator expectscontractAddressandyParity. Always map these safely:contractAddress: auth.contractAddress ?? auth.address. - No ERC-20 `approve()` needed: Under EIP-7702, the Delegate contract runs _in the context_ of the buyer's account and calls
transferdirectly (nottransferFrom). The buyer does not need to approve the Delegate contract to spend their tokens. - Wrong verifyingContract: The EIP-712 domain's
verifyingContractmust be the buyer's EOA address, NOT the Delegate contract address. This is the #1 source ofInvalidSignatureerrors. - Testing Expiration returns InvalidSignature: If you manually modify an intent's
deadlineafter it's been signed to test the expiration logic, the signature will fail to recover correctly, resulting in anInvalidSignatureerror rather thanExpired. - ETH payments need the zero address: For native ETH payments, set
assetto0x0000000000000000000000000000000000000000and useEthPaymentIntent. Note: Whiletokenis omitted, theamount,to,nonce, anddeadlinefields are still required. - Anvil testing requires Prague: If testing locally on Anvil, you must start Anvil with
--hardfork prague. Without it, EIP-7702 Type 4 transactions will fail to persist delegation code. - Missing extra fields for ERC-3009: The
exactscheme requiresextra.nameandextra.versionmatching the target USDC contract's EIP-712 domain. - Relayer needs ETH: The self-hosted facilitator's relayer pays gas for every settlement. Monitor the
/infoendpoint for balances.
{
"skill_name": "x402-facilitator",
"evals": [
{
"id": 1,
"prompt": "I run a weather API on Express (Node.js). I want to start charging $0.01 per request in USDC on Base. My wallet is 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B. Can you help me set that up?",
"expected_output": "Should guide the user through adding @x402/express middleware with the 'exact' scheme on eip155:8453, including payment requirements with USDC asset address on Base and the user's payTo address. Should mention they can use the public Coinbase CDP facilitator since they only need USDC/exact scheme.",
"files": []
},
{
"id": 2,
"prompt": "I'm building an AI agent that needs to pay for data from other agents. I want it to be able to pay with USDT on Base using EIP-7702. I have a private key for the agent's wallet. How do I set up the client side?",
"expected_output": "Should guide through installing @x402/fetch and viem, implementing a SchemeNetworkClient for the eip7702 scheme that signs PaymentIntent typed data + EIP-7702 authorization, using the Delegate contract address on Base, and wrapping fetch with wrapFetchWithPaymentFromConfig. Should mention the EIP-712 domain uses the buyer's address as verifyingContract.",
"files": []
},
{
"id": 3,
"prompt": "I want to accept USDT payments on my API running on Polygon. I don't want to use Coinbase's facilitator — I want to self-host. I need the facilitator to survive restarts. I have a relayer private key and a Postgres database at postgres://facilitator:secret@db.example.com:5432/facilitator. Walk me through the full setup including the facilitator server and the server-side payment verification.",
"expected_output": "Should cover: (1) verifying Delegate.sol is deployed on Polygon (address 0xD064939e...) or deploying it, (2) starting @facilitator/server with --relayer-key, --chain 137=<rpc>, --db <postgres-url>, (3) explaining the database tables (used_nonces + settlements), (4) adding seller-side code that returns 402 with eip7702 scheme payment requirements and calls /verify + /settle on the self-hosted facilitator. Should mention funding the relayer wallet with POL (ex. MATIC) for gas.",
"files": []
}
]
}
Building a Buyer (Payment Client)
A buyer is an HTTP client that:
1. Sends a normal request to a paid resource 2. Receives a 402 response with payment requirements 3. Signs a payment authorization (gasless, off-chain) 4. Re-sends the request with the payment attached 5. Receives the resource
Note on ERC-20 Approvals: The buyer does not need to callapprove()on the ERC-20 token contract. Because EIP-7702 delegates code to the buyer's own account, the Delegate contract callstransfernatively from the buyer's context.
Using @x402/fetch
The simplest approach — wrap your fetch with automatic payment handling:
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount("0x..." as `0x${string}`);EIP-7702 Client (USDT / Any ERC-20 / ETH)
The buyer needs to implement SchemeNetworkClient to sign EIP-712 intents and EIP-7702 authorizations:
import type {
SchemeNetworkClient,
PaymentPayload,
PaymentRequirements,
} from "@x402/fetch";
import type { Address, PrivateKeyAccount, TypedDataDomain } from "viem";
class Eip7702Scheme implements SchemeNetworkClient {
readonly scheme = "eip7702";
constructor(
private account: PrivateKeyAccount,
private chainId: number,
private delegateAddress: Address,
) {}
async createPaymentPayload(
_version: number,
requirements: PaymentRequirements,
): Promise<Pick<PaymentPayload, "x402Version" | "payload">> {
// Create the payment intent.
// In production, use a secure, unique nonce (e.g., UUID or DB counter)
const intent = {
token: requirements.asset as Address,
amount: BigInt(requirements.amount),
to: requirements.payTo as Address,
nonce: BigInt(Date.now()),
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
};
const domain: TypedDataDomain = {
name: "Delegate",
version: "1.0",
chainId: this.chainId,
verifyingContract: this.account.address, // Must be the buyer's address!
};
const types = {
PaymentIntent: [
{ name: "token", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "to", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
} as const;
// 1. Sign EIP-712 Intent
const signature = await this.account.signTypedData({
domain,
types,
primaryType: "PaymentIntent",
message: intent,
});
// 2. Sign EIP-7702 Authorization
// Note: Because a relayer submits this, do NOT set `executor: "self"`
const authorization = await this.account.signAuthorization({
contractAddress: this.delegateAddress,
chainId: this.chainId,
nonce: 0, // This is the authorization nonce (often 0 for first-time use)
});
return {
x402Version: 2,
payload: {
authorization: {
// Safely map viem differences across versions (address vs contractAddress, v vs yParity)
contractAddress:
(authorization as any).contractAddress ??
(authorization as any).address,
chainId: authorization.chainId,
nonce: authorization.nonce,
r: authorization.r,
s: authorization.s,
yParity:
authorization.yParity ?? ((authorization as any).v === 27n ? 0 : 1),
},
intent: {
...intent,
amount: intent.amount.toString(),
nonce: intent.nonce.toString(),
deadline: intent.deadline.toString(),
},
signature,
},
};
}
}For native ETH payments, use the EthPaymentIntent type instead (no token field):
const types = {
EthPaymentIntent: [
{ name: "amount", type: "uint256" },
{ name: "to", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
} as const;ERC-3009 Client (USDC)
import { ExactEvmScheme } from "@x402/evm/exact/client";
const evmSigner = {
...publicClient,
...walletClient,
address: account.address,
};
const client = new ExactEvmScheme(evmSigner);Wiring It Together
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [
{
network: "eip155:8453",
client: new Eip7702Scheme(account, 8453, DELEGATE_ADDRESS),
},
],
});
// Now use fetchWithPayment just like fetch — 402 handling is automatic
const response = await fetchWithPayment("https://api.example.com/weather");
const data = await response.json();For multi-scheme support (both USDT and USDC), register both schemes:
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [
{
network: "eip155:8453",
client: new Eip7702Scheme(account, 8453, DELEGATE_ADDRESS),
},
{
network: "eip155:8453",
client: new ExactEvmScheme(evmSigner),
},
],
});The @x402/fetch Package
Install: npm install @x402/fetch viem
Key exports:
wrapFetchWithPaymentFromConfig(fetch, config)— Wraps anyfetchimplementation with automatic 402 payment handlingx402Client— Low-level client for manual payment creationdecodePaymentResponseHeader(header)— Decodes thePAYMENT-RESPONSEheader from the seller's response
Building a Seller (Resource Server)
A seller is an HTTP server that:
1. Returns 402 Payment Required with payment requirements when no payment is attached 2. Verifies payments with the facilitator before serving the resource 3. Settles payments on-chain via the facilitator after verification
The 402 Flow
Client → Seller: GET /resource
Seller → Client: 402 + PAYMENT-REQUIRED header (base64 JSON with payment requirements)
Client → Seller: GET /resource + PAYMENT-SIGNATURE header (base64 JSON with signed payment)
Seller → Facilitator: POST /verify { paymentPayload, paymentRequirements }
Facilitator → Seller: { isValid: true, payer: "0x..." }
Seller → Facilitator: POST /settle { paymentPayload, paymentRequirements }
Facilitator → Seller: { success: true, transaction: "0x...", payer: "0x..." }
Seller → Client: 200 + resource body + PAYMENT-RESPONSE headerPayment Requirements Structure
When returning a 402, the seller specifies what payment it accepts:
interface PaymentRequirements {
scheme: "eip7702" | "exact"; // Payment mechanism
network: string; // CAIP-2 format: "eip155:<chainId>"
asset: string; // Token contract address or zero address for ETH
amount: string; // Amount in smallest unit (wei)
payTo: string; // Seller's address (where payment goes)
maxTimeoutSeconds: number; // How long the payment authorization is valid
extra: Record<string, unknown>; // Scheme-specific metadata
}EIP-7702 (USDT/Any ERC-20/ETH)
const requirements = {
scheme: "eip7702",
network: "eip155:8453", // Base mainnet
asset: USDT_ADDRESS, // ERC-20 token contract
amount: (10n ** 18n).toString(), // 1 token (18 decimals)
payTo: sellerAddress, // Your wallet address
maxTimeoutSeconds: 300,
extra: {},
};For native ETH, use the zero address as asset: 0x0000000000000000000000000000000000000000.
ERC-3009 (USDC)
const requirements = {
scheme: "exact",
network: "eip155:8453",
asset: USDC_ADDRESS,
amount: (10n ** 6n).toString(), // 1 USDC (6 decimals)
payTo: sellerAddress,
maxTimeoutSeconds: 300,
extra: {
name: "USD Coin", // EIP-712 domain name from the target token contract
version: "2", // EIP-712 domain version from the target token contract
},
};The extra.name and extra.version are required for ERC-3009 — they define the EIP-712 domain used by the USDC contract. _Tip: You can usually find these by checking the name() and version() or querying EIP712_DOMAIN_SEPARATOR() on the ERC-20 contract itself._
The 402 Response
Return a 402 with the payment requirements in both the response body and the PAYMENT-REQUIRED header:
function create402Response() {
const paymentRequired = {
x402Version: 2,
error: "Payment required",
resource: {
url: "https://api.example.com/weather",
description: "Weather data",
mimeType: "application/json",
},
accepts: [
{
scheme: "eip7702",
network: "eip155:8453",
asset: USDT_ADDRESS,
amount: "1000000000000000000",
payTo: sellerAddress,
maxTimeoutSeconds: 300,
extra: {},
},
],
};
return new Response(JSON.stringify(paymentRequired), {
status: 402,
headers: {
"Content-Type": "application/json",
"PAYMENT-REQUIRED": btoa(JSON.stringify(paymentRequired)),
},
});
}The accepts array can include multiple options — a seller can accept both USDT (eip7702) and USDC (exact).
Verifying a Payment
When a request comes in with a PAYMENT-SIGNATURE header:
const signatureHeader = req.headers.get("PAYMENT-SIGNATURE");
if (!signatureHeader) return create402Response();
const paymentPayload = JSON.parse(atob(signatureHeader));
const requestBody = JSON.stringify({ paymentPayload, paymentRequirements });
// 1. Verify
const verifyRes = await fetch(`${FACILITATOR_URL}/verify`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: requestBody,
});
const verifyData = await verifyRes.json();
if (!verifyData.isValid) {
return new Response("Payment verification failed", { status: 402 });
}Settling a Payment
After successful verification, settle on-chain:
// 2. Settle
const settleRes = await fetch(`${FACILITATOR_URL}/settle`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: requestBody,
});
const settleData = await settleRes.json();
if (!settleData.success) {
return new Response("Settlement failed", { status: 402 });
}
// 3. Deliver the resource
const payload = { data: "your resource", txHash: settleData.transaction };
return new Response(JSON.stringify(payload), {
headers: {
"Content-Type": "application/json",
"PAYMENT-RESPONSE": btoa(JSON.stringify(settleData)),
},
});Using @x402/express Middleware
Instead of handling the 402 flow manually, you can use the official Express middleware:
import { paymentMiddleware } from "@x402/express";
app.use(
paymentMiddleware({
"GET /weather": {
accepts: [
{
scheme: "eip7702",
price: "$0.01",
network: "eip155:8453",
payTo: "0xYourAddress",
},
],
description: "Weather data",
},
}),
);Other framework integrations: @x402/hono, @x402/next, @x402/paywall. These use the Coinbase CDP facilitator by default. To use your self-hosted facilitator with these, set the facilitator option in the middleware config.
Delegate Contract — Deployment & Addresses
Deployed Addresses
The Delegate.sol contract is deployed via CREATE2 at the same address on all supported networks:
| Network | Chain ID | Address |
|---|---|---|
| Ethereum Mainnet | 1 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Optimism | 10 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| BNB Chain | 56 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Polygon | 137 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Base | 8453 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Arbitrum | 42161 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
| Avalanche | 43114 | 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd |
Local Testing (Anvil / Foundry)
If you are writing tests on a local network, you must run Anvil with the Prague hardfork. EIP-7702 Type-4 transactions are only supported from Prague onwards. Without it, transactions might succeed but the EOA delegation will not persist.
# Start Anvil with Prague support
anvil --hardfork pragueOnce running, you can manually deploy the Delegate to 127.0.0.1:8545 to test the full lifecycle.
Deploying to a New Network
The contract uses the Arachnid Deterministic Deployment Proxy (0x4e59b44847b379578588920cA78FbF26c0B4956C) which is pre-deployed on virtually every EVM chain. Because the salt and initcode are fixed, the address will be the same everywhere.
Prerequisites
- Foundry (forge, cast)
- A funded deployer wallet (even 0 ETH works on most chains — the CREATE2 factory pays gas)
- RPC URL for the target network
Steps
1. Install the facilitator contracts:
forge install melonask/facilitator2. Deploy:
forge script lib/facilitator/packages/contracts/script/Deploy.s.sol \
--rpc-url <YOUR_RPC_URL> \
--broadcastThe script will:
- Predict the address (verify it matches
0xD064939e706dC03699dB7Fe58bB0553afDF39fDd) - Skip if the contract is already deployed at that address
- Otherwise deploy via the CREATE2 factory
Override the Salt
If you need a different address (e.g., on a chain where the default address is already taken), set the DEPLOY_SALT environment variable:
DEPLOY_SALT=0x... forge script lib/facilitator/packages/contracts/script/Deploy.s.sol \
--rpc-url <YOUR_RPC_URL> \
--broadcastThen pass --delegate-address <your-address> to the facilitator server.
Verify the Deployment
cast code 0xD064939e706dC03699dB7Fe58bB0553afDF39fDd --rpc-url <YOUR_RPC_URL>If the output is 0x (empty), the contract is not deployed yet on that chain.
Contract Details
- Solidity Version:
^0.8.24 - EVM Target: Prague (Cancun + EIP-7702)
- Compiler Settings:
via_ir = true,optimizer_runs = 1,bytecode_hash = "none" - OpenZeppelin: v5.5.0
- EIP-712 Domain:
{ name: "Delegate", version: "1.0" } - Key Functions:
transfer(PaymentIntent, bytes)— Execute a signed ERC-20 paymenttransferEth(EthPaymentIntent, bytes)— Execute a signed native ETH paymentinvalidateNonce(uint256)— Cancel a pending intent (owner only)- Key Events:
PaymentExecuted(address indexed token, address indexed to, uint256 amount, uint256 indexed nonce)EthPaymentExecuted(address indexed to, uint256 amount, uint256 indexed nonce)NonceInvalidated(uint256 indexed nonce)
Warning: This contract has not been audited.
Self-Hosted Facilitator — Setup & API
Why Self-Host?
The x402 ecosystem has public facilitators (e.g., Coinbase CDP at https://api.cdp.coinbase.com/platform/v2/x402), but they only support the exact scheme (ERC-3009 / USDC). To accept any ERC-20 token (USDT, DAI, etc.) or native ETH via EIP-7702, you need a self-hosted facilitator.
A self-hosted facilitator also gives you:
- Full control over nonce replay protection (with a persistent database)
- Settlement audit trail
- Custom delegate contract addresses
- No dependency on third-party uptime
Installation
npm install -g @facilitator/server
# or use via npx
npx @facilitator/server --helpCLI Options
| Option | Default | Description |
|---|---|---|
--port | 8080 | Server port |
--host | 0.0.0.0 | Server host |
--relayer-key | required | Private key (hex) for the relayer that pays gas |
--chain | all known | Chain config (repeatable). Formats: id=url, url, or id. |
--delegate-address | auto-detect | Override the Delegate contract address for all chains |
--db | optional | Database path (SQLite) or connection string (PostgreSQL) |
Running the Server
Defaults (All Known Chains)
Run on all 7 supported networks with default public RPCs:
npx @facilitator/server --relayer-key 0x...Local Testing (Anvil)
When testing locally against Anvil (which must be started with --hardfork prague), bind specifically to chain 31337:
npx @facilitator/server \
--relayer-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--chain 31337=http://127.0.0.1:8545Specific Chains
Three --chain formats:
# Chain ID with custom RPC
--chain 8453=https://mainnet.base.org
# Custom RPC only (chain ID auto-detected)
--chain https://mainnet.base.org
# Chain ID only (uses default public RPC)
--chain 8453With Database (Recommended for Production)
# SQLite
npx @facilitator/server \
--relayer-key 0x... \
--db ./facilitator.db
# PostgreSQL
npx @facilitator/server \
--relayer-key 0x... \
--db postgres://user:pass@localhost:5432/facilitator
# PostgreSQL via env vars
PGHOST=localhost PGDATABASE=facilitator npx @facilitator/server \
--relayer-key 0x...Without --db, nonce state and settlement history are in-memory only (lost on restart).
HTTP API
| Endpoint | Method | Description |
|---|---|---|
/verify | POST | Verify a signed payment (read-only, no state change) |
/settle | POST | Verify + submit Type 4 transaction on-chain |
/supported | GET | List supported schemes, networks, and signers |
/healthcheck | GET | { status: "ok" } |
/info | GET | Relayer ETH balance per chain |
/settlements | GET | Settlement history (?payer=0x..., optional &chainId=1) |
Request Body for /verify and /settle
The paymentPayload must follow the x402 V2 format, which includes the resource and accepted fields alongside the scheme-specific payload.
{
"paymentPayload": {
"x402Version": 2,
"resource": {
"url": "https://api.example.com/data",
"description": "API Access"
},
"accepted": {
"scheme": "eip7702",
"network": "eip155:1",
"asset": "0x...",
"amount": "1000000",
"payTo": "0x...",
"maxTimeoutSeconds": 300
},
"payload": {
"authorization": {
"contractAddress": "0x...",
"chainId": 1,
"nonce": 0,
"r": "0x...",
"s": "0x...",
"yParity": 0
},
"intent": {
"token": "0x...",
"amount": "1000000",
"to": "0x...",
"nonce": "123",
"deadline": "1700000000"
},
"signature": "0x..."
}
},
"paymentRequirements": {
"scheme": "eip7702",
"network": "eip155:1",
"asset": "0x...",
"amount": "1000000",
"payTo": "0x...",
"maxTimeoutSeconds": 300,
"extra": {}
}
}Verification Checks
The facilitator performs these checks before settlement:
1. Recover signer from EIP-7702 authorization 2. Verify delegate contract address is trusted 3. Verify EIP-712 intent signature matches authorization signer 4. Check intent matches payment requirements (recipient, amount, asset) 5. Check deadline has not expired 6. Check nonce has not been used (replay protection) 7. Check payer has sufficient token/ETH balance on-chain
Supported Mechanisms
| Mechanism | Scheme | Token Support | How It Works |
|---|---|---|---|
| EIP-7702 | eip7702 | Any ERC-20 + native ETH | Account-level delegation, gasless for buyer |
| ERC-3009 | exact | USDC and tokens with transferWithAuthorization | Token-level authorization, gasless for buyer |
Both mechanisms are registered simultaneously — the scheme in the payment requirements determines which is used.