@quaestor/bridge
v0.1.0
Published
Protocol translation layer: turns Quaestor mandate credentials into MPP / x402 / AP2 payment headers. Holds no keys, decides no policy.
Readme
quaestor-bridge
Demo
Phase 2.0 — Local policy LLM (latest). Intent enforcement, locally. Watch · Phase 1.5a
Pay any merchant, any protocol — without holding a key. quaestor-bridge is the stateless protocol translator that turns one verified Quaestor mandate into the wire format three different payment ecosystems each insist on (IETF MPP, Coinbase x402 v2, Google AP2 over x402). The bridge holds no key material, makes no policy calls, and runs only against a local quaestor-core daemon — every signature still happens behind the trust boundary, on your machine.
How it fits together
flowchart LR
Agent["Agent / App"] -->|"mandate JWT"| Bridge["quaestor-bridge<br/>(protocol translator)"]
Bridge -->|"POST /mandate/redeem<br/>X-Local-Auth"| Core["quaestor-core<br/>127.0.0.1:3402"]
Core -->|"x402-shape Credential"| Bridge
Bridge -->|"X-PAYMENT / Authorization: Payment /<br/>X-A2A-Payment"| Merchant["Merchant /<br/>Facilitator"]
Bridge -->|"POST /ledger/receipt"| Core
Core --> Vault[("Encrypted vault<br/>HD Ed25519")]
Core --> Ledger[("BLAKE3-chained<br/>SQLite WAL ledger")]What's working today
- ✅ Three adapters, one verified credential — MPP (
Authorization: Payment), x402 v2 (X-PAYMENT), AP2 (X-A2A-Paymentwrapping x402) - ✅ Multi-chain x402:
base-sepolia,solana-devnet,tempo-testnet(mainnets gated byQUAESTOR_MAINNET=1) - ✅ AP2 always delegates settlement to x402 —
vi.spyOnproves composition, not duplicated logic - ✅ Defensive SDK loading:
[email protected]and[email protected]ship emptydist/on npm; adapters raceimport()against a 2 s timeout and fall back to in-house IETF / x402-v2 builders - ✅ Receipt write-back returns
status=200against corev0.1.1 - ✅ Bridge holds no keys: the architectural grep test (
test/ap2.test.ts → "no key material leaks") runs every CI cycle - ✅ 36/36 tests pass,
tsc --noEmitclean - ✅ End-to-end demo runs in 0.2 s, exit code 0, ledger verify still OK afterwards
Quickstart
Install from npm:
pnpm add @quaestor/bridgepnpm install && pnpm build
pnpm test # 36/36 — fully mocked, no network
pnpm tsx examples/multi-protocol-pay.ts # routing demo (set DEMO_MANDATE for full E2E)Use Node 22 LTS — nvm use picks it up from .nvmrc. Engines pin: >=20 <23.
For the full end-to-end demo (live mandate → all three protocols → three receipts), start a local quaestor-core daemon first, then:
TOKEN=$(cat "${XDG_CONFIG_HOME:-$HOME/.config}/quaestor/auth.token")
DEMO_MANDATE=$(curl -s http://127.0.0.1:3402/mandate/request \
-H "X-Local-Auth: $TOKEN" -H 'Content-Type: application/json' \
-d '{"sub":"agent:demo","aud":"merch:demo","amount_max":50,"ttl_seconds":300,"use_counter_max":3,"purpose_tag":"inventory"}' \
| node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(JSON.parse(d).mandate))')
DEMO_MANDATE=$DEMO_MANDATE pnpm tsx examples/multi-protocol-pay.tsTrust boundaries
These are load-bearing. Violating any one of them is a regression, not a feature.
- The bridge holds no keys. Not at rest, not in memory, not in transit.
test/ap2.test.ts → "no key material leaks"greps every.ts/.jsfor private-key-shaped strings on every test run. - The bridge does not verify mandate JWTs. Signature, expiry, audience, use-counter — all checked by
quaestor-core'sPOST /mandate/redeem. - The bridge does not pick policy. Network classification, mainnet opt-in, ledger writes — all proxied through core or guarded by env vars the operator sets.
- Loopback-only access to core.
CoreClientdefaults tohttp://127.0.0.1:3402. The bridge does not expose a network-facing port itself. - Mainnet refused at the boundary. Every adapter constructor calls
assertMainnetAllowed()— no path touches a mainnet facilitator withoutQUAESTOR_MAINNET=1. - AP2 composes over x402, never reimplements it.
Ap2Adapter.paycallsX402Adapter.pay. If AP2 settlement changes, that change happens in one file. - No persistent state in the bridge. All state lives in core's ledger. The bridge restarting loses nothing.
Demo
90-second screen recording — one mandate, three protocols, one ledger:
Run the live demo
End-to-end on-chain x402 settlement against a real Coinbase facilitator on Base
Sepolia. Bridge holds zero keys throughout; core's POST /mandate/sign-x402
produces the EIP-3009 signature, the bridge formats the X-PAYMENT envelope
and posts it at the seller.
Start
quaestor-core(terminal 1):cd ~/code/quaestor-core nvm use && node ./bin/run.js startIssue a test mandate, get the settlement address, and fund it on Base Sepolia. The mandate's settlement address is the BUYER for the demo:
TOKEN=$(cat ~/.config/quaestor/auth.token) curl -sX POST http://127.0.0.1:3402/mandate/request \ -H "X-Local-Auth: $TOKEN" -H 'Content-Type: application/json' \ -d '{"sub":"agent:demo","aud":"merchant:demo-seller","amount_max":0.1,"currency":"USDC","network":"base-sepolia","ttl_seconds":600,"use_counter_max":3,"purpose_tag":"demo"}' # → { "mandate": "<jwt>", "claims": {"jti": "<jti>", ...}, "mandate_index": <n> } curl -s -H "X-Local-Auth: $TOKEN" \ "http://127.0.0.1:3402/mandate/<jti>/settlement-address" # → { "address": "0x...", "chain_id": 84532 }Send ~5 USDC (and a touch of ETH for fallback gas) on Base Sepolia to the returned
address. Verify onhttps://sepolia.basescan.org/address/<address>.Start the demo seller (terminal 2):
cd ~/code/quaestor-bridge DEMO_SELLER_PAY_TO=0xYourSellerPayoutAddress \ pnpm tsx examples/demo-seller/index.ts # listens on http://127.0.0.1:4021Run the live test (terminal 3) — re-uses the funded mandate via env:
RUN_LIVE=1 \ LIVE_MANDATE_JTI=<jti> \ LIVE_MANDATE_JWT=<jwt> \ DEMO_SELLER_PAY_TO=0xYourSellerPayoutAddress \ pnpm exec vitest run test/live/x402-onchain.test.tsThe test prints a Basescan URL on success. Bridge held zero keys; the architectural-invariant grep (
test/ap2.test.ts → "no key material leaks") continues to enforce that across every CI run.
More
- Trust-boundary diagram, verification flow, SDK fallback path, spec-drift log: ARCHITECTURE.md
- Status, verified capabilities, known gaps: STATUS.md
- Companion repo: quaestor-core — local daemon, vault, ledger
