x402_middleware
shippedswitchboard/x402_middleware.py
Server-side HTTP 402 gate. Drop into FastAPI or Flask,
point it at a treasury address and an accepted asset/network, and any
route you list is paywalled. The middleware verifies the caller's
X-PAYMENT envelope, emits the standard accepts[]
response on first hit, and passes verified requests through.
The wire format is the open x402 spec; the on-chain settlement is whichever rail the route accepts. Switchboard makes no assumption about which token or chain — the middleware's constructor is the place that decides.
parameters
| name | type | note |
|---|---|---|
| pay_to | str | treasury address that receives payment |
| asset | str | token contract address (USDC, USDT, custom — token-agnostic) |
| network | str | base-sepolia, base-mainnet, ethereum, avalanche-c, tron, lux-c… |
| price | str | amount in token's smallest unit per request |
| paths | list[str] | routes to gate. Globs supported. |
| accepted_algs | list[str] | signature algs to accept. Defaults: ecdsa-secp256k1 + dilithium3. |
example
from fastapi import FastAPI from switchboard.x402_middleware import X402Middleware app = FastAPI() app.add_middleware( X402Middleware, pay_to="0xYourTreasury...", asset="0x036CbD53842c5426634e7929541eC2318f3dCF7e", # USDC on Base Sepolia network="base-sepolia", price="1000", # 0.001 USDC per call paths=["/agent-only"], ) @app.get("/agent-only") def agent_only(): return {"ok": True}
zap_transport
in PRswitchboard/zap_transport.py
Binary wire format for PaymentOffer and
PaymentProof over luxfi/zap.
Zero-allocation; roughly an order of magnitude smaller than JSON. The
right choice when two agents exchange thousands of payment messages a
second — for one-off settlement, the JSON envelope is fine.
Switchboard is the first production consumer of zap_py,
the Python binding for ZAP. The wire schema is shared with
payment_protocol; ZAP is just an
alternative encoding.
example
from switchboard.zap_transport import ZapTransport from switchboard import PaymentOffer t = ZapTransport(endpoint="zap://peer.example:9601") offer = PaymentOffer( pay_to="0x...", asset="USDC", amount="1000", network="base-sepolia", ) # Send: serializes to ZAP binary, single syscall, no GC pressure proof = await t.request(offer)
payment_protocol
shippedsrc/payment_protocol.py
The shared schema for PaymentOffer and
PaymentProof. Every rail (x402 over HTTP, ZAP over
sockets, on-chain via AgentEscrow) speaks variants of these two
messages. The protocol module owns the canonical form and the
transcript hash used by signing.
Token-agnostic: asset is whatever the two parties agree
on. Chain-agnostic: network selects an adapter at the
consumer layer. PQ-ready: signature_alg + signature
are first-class fields, not optional add-ons.
PaymentOffer
@dataclass class PaymentOffer: pay_to: str asset: str # token contract or chain-native symbol network: str # base-sepolia, tron, avalanche-c, lux-c, ... amount: str # in token's smallest unit nonce: bytes # 16 random bytes deadline: int # unix seconds signature_alg: str = "dilithium3" signature: bytes = b""
PaymentProof
@dataclass class PaymentProof: offer_hash: bytes # transcript hash of the accepted offer tx_hash: str # on-chain tx (empty for off-chain rails) block: int | None signature_alg: str = "dilithium3" signature: bytes = b""
contracts/AgentEscrow.solTrustless escrow for agent-to-agent payments. Caller locks funds with a deadline and a challenge period; provider claims on delivery; either side can dispute inside the challenge window. Native-ETH variant has no token wrapper at all — pay, claim, refund all happen in native ETH.
The Python client (src/payment_protocol.py) wraps the
contract in a clean async interface; the CLI ships the same flows for
shell use. The native-ETH variant is formalized as a Standards-Track
ERC (#50).
solidity surface
function deposit(address provider, uint256 deadline, uint256 challenge) external payable returns (uint256 escrowId); function claim(uint256 escrowId, bytes calldata receipt) external; function dispute(uint256 escrowId, bytes calldata evidence) external; function refund(uint256 escrowId) external; // after deadline + challenge function cancelMutual(uint256 escrowId, bytes calldata peerSig) external;
python flow
from switchboard.escrow import AgentEscrow esc = AgentEscrow(network="base-sepolia", contract="0x...") # caller: lock 0.01 ETH for the provider, 1 hour deadline + 1 hour challenge escrow_id = await esc.deposit( provider="0xPeer...", value=eth("0.01"), deadline=hours("1"), challenge=hours("1"), ) # provider: claim with receipt await esc.claim(escrow_id, receipt=signed_receipt)
gas_budget · gas_tracker
shippedswitchboard/gas_budget.py · switchboard/gas_tracker.py
Hard budgets — per-hour, per-day, per-network — that an agent cannot
exceed even if its policy goes haywire. gas_tracker
records what's been spent; gas_budget rejects the next
tx if it would breach the budget. Closes the #1 footgun of autonomous
on-chain agents: runaway loops burning their own treasury.
Tracker is in-memory by default but takes a backing store (SQLite, Redis) for multi-process agents.
example
from switchboard.gas_budget import GasBudget from switchboard.gas_tracker import GasTracker tracker = GasTracker() # in-memory budget = GasBudget( tracker=tracker, per_hour=gwei("500_000"), per_day =gwei("5_000_000"), ) if not budget.would_allow(network="base-sepolia", est_gas=tx_gas): raise BudgetExceeded() tracker.record(network="base-sepolia", used=tx_gas)
nonce_manager
shippedswitchboard/nonce_manager.pyClient-side nonce manager with reorg protection. Keeps a local view of the next nonce per (address, network); on reorg, rolls back. The piece every shipping autonomous agent eventually has to write — written once, here, with the edge cases (replacement tx, stuck mempool, multi-network parallelism) handled.
example
from switchboard.nonce_manager import NonceManager nm = NonceManager(rpc=rpc_client) async with nm.reserve(address, network="base-sepolia") as n: tx = build_tx(nonce=n, ...) await rpc.send(tx) # NonceManager auto-commits on success, rolls back on reorg or send error
switchboard/pq.py · switchboard/pq_keys.py
Wrapper over liboqs
providing Dilithium / Falcon / SPHINCS+ signatures with a single
API. Default algorithm is Dilithium3 (NIST level 3, balanced
speed/size). Algorithm is carried explicitly in every
PaymentOffer and PaymentProof; switchboard
never has to guess what to verify with.
Install with pip install switchboard[pq] — the [pq]
extra pulls liboqs-python. The wire schema for
signature_alg + signature is fixed in
PQ-5.
example
from switchboard.pq import sign, verify, ALG_DILITHIUM3 from switchboard.pq_keys import generate, load # once, off-thread: generate and save a PQ keypair kp = generate(alg=ALG_DILITHIUM3) kp.save("./agent.pq.json") # at runtime: sign an offer's canonical transcript kp = load("./agent.pq.json") sig = sign(offer.canonical(), kp) # peer side: verify assert verify(offer.canonical(), sig, kp.public, alg=offer.signature_alg)
a2a_x402 adapter
shippedswitchboard/adapters/a2a_x402.pyAdapter between Google's A2A (Agent-to-Agent) framework and the open x402 protocol. Lets an A2A agent get paid in x402; lets an x402 caller invoke an A2A agent. Translates message envelopes, preserves the PQ signature, and respects the gas budget on both sides.
example
from switchboard.adapters.a2a_x402 import A2AOverX402 bridge = A2AOverX402( a2a_endpoint="https://my-agent.example/a2a", x402_pay_to="0x...", asset="USDC", network="base-sepolia", ) result = await bridge.invoke(task=task, max_payment=usdc("0.05"))
x402.server
shippedswitchboard/x402/server.pyStandalone HTTP 402 server. The middleware variant (x402_middleware) wraps an existing FastAPI/Flask app; this module ships a complete server you can run as its own process — useful when the paid endpoint isn't part of a larger application.
mpp.session
shippedswitchboard/mpp/session.pyMulti-party payment sessions. Open a streaming channel between two agents under a budget cap; stream micro-pays as work is performed; settle on close. The Tempo / Stripe-style rail for long-running agent collaborations.
mpc_wallet
shippedswitchboard/mpc_wallet.pyMulti-party-computation wallet — threshold signatures across an agent fleet, so no single machine holds the spending key. Useful for high-value treasuries that have to survive any one node being compromised.
OracleAggregator
shippedcontracts/IOracleAggregator.sol · AgentEscrow.releaseByAttestation()Oracle-mediated escrow release. A designated oracle aggregator attests that delivery occurred; the contract releases funds without requiring on-chain dispute resolution. The fast path for the common case (provider delivers, no contest).
adapters/lucidly
shippedswitchboard/adapters/lucidly.pysyUSD auto-park for idle agent balances. Any USDC the agent isn't actively using gets parked into Lucidly's yield-bearing syUSD position by default, then pulled back automatically when the agent needs to make a payment. Idle treasury earns yield without manual management.
multi-chain settlement
design v0.1docs/multi-chain-settlement.md · contracts forthcomingThe chain-agnostic settlement surface. Agents transact on whichever chain fits the job (TRON for sub-cent fees, AVAX for liquidity, Base for USDC, ETH for native escrow); switchboard adapters present a uniform interface so payment code never branches per-chain.
Chosen path: notary attestation for v1 (low-cost, production-ready today), upgradable in-place to ZK-relay for v2 (Groth16 proofs on LUX, trustless cross-chain verification). TRON finality at 19 blocks, Avalanche C-Chain at 5, LUX at 2.
Full spec — envelope schema, per-chain verification paths, finality
tables, and considerations — in
docs/multi-chain-settlement.md. Resolves
#59.
envelope schema
envelope { source_chain: string // CAIP-2 (e.g. "eip155:1", "tron:mainnet") destination_chain: string request_id: string // UUIDv4 payload: bytes // serialized PaymentRequest / PaymentProof source_block: uint64 source_tx: bytes32 attestation: bytes // notary signature or ZK proof attestation_type: uint8 // 0x01 = notary (ECDSA), 0x02 = ZK (Groth16) }