x402_middleware
+ shipped +switchboard/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 PR +switchboard/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
+ shipped +src/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.sol+ Trustless 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
+ shipped +switchboard/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
+ shipped +switchboard/nonce_manager.py+ Client-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
+ shipped +switchboard/adapters/a2a_x402.py+ Adapter 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
+ shipped +switchboard/x402/server.py+ Standalone 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
+ shipped +switchboard/mpp/session.py+ Multi-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
+ shipped +switchboard/mpc_wallet.py+ Multi-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
+ shipped +contracts/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
+ shipped +switchboard/adapters/lucidly.py+ syUSD 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.1 +docs/multi-chain-settlement.md · contracts forthcoming+ The 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) +}+ + +