x402-fl
v0.6.0
Published
Local x402 payment facilitator for development and testing. Fork Base mainnet with Anvil, verify and settle x402 payments locally.
Maintainers
Readme
x402-fl: Local x402 Payment Facilitator
A local x402 facilitator for development and testing. One command gives you a fully functional x402 payment environment: a forked Base mainnet via Foundry Anvil paired with a local facilitator server. Build and test x402-powered apps without touching real funds.
Warning: Local development only. Uses Anvil's deterministic private keys, which are publicly known and provide zero security. Do not use in production.
Note: Currently supports USDC on Base and Base Sepolia. Custom ERC-20 token support is on the roadmap.
What it does
npx x402-fl devThat's it. Behind the scenes, x402-fl:
- Forks Base mainnet using Anvil with real USDC contract state, at zero cost
- Starts a facilitator server that verifies and settles x402 payments locally
- Provides a
fundcommand to mint USDC to any address instantly
Prerequisites
- Node.js 20+
- pnpm
- One of:
- Foundry (recommended, provides
anvil) - Docker (fallback, runs Anvil in a container; also required for Testcontainers)
- Foundry (recommended, provides
Quick Start
Try it instantly with npx:
npx x402-fl devOr install globally:
npm install -g x402-fl
x402-fl devThis starts Anvil on port 8545 and launches the facilitator on port 4022.
From source
git clone https://github.com/kevzzsk/x402-fl.git
cd x402-fl
pnpm install
pnpm devFund an account with 100 USDC:
x402-fl fund 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 100CLI Commands
x402-fl dev
Start Anvil fork + facilitator server for local x402 development.
x402-fl dev [options]| Flag | Default | Description |
| ----------------------- | -------------------------- | -------------------------------------------------------------------- |
| --port <number> | 4022 | Facilitator HTTP port |
| --anvil-port <number> | 8545 | Anvil RPC port (first instance; additional instances use next ports) |
| --network <name...> | base | Network preset(s) to fork — repeatable (e.g. --network base --network base-sepolia) |
| --rpc-url <url> | https://mainnet.base.org | RPC URL to fork (overrides the first --network preset's default) |
| -v | | Show facilitator request logs |
| -vv | | Show facilitator request logs and Anvil process logs |
| --private-key <key> | | Custom facilitator private key (prefer default Anvil account) |
The --network flag controls which chains get a local Anvil fork. Specify it multiple times to run multiple Anvil instances (e.g. --network base --network base-sepolia). The facilitator always registers all supported schemes across all configured networks, so GET /supported will list every scheme/network combination.
Warning:
--private-keyis not recommended
Note: anvil state is not persisted.
x402-fl fund
Fund any address with USDC on the local Anvil fork.
x402-fl fund <address> <amount> [options]| Argument / Flag | Default | Description |
| ----------------------- | -------- | ---------------------------------------------------------------- |
| <address> | required | 0x-prefixed Ethereum address to fund |
| <amount> | required | USDC amount in major units (human-readable, e.g. 100 or 1.5) |
| --anvil-port <number> | 8545 | Anvil RPC port |
x402-fl balance
Check USDC balance for an address on the local Anvil fork.
x402-fl balance <address> [options]| Argument / Flag | Default | Description |
| ----------------------- | -------- | ---------------------------- |
| <address> | required | 0x-prefixed Ethereum address |
| --anvil-port <number> | 8545 | Anvil RPC port |
Facilitator API Endpoints
The facilitator server exposes the following HTTP endpoints (default http://localhost:4022):
POST /verify
Verify an x402 payment payload without settling it.
Request body:
{
"paymentPayload": { ... },
"paymentRequirements": { ... }
}Response: Verification result from the facilitator.
POST /settle
Settle (execute) a verified x402 payment on-chain.
Request body:
{
"paymentPayload": { ... },
"paymentRequirements": { ... }
}Response: Settlement result with transaction details.
GET /supported
List supported payment schemes and networks.
Response:
{ ... }GET /health
Health check endpoint.
Response:
{
"status": "ok",
"networks": ["eip155:8453", "eip155:84532"],
"facilitator": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
}Docker
A pre-built Docker image is available on GitHub Container Registry:
docker pull ghcr.io/kevzzsk/x402-fl:latestRun it directly:
docker run -p 4022:4022 -p 8545:8545 ghcr.io/kevzzsk/x402-fl:latestOverride the fork RPC URL:
docker run -p 4022:4022 -p 8545:8545 ghcr.io/kevzzsk/x402-fl:latest --rpc-url https://your-rpc-url.com| Tag | Description |
| -------- | ----------------------------------------------------- |
| latest | Latest stable release |
| next | Pre-release builds (e.g. 1.0.0-beta.1) |
| x.y.z | Pinned version (e.g. ghcr.io/kevzzsk/x402-fl:0.1.0) |
Testcontainers
x402-fl ships a Testcontainers module so you can spin up a fully isolated x402 environment in integration tests. The container pulls the GHCR image by default.
Install
The testcontainers package is an optional peer dependency:
pnpm install -D testcontainersUsage
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import {
X402FacilitatorLocalContainer,
type StartedX402FacilitatorLocalContainer,
accounts,
} from "x402-fl/testcontainers";
describe("x402 integration", () => {
let container: StartedX402FacilitatorLocalContainer;
beforeAll(async () => {
container = await new X402FacilitatorLocalContainer().start();
});
afterAll(async () => {
await container?.stop();
});
it("facilitator is healthy", async () => {
const res = await fetch(`${container.getFacilitatorUrl()}/health`);
expect(res.status).toBe(200);
});
it("funds an address with USDC", async () => {
await container.fund(accounts.facilitator.address, "100");
});
});API
X402FacilitatorLocalContainer
| Method | Description |
| ------------------------------------------- | -------------------------------------------------------------- |
| new X402FacilitatorLocalContainer(image?) | Create a container (default: ghcr.io/kevzzsk/x402-fl:latest) |
| .withNetworkPreset(...names) | Select network preset(s) — pass multiple for multi-Anvil (chainable) |
| .withForkUrl(url) | Set a custom RPC URL to fork (chainable) |
| .start() | Build the image (if needed) and start the container |
StartedX402FacilitatorLocalContainer
| Method | Description |
| ----------------------------------- | ---------------------------------------------------------------- |
| .getRpcUrl(network?) | Anvil RPC endpoint (pass network name for multi-network setups) |
| .getFacilitatorUrl() | Facilitator HTTP endpoint (http://host:port) |
| .fund(address, amount, network?) | Mint USDC to an address (network name selects Anvil instance) |
| .balance(address, network?) | Get USDC balance (network name selects Anvil instance) |
| .getPublicClient(network?) | Get a viem PublicClient (network name selects Anvil instance) |
| .stop() | Stop and remove the container |
Note: The first call to
.start()pulls the Docker image from GHCR, which may take a moment. Subsequent calls reuse the cached image. You can pass a custom image tag to the constructor if needed.
Test Accounts
The facilitator uses Anvil's default deterministic account (index 0). Do not use these keys for anything real.
| Role | Address |
| ----------- | -------------------------------------------- |
| Facilitator | 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 |
Any address can be funded using x402-fl fund.
Roadmap
- [ ] Custom Asset support (EIP-3009 tokens)
- [ ] Custom Fork url
- [ ] Custom Fork block height
- [x] Dockerised anvil node (fallback when Foundry is not installed)
- [x] Testcontainers for facilitator + anvil for deterministic testing env
- [ ] Support all anvil args and passdown the args
- [ ] Support SVM
Issues
Found a bug or have a feature request? Open an issue.
License
MIT
