Skip to content

Local Development & Testing

Try tollbooth locally with a dummy API — no wallets or real payments needed.

Terminal window
git clone https://github.com/Loa212/x402-tollbooth.git
cd x402-tollbooth
bun install
Terminal window
bun run examples/dummy-api.ts

This starts a fake API on http://localhost:4000 with three endpoints:

EndpointResponse
GET /weatherWeather data
POST /chatEchoes back the model name
GET /data/:idMock query results

In a second terminal:

Terminal window
bun run src/cli.ts -- --config=examples/tollbooth.config.dev.yaml

Tollbooth starts on http://localhost:3000 and proxies to the dummy API with x402 payment requirements.

In a third terminal:

Terminal window
bun run examples/test-client.ts

This fires requests at tollbooth and prints the 402 responses. You should see different prices depending on the route:

RequestPriceWhy
GET /weather$0.01 (10,000)Static price
POST /chat body model: "haiku"$0.005 (5,000)Body-match rule
POST /chat body model: "opus"$0.075 (75,000)Body-match rule
GET /data/12345$0.05 (50,000)Param extraction + static price
GET /.well-known/x402200V2 discovery metadata
GET /health200Health check

Since no real wallet is signing payments, every request gets a 402 back with the PAYMENT-REQUIRED header — which is exactly what you want to verify.


Run a full payment cycle on Base Sepolia testnet: GET /weather → 402 → sign → pay → 200 with tx hash.

You need two separate Ethereum wallets:

  • Payer wallet — the “buyer” that signs and pays for requests. Must hold testnet USDC.
  • Gateway wallet — the “seller” that receives USDC. Can be any address (no funds needed).
Terminal window
# Using cast (install Foundry: https://getfoundry.sh)
cast wallet new # run twice — one payer, one gateway
Terminal window
cp .env.test.example .env.test
.env.test
# Payer wallet (the "buyer") — needs testnet USDC
TEST_PRIVATE_KEY=0x... # private key of the payer wallet
TEST_WALLET_ADDRESS=0x... # public address of the payer wallet
# Gateway wallet (the "seller") — receives USDC payments
TEST_GATEWAY_ADDRESS=0x... # must be a different address from TEST_WALLET_ADDRESS

The payer wallet needs USDC on Base Sepolia. The x402 facilitator sponsors gas, so you only need USDC — no ETH required.

  1. Go to the Circle USDC Faucet
  2. Select Base Sepolia as the network
  3. Paste your payer wallet address and request USDC

USDC contract on Base Sepolia: 0x036CbD53842c5426634e7929541eC2318f3dCF7e

Open three terminals:

Terminal 1 — dummy upstream:

Terminal window
bun run examples/dummy-api.ts

Terminal 2 — tollbooth gateway:

Terminal window
bun run --env-file=.env.test src/cli.ts start --config=examples/tollbooth.config.e2e.yaml

Terminal 3 — e2e test:

Terminal window
bun run --env-file=.env.test examples/e2e-payment.ts
🔑 Payer wallet: 0xYourPayerAddress
Network: Base Sepolia (chain 84532)
USDC contract: 0x036CbD53842c5426634e7929541eC2318f3dCF7e
Gateway: http://localhost:3000
── Step 1: GET /weather (expect 402) ──
✓ Got 402 with payment requirements:
scheme: exact
network: base-sepolia
asset: 0x036CbD53842c5426634e7929541eC2318f3dCF7e
maxAmountRequired: 1000 (0.001 USDC)
payTo: 0xYourGatewayAddress
maxTimeoutSeconds: 300
── Step 2: Sign EIP-3009 transferWithAuthorization ──
✓ Payment signed
── Step 3: Resend GET /weather + payment-signature (expect 200) ──
Status: 200
── Step 4: Verify payment-response header ──
✅ E2E test passed!
──────────────────────────────────────────────────────────────
Tx hash: 0xabc123...
Network: base-sepolia
Payer: 0xYourPayerAddress
Amount: 1000 raw units
──────────────────────────────────────────────────────────────
🔗 View on Basescan: https://sepolia.basescan.org/tx/0xabc123...

Next: How x402 Works →