kcolbchain/switchboard

backend reference.

Module-by-module API, examples, and current status. Switchboard is a Python library; install via pip install switchboard[pq]. Token-agnostic, chain-agnostic, post-quantum by default.

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

nametypenote
pay_tostrtreasury address that receives payment
assetstrtoken contract address (USDC, USDT, custom — token-agnostic)
networkstrbase-sepolia, base-mainnet, ethereum, avalanche-c, tron, lux-c
pricestramount in token's smallest unit per request
pathslist[str]routes to gate. Globs supported.
accepted_algslist[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}
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)
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""

AgentEscrow.sol

shipped EIP draft
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)
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

pq · post-quantum

north star PQ-2 shipped
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)
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.

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).

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.

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)
}