Quickstart¶
This guide gets you to a first successful 402 payment in about 5 minutes using the x402 rail on Base Sepolia testnet (no real funds required).
1. Install¶
Python 3.11+ required.
2. Get a testnet wallet¶
You need a Base Sepolia wallet with some testnet USDC and ETH for gas.
# Generate a new wallet (or use an existing key)
python -c "from eth_account import Account; a = Account.create(); print(a.key.hex(), a.address)"
Fund it:
- Testnet ETH (for gas): Alchemy Base Sepolia faucet or QuickNode
- Testnet USDC: Circle faucet — select Base Sepolia
3. Write the code¶
import asyncio
import os
from eth_account import Account
from routeweiler import Routeweiler, Funding
from routeweiler.trace.sink_sqlite import TraceSink
async def main():
# Load wallet from env var (never hard-code private keys)
signer = Account.from_key(os.environ["WALLET_PK"])
async with Routeweiler(
funding=[Funding.base_sepolia_usdc(wallet=signer)],
trace_sink=TraceSink.sqlite("rw.db"), # optional — enables local trace log
) as client:
response = await client.get("https://YOUR_X402_ENDPOINT")
print(response.status_code, response.json())
asyncio.run(main())
4. What happens under the hood¶
GET https://YOUR_X402_ENDPOINT
→ 402 Payment Required (with PAYMENT-REQUIRED header)
Routeweiler parses x402 challenge
Signs ERC-3009 transferWithAuthorization
Retries with PAYMENT-SIGNATURE header
→ 200 OK
Trace event written to rw.db
The whole flow is synchronous from your code's perspective — await client.get(url) returns the 200 response as if the 402 never happened.
5. Inspect the trace¶
import sqlite3, json
conn = sqlite3.connect("rw.db")
rows = conn.execute("SELECT * FROM trace_events").fetchall()
for row in rows:
print(json.loads(row[3])) # payload column
conn.close()
Key fields in the payload:
{
"selectedRail": "x402",
"payment": {
"proofType": "txid",
"proofValue": "0xabc...def",
"amountNative": 100,
"amountNativeCurrency": "eip155:84532/erc20:0x036..."
},
"outcome": { "httpStatus": 200, "serviceDelivered": true }
}