feat(bounty): real escrow — Permit2 vouchers, x402-escrow facilitator, drand panel seeds, decay, escalation, on-chain grounding#635
Conversation
Live Base Sepolia escrow smoke — PASSED ✅ (reserve → capture, real Permit2)Run against a live k3d cluster with this branch's Sequence: On-chain evidence (Base Sepolia):
Non-custodial property held throughout: funds stayed in the poster's wallet from One minor gap noted for follow-up: the facilitator has no |
…tator, drand seeds, decay, escalation, grounding Squash of: bc00584 (escrow slice) + 59ce619 (technical spec, plans/servicebounty-technical-spec.md)
1418c14 to
f6e5c1b
Compare
Stack position
PR 2 of 2, stacked on #634 (
feat/servicebounty-eval-market). Review #634 first; this PR's diff is only the escrow leg. The full architecture is documented inplans/servicebounty-technical-spec.md(included here) — it is the canonical reference for this stack.What
Replaces the dev-ledger escrow seam from #634 with real, non-custodial money movement on Base Sepolia:
internal/x402/escrow/permit2.go): the poster signs an EIP-712PermitBatchTransferFromvoucher agent-side (remote signer, or--keyfor dev) covering the bounty's seats (reward / bond / eval / escalation legs). Deterministic nonces (keccak256(uid|leg)) make re-funding idempotent and replay-proof. Funds never leave the poster's wallet until capture — the facilitator holds signatures, not money.cmd/x402-escrowfacilitator: new in-cluster service (x402 ns, ClusterIP-only, port 8403, distroless). Routes:POST /escrow/reserve|capture|void/{id},GET /escrow/info,/healthz; bearer-authed (constant-time). Capture recipients must be a subset of the voucher's signed seats with exact amounts — funds flow poster → recipients directly through Permit2; the facilitator only pays gas.obol.org/{reward,bond,eval,eval-r1}-voucher); the controller ferries them to the facilitator and never signs anything. Escrow URL/token reach the controller via env only.drandquicknet beacon (round strictly after bounty creation + 30 s), BLS-verified in-process; provenance (round, randomness, signature) recorded in status. A failed beacon fetch requeues — there is no silent local-randomness fallback (test-pinned anti-grinding property).Trust model (documented, test-named)
v1 residue: voucher recipients are facilitator-policy-bound, not signature-bound (Permit2 SignatureTransfer lets the spender pick
to; our facilitator enforces the seat-subset rule —TestVoucherRecipientAddressIsPolicyBoundNotSignatureBoundpins this honestly). The upgrade path is a witness+disperse contract; spec §5 covers it.Validation
🤖 Generated with Claude Code