Documentation Index Fetch the complete documentation index at: https://docs.0xmeta.ai/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This guide walks you through setting up your merchant account to accept x402 payments via 0xmeta facilitator with pre-settlement fee collection.
Trust-Minimized: Customer payments go directly to your address. The facilitator collects a $0.01 fee from your pre-approved USDC balance after each settlement.
Prerequisites
Merchant Wallet
An Ethereum wallet address where you’ll receive customer payments
Private Key Access
Access to the wallet’s private key (for one-time USDC approval)
Base Network Assets
ETH : ~0.001 ETH for approval transaction gas
USDC : ~100 USDC for fees (covers ~10,000 settlements)
Network Selection
Choose Base Mainnet (production) or Base Sepolia (testing)
Step 1: Get Testnet Assets (For Testing)
If testing on Base Sepolia first (recommended):
Get Base Sepolia ETH
Visit the Coinbase faucet:
https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet
Connect your merchant wallet
Request 0.05 ETH (free, once per 24 hours)
Wait ~30 seconds for confirmation
Get Base Sepolia USDC
Visit the Circle faucet:
https://faucet.circle.com/
Select “Base Sepolia”
Enter your merchant address
Request 10 USDC
Wait ~1 minute for confirmation
Step 2: Approve USDC Spending
This is the only required setup step . You must approve 0xmeta’s treasury to collect the $0.01 fee per settlement from your USDC balance.
Why Approval Is Required
The facilitator uses standard ERC-20 transferFrom to collect fees:
// You approve facilitator (one-time)
USDC. approve (treasury, 100 * 10 ^ 6 ); // 100 USDC
// Later, facilitator collects fee per settlement
USDC. transferFrom (merchant, treasury, 0.01 * 10 ^ 6 ); // $0.01
This is how the facilitator maintains x402 trust-minimization:
✅ Customers pay YOU directly
✅ Facilitator collects fee from YOUR approved balance
✅ No facilitator custody of customer funds
Option A: Using Our Setup Script (Recommended)
1. Install dependencies:
npm install ethers dotenv
2. Configure environment:
# Create .env file
cat > .env << EOF
EVM_PRIVATE_KEY=0x... # Your merchant private key
NETWORK=sepolia # or mainnet
EOF
3. Run approval:
node approve-facilitator.mjs
Expected output:
📍 Base Sepolia
Merchant: 0xA821f428Ef8cC9f54A9915336A82220853059090
Treasury: 0x5D791e3554D0e83f171126905Bda1640Bf6f9A8B
💰 Approving 100 USDC for fee collection...
Transaction: 0x123...
✅ Approval successful!
Allowance: 100.0 USDC
Settlements: ~10000
Script source: See approve-facilitator.mjs
Option B: Manual Approval (via Etherscan)
1. Visit Base USDC contract:
Sepolia:
https://sepolia.basescan.org/address/0x036CbD53842c5426634e7929541eC2318f3dCF7e#writeContract
Mainnet:
https://basescan.org/address/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913#writeContract
2. Connect your wallet
3. Call approve function:
spender: 0x5D791e3554D0e83f171126905Bda1640Bf6f9A8B (Treasury)
amount: 100000000 (100 USDC with 6 decimals)
4. Confirm transaction
Approval amount = settlements × $0.01 Example: 100 USDC approval = 10,000 settlements
Step 3: Verify Setup
Check that approval was successful:
Expected output:
============================================================
0xmeta Facilitator Fee Allowance Check
============================================================
👤 Merchant: 0xA821f428Ef8cC9f54A9915336A82220853059090
🏦 Treasury: 0x5D791e3554D0e83f171126905Bda1640Bf6f9A8B
📍 Base Sepolia
USDC Balance: 500.00 USDC
Fee Allowance: 100.00 USDC
Settlements Remaining: 10000
✅ Good
============================================================
Script source: See check-allowance.mjs
Step 4: Integrate x402 Middleware
Now you’re ready to accept payments! Choose your x402 version:
Option A: x402 v2 (Recommended)
import { config } from "dotenv" ;
import express from "express" ;
import { paymentMiddleware , x402ResourceServer } from "@x402/express" ;
import { ExactEvmScheme } from "@x402/evm/exact/server" ;
import { HTTPFacilitatorClient } from "@x402/core/server" ;
config ();
const evmAddress = process . env . EVM_ADDRESS as `0x ${ string } ` ;
const facilitatorUrl = "https://facilitator.0xmeta.ai/v1" ;
const facilitatorClient = new HTTPFacilitatorClient ({ url: facilitatorUrl });
const app = express ();
app . use (
paymentMiddleware (
{
"GET /premium-content" : {
accepts: [{
scheme: "exact" ,
price: "$0.02" , // Your price + fee
network: "eip155:84532" , // Base Sepolia
payTo: evmAddress , // YOUR address
}],
description: "Premium content access"
}
},
new x402ResourceServer ( facilitatorClient )
. register ( "eip155:84532" , new ExactEvmScheme ())
)
);
app . get ( "/premium-content" , ( req , res ) => {
res . json ({ content: "This is premium content" });
});
app . listen ( 4021 , () => {
console . log ( "Server ready at http://localhost:4021" );
});
Option B: x402 v1 (Legacy)
import { config } from "dotenv" ;
import express from "express" ;
import { paymentMiddleware } from "x402-express" ;
config ();
const evmAddress = process . env . EVM_ADDRESS as `0x ${ string } ` ;
const facilitatorUrl = "https://facilitator.0xmeta.ai/v1" ;
const app = express ();
app . use (
paymentMiddleware (
evmAddress , // YOUR address
{
"GET /premium-content" : {
price: "$0.02" ,
network: "base-sepolia" ,
}
},
{ url: facilitatorUrl }
)
);
app . get ( "/premium-content" , ( req , res ) => {
res . json ({ content: "This is premium content" });
});
app . listen ( 4021 );
Step 5: Test Payment Flow
1. Start Your Server
2. Test with x402 Client
Create a test client (or use our example):
import { x402Client , wrapAxiosWithPayment } from "@x402/axios" ;
import { registerExactEvmScheme } from "@x402/evm/exact/client" ;
import { privateKeyToAccount } from "viem/accounts" ;
import axios from "axios" ;
const clientPrivateKey = process . env . CLIENT_PRIVATE_KEY as `0x ${ string } ` ;
const evmSigner = privateKeyToAccount ( clientPrivateKey );
const client = new x402Client ();
registerExactEvmScheme ( client , { signer: evmSigner });
const api = wrapAxiosWithPayment ( axios . create (), client );
// Make request
const response = await api . get ( "http://localhost:4021/premium-content" );
console . log ( response . data ); // { content: "This is premium content" }
3. Expected Flow
1. ✅ Client requests /premium-content
2. ✅ Server returns 402 Payment Required
Headers: payTo = YOUR_MERCHANT_ADDRESS
3. ✅ Client creates authorization to YOUR address
4. ✅ Client sends to facilitator
5. ✅ Facilitator verifies authorization
6. ✅ Facilitator collects $0.01 from YOUR approved balance
7. ✅ Facilitator executes: Customer → YOU ($0.02)
8. ✅ Server returns 200 OK + premium content
Success! If you see the premium content, your integration is working.
Step 6: Monitor Allowance
Set up regular monitoring to ensure you don’t run out of approved funds:
Manual Check
Automated Monitoring
Option 1: Cron job
# Check allowance daily at 9 AM
0 9 * * * cd /path/to/project && node check-allowance.mjs
Option 2: Application monitoring
import { ethers } from "ethers" ;
async function checkAllowance () {
const provider = new ethers . JsonRpcProvider ( "https://mainnet.base.org" );
const usdc = new ethers . Contract ( USDC_ADDRESS , USDC_ABI , provider );
const allowance = await usdc . allowance ( merchantAddress , treasuryAddress );
const settlements = Math . floor ( Number ( ethers . formatUnits ( allowance , 6 )) / 0.01 );
if ( settlements < 100 ) {
alert ( "Low allowance! Top up soon." );
}
return settlements ;
}
// Check every hour
setInterval ( checkAllowance , 3600000 );
Step 7: Production Deployment
When ready for production:
Switch to Base Mainnet
1. Update configuration:
// v2
network : "eip155:8453" // Base Mainnet
// v1
network : "base"
2. Run approval on mainnet:
# Get real USDC and ETH first!
NETWORK = mainnet node approve-facilitator.mjs
3. Update environment:
EVM_ADDRESS=0x... # Your mainnet address
NETWORK=mainnet
Production Checklist
Base Sepolia (Testnet)
Resource Value Chain ID 84532USDC 0x036CbD53842c5426634e7929541eC2318f3dCF7eTreasury 0x5D791e3554D0e83f171126905Bda1640Bf6f9A8BRPC https://sepolia.base.orgExplorer https://sepolia.basescan.orgFaucet https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet
Base Mainnet (Production)
Resource Value Chain ID 8453USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913Treasury 0x5D791e3554D0e83f171126905Bda1640Bf6f9A8BRPC https://mainnet.base.orgExplorer https://basescan.org
Troubleshooting
Approval transaction fails
Causes:
Insufficient ETH for gas
Wrong network selected
Invalid treasury address
Solution:
Check ETH balance: Must have ~0.001 ETH
Verify you’re on correct network
Double-check treasury address: 0x5D791e3554D0e83f171126905Bda1640Bf6f9A8B
Settlement fails with 'insufficient_allowance'
Cause: Approval wasn’t successful or depletedSolution: # Check current allowance
node check-allowance.mjs
# If zero, run approval again
node approve-facilitator.mjs
Cause: Network issues or RPC rate limitingSolution:
Wait a few minutes and try again
Use custom RPC (Alchemy, Infura, QuickNode)
Check Base network status
Customer payments not arriving
Cause: Wrong address in payToSolution:
Verify payTo uses YOUR merchant address (not treasury):// ✅ Correct
payTo : process . env . EVM_ADDRESS // Your address
// ❌ Wrong
payTo : "0x5D791e3554D0e83f171126905Bda1640Bf6f9A8B" // Treasury
Script References
Approval Script Reference
Save as approve-facilitator.mjs:
import { ethers } from "ethers" ;
import dotenv from "dotenv" ;
dotenv . config ();
const NETWORKS = {
sepolia: {
name: "Base Sepolia" ,
rpc: "https://sepolia.base.org" ,
chainId: 84532 ,
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" ,
},
mainnet: {
name: "Base Mainnet" ,
rpc: "https://mainnet.base.org" ,
chainId: 8453 ,
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" ,
},
};
const OXMETA_TREASURY = "0x5D791e3554D0e83f171126905Bda1640Bf6f9A8B" ;
const USDC_ABI = [
"function approve(address spender, uint256 amount) external returns (bool)" ,
"function allowance(address owner, address spender) external view returns (uint256)" ,
];
async function main () {
const networkKey = process . env . NETWORK || "sepolia" ;
const network = NETWORKS [ networkKey ];
const privateKey = process . env . EVM_PRIVATE_KEY ;
if ( ! privateKey ) {
console . error ( "❌ EVM_PRIVATE_KEY required" );
process . exit ( 1 );
}
const provider = new ethers . JsonRpcProvider ( network . rpc );
const merchant = new ethers . Wallet ( privateKey , provider );
const usdc = new ethers . Contract ( network . usdc , USDC_ABI , merchant );
console . log ( ` \n 📍 ${ network . name } ` );
console . log ( ` Merchant: ${ merchant . address } ` );
console . log ( ` Treasury: ${ OXMETA_TREASURY } ` );
const approvalAmount = ethers . parseUnits ( "100" , 6 ); // 100 USDC
console . log ( ` \n 💰 Approving 100 USDC...` );
const tx = await usdc . approve ( OXMETA_TREASURY , approvalAmount );
console . log ( ` Transaction: ${ tx . hash } ` );
await tx . wait ();
console . log ( `✅ Approval successful!` );
const allowance = await usdc . allowance ( merchant . address , OXMETA_TREASURY );
console . log ( ` Allowance: ${ ethers . formatUnits ( allowance , 6 ) } USDC` );
console . log ( ` Settlements: ~ ${ Math . floor ( Number ( ethers . formatUnits ( allowance , 6 )) / 0.01 ) } ` );
}
main (). catch ( console . error );
Monitoring Script Reference
Save as check-allowance.mjs:
import { ethers } from "ethers" ;
import dotenv from "dotenv" ;
dotenv . config ();
const NETWORKS = {
sepolia: {
name: "Base Sepolia" ,
rpc: "https://sepolia.base.org" ,
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" ,
},
mainnet: {
name: "Base Mainnet" ,
rpc: "https://mainnet.base.org" ,
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" ,
},
};
const OXMETA_TREASURY = "0x5D791e3554D0e83f171126905Bda1640Bf6f9A8B" ;
const USDC_ABI = [
"function allowance(address owner, address spender) external view returns (uint256)" ,
"function balanceOf(address account) external view returns (uint256)" ,
];
async function checkNetwork ( networkKey , merchantAddress ) {
const network = NETWORKS [ networkKey ];
const provider = new ethers . JsonRpcProvider ( network . rpc );
const usdc = new ethers . Contract ( network . usdc , USDC_ABI , provider );
const [ allowance , balance ] = await Promise . all ([
usdc . allowance ( merchantAddress , OXMETA_TREASURY ),
usdc . balanceOf ( merchantAddress ),
]);
const allowanceFormatted = ethers . formatUnits ( allowance , 6 );
const balanceFormatted = ethers . formatUnits ( balance , 6 );
const settlements = Math . floor ( Number ( allowanceFormatted ) / 0.01 );
console . log ( ` \n 📍 ${ network . name } ` );
console . log ( ` USDC Balance: ${ balanceFormatted } USDC` );
console . log ( ` Fee Allowance: ${ allowanceFormatted } USDC` );
console . log ( ` Settlements Remaining: ${ settlements } ` );
if ( settlements < 10 ) {
console . log ( ` ⚠️ Low - run: NETWORK= ${ networkKey } node approve-facilitator.mjs` );
} else if ( settlements < 50 ) {
console . log ( ` ⚠️ Getting low` );
} else {
console . log ( ` ✅ Good` );
}
}
async function main () {
const merchantAddress = process . env . EVM_ADDRESS ;
if ( ! merchantAddress ) {
console . error ( "❌ Set EVM_ADDRESS in .env" );
process . exit ( 1 );
}
console . log ( "============================================================" );
console . log ( "0xmeta Facilitator Fee Allowance Check" );
console . log ( "============================================================" );
console . log ( ` \n 👤 Merchant: ${ merchantAddress } ` );
console . log ( `🏦 Treasury: ${ OXMETA_TREASURY } ` );
const networkArg = process . argv [ 2 ] || "both" ;
if ( networkArg === "both" ) {
await checkNetwork ( "sepolia" , merchantAddress );
await checkNetwork ( "mainnet" , merchantAddress );
} else {
await checkNetwork ( networkArg , merchantAddress );
}
console . log ( " \n ============================================================" );
}
main (). catch ( console . error );
Next Steps
x402 Integration Complete integration guide
Architecture Understand payment flow
API Reference Explore endpoints
You’re all set! Start accepting x402 payments with pre-settlement fee collection.