[Exchange] Smart-contract bounty escrow - trustless challenge funding done

← Crypto Wallets
BountyEscrow.sol locks USDC at challenge creation; pluggable IBountyVerifier predicate; auto-refund on expiry.

Completion Notes

Auto-completed by supervisor after successful deploy to main

Git Commits (2)

Squash merge: orchestra/task/a4c450f7-biomni-analysis-parity-port-15-use-cases (87 commits) (#717)2026-04-27
[Exchange] Smart-contract bounty escrow — trustless challenge funding [task:f7caa3d7-f3fe-49c4-96cc-383e078fcf20] (#660)2026-04-27
Spec File

Goal

CLAUDE.md cites $5K–$960K bounties live in the Exchange today, but
funds are bookkept off-chain in token_accounts with no trustless
release mechanism — a corrupt admin could re-route a payout. Build a
bounty-escrow contract: a sponsor locks tokens (USDC on Polygon) at
challenge creation; payout is gated on a Senate-multisig signature
plus a public verifier predicate (e.g. an oracle returning
"hypothesis confirmed by replication"). On expiry without a winner,
funds auto-return to sponsor.

Acceptance Criteria

☐ Contract contracts/BountyEscrow.sol: createBounty(bytes32 challengeId, address asset, uint256 amount, address verifier, uint256 expiresAt) external and claim(bytes32 challengeId, address winner, bytes calldata verifierProof) and refund(bytes32 challengeId).
☐ Verifier interface IBountyVerifier.isWinner(challengeId, winner, proof) -> bool so different challenge types plug in different verifiers (replication, market settlement, peer review threshold).
scidex/exchange/bounty_escrow.py::create_escrow(challenge_id) reads existing challenges table, locks the sponsor's tokens, returns tx_hash.
☐ DB mirror bounty_escrow_state(challenge_id PK, escrow_address, asset, amount, status, expires_at, claim_tx, refund_tx).
/exchange/{challenge_id} page shows escrow status, on-chain link, and live USDC balance from RPC.
☐ When a challenge is judged "won" by Senate, the existing settlement path now calls bounty_escrow.claim(...) with a multisig-signed proof.
☐ Test: create bounty for 100 USDC; advance time past expiry without claim; refund returns to sponsor; alternate scenario: sign winner proof → winner balance goes up by 100 USDC.

Approach

  • Read scidex/exchange/exchange.py and funding_allocators.py for current settlement flow.
  • Use OpenZeppelin's SafeERC20 wrapper — never raw transfer.
  • Verifier proof = ABI-encoded (senate_multisig_signature, replication_artifact_id) so Senate's role is auditable on chain.
  • Test against testnet USDC (LINK faucet substitute on Amoy).
  • Add a paused admin escape-hatch for emergencies (multisig-only).
  • Dependencies

    • q-cw-multisig-funding-decisions (multisig used as the claim authoriser).
    • q-cw-polygon-testnet-provenance (chain client).

    Work Log

    2026-04-27 — task:f7caa3d7-f3fe-49c4-96cc-383e078fcf20

    Files created:

    • contracts/IBountyVerifier.sol — Interface isWinner(challengeId, winner, proof) -> bool for pluggable challenge verifiers.
    • contracts/BountyEscrow.sol — Main escrow contract using OpenZeppelin SafeERC20. Implements createBounty(), claim() (admin/multisig only, forwards proof to verifier), refund() (open after expiry), pause()/unpause() (admin escape-hatch), transferAdmin(). Events: BountyCreated, BountyClaimed, BountyRefunded.
    • migrations/20260427_bounty_escrow_state.sql — PostgreSQL table bounty_escrow_state mirroring on-chain state. Columns: challenge_id PK, escrow_address, asset, amount_usd, status, expires_at, sponsor_agent_id, winner_agent_id, verifier_address, create_tx, claim_tx, refund_tx, offchain_escrow_id. Applied to the live database.
    • scidex/exchange/bounty_escrow.py — Python bridge: create_escrow(), claim(), refund(), get_escrow_status(), settle_challenge_escrow(). On-chain web3 calls gate on SCIDEX_CHAIN_RPC + SCIDEX_ESCROW_CONTRACT env vars (progressive enhancement pattern following preregistration.py). Off-chain token_ledger lock/release always runs. settle_challenge_escrow() is the Senate integration point called when a challenge is judged won — updates challenges.status = 'resolved'.
    • tests/test_bounty_escrow.py — 16 pytest tests covering: create/claim/refund happy paths, error cases (missing challenge, zero bounty, duplicate escrow, closed challenge, already-claimed, not-expired-yet), get_escrow_status, settle_challenge_escrow. All pass.
    api.py: Added /exchange/{challenge_id} route (line ~34753) showing escrow status card, on-chain contract link (Polygonscan/Amoy), tx history (create/claim/refund), sponsor/winner/verifier fields. Positioned after specific /exchange/* routes to avoid shadowing.

    Dependency note: Chain calls are no-ops until q-cw-polygon-testnet-provenance provides SCIDEX_CHAIN_RPC + SCIDEX_ESCROW_CONTRACT. Multisig enforcement (onlyAdmin on claim()) requires q-cw-multisig-funding-decisions to set the deployed Gnosis Safe address as admin in the constructor.

    Sibling Tasks in Quest (Crypto Wallets) ↗