A complete x402 facilitator implementation for Arbitrum
x402-facilitator is an early-stage TypeScript project in the AI payments / x402 ecosystem, focused on arbitrum, x402, x402-facilitator. It currently has 2 GitHub stars and 0 forks, and sits alongside related tools like r402, OpenFacilitator, facilitator.
An x402 payment facilitator service for Arbitrum with multi-merchant support, automatic fee collection, persistent nonce storage, and failure recovery.
This facilitator enables merchants to accept USDC payments on Arbitrum using the x402 HTTP payment protocol with EIP-3009 transfer authorizations. The service handles payment verification, onchain settlement, fee collection, and automatic recovery of failed transactions.
Payment Processing
SDK Compatibility
/requirements endpointsReliability and Recovery
Operational
cd facilitator
pnpm install
Local Development (Docker):
# Start PostgreSQL
docker-compose up -d
# Migrations run automatically on first start
Production:
# Run migrations
psql $DATABASE_URL -f migrations/001_init.sql
psql $DATABASE_URL -f migrations/002_merchants.sql
cp .env.example .env
Edit .env with your configuration:
# Network (CAIP-2: eip155:421614 or eip155:42161; legacy names still accepted)
NETWORK=eip155:421614
# Database (REQUIRED for production)
POSTGRES_USER=facilitator
POSTGRES_PASSWORD=create_a_secure_password_here
POSTGRES_DB=facilitator
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
# Facilitator private key (placeholder - use your actual private key)
FACILITATOR_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
# Admin API key hash (generate with: pnpm generate-api-key)
# Placeholder example - do not use in production
ADMIN_API_KEY_HASH=$2b$10$placeholder.hash.do.not.use.in.production
# Fee configuration
SERVICE_FEE_BPS=50 # 0.5%
GAS_FEE_USDC=100000 # 0.1 USDC
# Merchants are stored in database (see docs/MERCHANT_MANAGEMENT.md)
# Add merchants with: pnpm merchants add <address> <name> <apiKeyHash>
# Generate a key
pnpm generate-api-key
Example output:
API Key (plain text - give to merchant securely):
xyz_abc123def456...
Bcrypt Hash (store in database or .env):
$2b$10$abcdefghijklmnopqrstuvwxyz...
Security Note:
.env fileDevelopment:
pnpm dev
Production:
pnpm build
pnpm start
Docker:
docker build -t x402-facilitator .
docker run -p 3002:3002 --env-file .env x402-facilitator
Important: All code examples below use placeholder values. Never hardcode real API keys, private keys, or sensitive data in documentation or source code.
GET /health
Health check with network information.
{
"status": "ok",
"network": "eip155:421614",
"chainId": 421614,
"timestamp": 1699000000000
}
GET /supported
Returns supported payment kinds.
{
"kinds": [
{ "x402Version": 1, "scheme": "exact", "network": "arbitrum" },
{ "x402Version": 1, "scheme": "exact", "network": "arbitrum-sepolia" },
{ "x402Version": 2, "scheme": "exact", "network": "eip155:42161", "payTo": "0x..." },
{ "x402Version": 2, "scheme": "exact", "network": "eip155:421614", "payTo": "0x..." }
],
"versions": {
"1": { "kinds": [{ "x402Version": 1, "scheme": "exact", "network": "arbitrum" }, { "x402Version": 1, "scheme": "exact", "network": "arbitrum-sepolia" }] },
"2": { "kinds": [{ "x402Version": 2, "scheme": "exact", "network": "eip155:42161", "payTo": "0x..." }, { "x402Version": 2, "scheme": "exact", "network": "eip155:421614", "payTo": "0x..." }] }
},
"signingAddresses": {
"settlement": "0x..."
}
}
GET /requirements
Returns default payment requirements with facilitator address. Requirements are also included in the PAYMENT-RESPONSE header (mirrored to X-PAYMENT-RESPONSE).
Response:
{
"x402Version": 2,
"error": "Payment required",
"accepts": [
{
"scheme": "exact",
"network": "eip155:421614",
"maxAmountRequired": "1000000",
"asset": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
"payTo": "0xFACILITATOR_ADDRESS",
"resource": "http://localhost:3002/resource",
"description": "Payment required for resource access",
"mimeType": "application/json",
"maxTimeoutSeconds": 3600,
"extra": {
"feeMode": "facilitator_split",
"feeBps": 50,
"gasBufferWei": "100000",
"nonce": "0xEXAMPLE1234567890abcdef",
"deadline": 1731024000
}
}
]
}
POST /requirements
Generates payment requirements with specific amount and merchant address.
Request:
{
"amount": "2500000",
"memo": "Order #A1234",
"extra": {
"merchantAddress": "0xMERCH...ADD"
}
}
Response:
{
"x402Version": 2,
"error": "Payment required",
"accepts": [
{
"scheme": "exact",
"network": "eip155:42161",
"maxAmountRequired": "2500000",
"asset": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"payTo": "0xFACILITATOR_ADDRESS",
"resource": "http://localhost:3002/resource",
"description": "Order #A1234",
"mimeType": "application/json",
"maxTimeoutSeconds": 3600,
"extra": {
"feeMode": "facilitator_split",
"merchantAddress": "0xMERCHANTADDRESS1234567890abcdef",
"feeBps": 120,
"gasBufferWei": "150000",
"nonce": "0xEXAMPLE9876543210fedcba",
"deadline": 1731024000
}
}
]
}
POST /verify
Verifies payment payload without executing settlement. No authentication required. Accepts SDK-compatible format.
Request:
{
"network": "eip155:42161",
"token": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"recipient": "0xFACILITATOR_ADDRESS", // Must match facilitator address
"amount": "2500000",
"nonce": "0xEXAMPLE9876543210fedcba", // Example nonce - unique per request
"deadline": 1731024000,
"memo": "Order #A1234",
"extra": {
"merchantAddress": "0xMERCHANTADDRESS1234567890abcdef", // Placeholder merchant address
"feeMode": "facilitator_split"
},
"permit": {
"owner": "0xBUYERADDRESS1234567890abcdef", // Placeholder buyer address
"spender": "0xFACILITATOR_ADDRESS", // Must match facilitator address
"value": "2500000",
"deadline": 1731024000,
"sig": "0xSIG..."
}
}
Response:
{
"valid": true,
"reason": null,
"meta": {
"facilitatorRecipient": "0xFACILITATOR_ADDRESS"
}
}
POST /settle
Executes onchain settlement. Requires merchant API key. Accepts SDK-compatible format.
Headers:
X-API-Key: your_merchant_api_key_here # Placeholder - use your actual API key
Request: Same format as /verify (SDK-compatible with permit)
Response:
{
"success": true,
"txHash": "0xONCHAIN...",
"meta": {
"journalId": "0x3c2d...ab",
"grossAmount": "2500000",
"feeAmount": "30000",
"merchantNet": "2470000",
"forwardTxHash": "0xFORWARD...",
"incomingTxHash": "0xINCOMING...",
"outgoingTxHash": "0xOUTGOING...",
"blockNumber": 12345678,
"status": "FORWARDED"
},
"transactionHash": "0xONCHAIN...",
"incomingTransactionHash": "0xINCOMING...",
"outgoingTransactionHash": "0xOUTGOING...",
"blockNumber": 12345678,
"status": "confirmed",
"merchantAddress": "0xMERCH...",
"feeBreakdown": {
"merchantAmount": "2470000",
"serviceFee": "12350",
"gasFee": "100000",
"totalAmount": "2500000"
}
}
POST /admin/refund
Executes refund for failed payment. Requires admin API key.
Headers:
X-Admin-Key: admin-api-key
Request:
{
"nonce": "0x...",
"reason": "Settlement failed after max retries"
}
Response:
{
"success": true,
"refundHash": "0x..."
}
The facilitator collects two types of fees:
User pays total amount (merchant amount + fees)
|
v
Facilitator receives total
|
+---> Forwards merchant amount to merchant
|
+---> Keeps service fee + gas fee
For a 1 USDC merchant payment:
Merchant Amount: 1.000000 USDC (goes to merchant)
Service Fee: 0.005000 USDC (0.5% of merchant amount)
Gas Fee: 0.100000 USDC (fixed)
-----------------------------------------
Total User Pays: 1.105000 USDC
SERVICE_FEE_BPS=50 # 50 basis points = 0.5%
GAS_FEE_USDC=100000 # 0.1 USDC (6 decimals)
arbitrum0xaf88d065e77c8cC2239327C5EDb3A432268e5831 (native USDC)https://arb1.arbitrum.io/rpcarbitrum-sepolia0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d (test USDC)https://sepolia-rollup.arbitrum.io/rpcpending
|
v
incoming_submitted (user -> facilitator tx submitted)
|
v
incoming_complete (user -> facilitator tx confirmed)
|
v
outgoing_submitted (facilitator -> merchant tx submitted)
|
v
complete (facilitator -> merchant tx confirmed)
OR
|
v
failed (terminal state, requires manual refund)
The recovery worker runs every 5 minutes (configurable) and:
incoming_complete: retries outgoing transferoutgoing_submitted: checks onchain statusfailed after max retriesMerchant Authentication
/settle endpointAdmin Authentication
/admin/* endpointsStartup Validations
Payment Validations
Find incomplete payments:
SELECT * FROM payments
WHERE status IN ('incoming_complete', 'outgoing_submitted')
ORDER BY created_at ASC;
Check recovery history:
SELECT * FROM payment_events
WHERE event_type LIKE 'recovery_%'
ORDER BY created_at DESC;
Payment statistics:
SELECT
status,
COUNT(*) as count,
SUM(total_amount) as total_volume
FROM payments
GROUP BY status;
Clients integrate with the facilitator using the facilitator URL. The facilitator's address is provided dynamically through the requirements endpoint, simplifying client implementation and allowing the facilitator to manage the fee model server-side.
POST /requirements with amount and merchant addressPOST /verifyPOST /settleconst facilitatorUrl = process.env.NEXT_PUBLIC_FACILITATOR_URL;
// Step 1: Fetch requirements (no facilitator address needed)
const requirements = await fetch(`${facilitatorUrl}/requirements`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: '2500000',
memo: 'Order #123',
extra: { merchantAddress: '0xMERCH...' }
})
}).then(r => r.json());
// Step 2: Create EIP-3009 permit using requirements.recipient (facilitator address)
const permit = await createPermit({
owner: userAddress,
spender: requirements.recipient, // Facilitator address injected here
value: requirements.amount,
deadline: requirements.deadline,
nonce: requirements.nonce,
});
// Step 3: Verify
const verification = await fetch(`${facilitatorUrl}/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...requirements, permit })
}).then(r => r.json());
// Step 4: Settle (via backend with merchant API key)
pnpm generate-api-keypnpm merchants add <address> <name> <hash>const merchantAmount = 1000000; // 1 USDC
const serviceFee = Math.floor(merchantAmount * 50 / 10000); // 0.5%
const gasFee = 100000; // 0.1 USDC
const totalAmount = merchantAmount + serviceFee + gasFee;
const paymentRequirements = {
scheme: 'exact',
network: 'arbitrum-sepolia',
token: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
amount: totalAmount.toString(),
recipient: '0xFacilitatorAddress', // Facilitator, not merchant
merchantAddress: '0xYourMerchantAddress',
description: `Content + ${serviceFee/1e6} USDC fee + ${gasFee/1e6} USDC gas`,
maxTimeoutSeconds: 300,
};
res.status(402).json({
error: 'Payment Required',
paymentRequirements,
facilitatorUrl: 'https://facilitator.example.com',
});
const response = await fetch('https://facilitator.example.com/settle', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'your_merchant_api_key_here', // Placeholder - use your actual API key
},
body: JSON.stringify({
paymentPayload,
paymentRequirements,
}),
});
const result = await response.json();
console.log('Settlement:', result.transactionHash);
console.log('Merchant received:', result.feeBreakdown.merchantAmount);
Health Check
curl http://localhost:3002/health
Database Status
SELECT COUNT(*) FROM payments WHERE status = 'complete';
SELECT COUNT(*) FROM payments WHERE status = 'failed';
Recovery Worker Check logs for:
Recovery worker startedFound incomplete settlementsSuccessfully recovered paymentcurl -X POST http://localhost:3002/admin/refund \
-H "Content-Type: application/json" \
-H "X-Admin-Key: your-admin-key" \
-d '{
"nonce": "0x...",
"reason": "Settlement failed after max retries"
}'
Backup Database
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d_%H%M%S).sql
Restore Database
psql $DATABASE_URL < backup_20241104_120000.sql
# Type checking
pnpm check
# Build
pnpm build
# Run tests (if implemented)
pnpm test
# Clean build artifacts
pnpm clean
# Generate API key
pnpm generate-api-key
# Start database
docker-compose up -d
# Start facilitator
pnpm dev
# Check logs
tail -f logs/facilitator.log
Detailed reference guides are available in the docs/ directory:
docs/DATABASE_SETUP.md: Database configuration, advanced queries, and backup proceduresdocs/MERCHANT_MANAGEMENT.md: How to add, manage, and monitor merchantsdocs/AUTHENTICATION_GUIDE.md: Authentication setup, API key management, and testingdocs/INTEGRATION_GUIDE.md: Complete merchant integration walkthrough with examplesMIT - See LICENSE
Rust SDK for the x402 payment protocol.
Accept payments from AI agents. Open-source. Your data, your domain, your rules. Free forever.
A production-ready payment settlement service for the x402 protocol. Built with Elysia and Node.js, it verifies cryptographic payment signatures and settles transactions on-chain for EVM, SVM (Solana), and Starknet networks.
Production-ready x402 facilitator server.
Golang implementation of an x402 payment facilitator
x402 payment facilitator — verifies and settles EIP-3009 USDC micropayments on Base, Arbitrum, and Ethereum.
Off-chain credit ledger + hire marketplace for AI agents. Ed25519-signed envelopes, atomic settlement, hire-and-release escrow. https://voidly.ai/pay
go-x402-facilitator is a facilitator for X402 payment implemented in the Go language.
An x402 facilitator server for local development that works natively with Tenderly Virtual TestNets. Avoiding bottlenecks while working on public TestNets and using faucets for x402 development.