No escrow contract is deployed on Production. Production settlement is manual: the poster pays each winner directly in USDC on Base and records the transaction hash (see Payment rails). The escrow code path (deposit, lock, settle, refund) exists and has been run end to end only on Preview deployments, on Base Sepolia and Arc Testnet, with test USDC.
Check the live gate#
curl -s https://birdfury.com/api/escrow/readiness | jq '.ready, .mode, .escrowAddress, .blockers'On Production this returns HTTP 503 with ready: false, mode: "manual_alpha", escrowAddress: null, network: "base" and the blocker mission_escrow_address_missing. The response also says settlementMode: "manual_settlement" and fundsLocked: false: readiness never claims a mission's funds are locked.
ready: true needs a valid escrow address, a valid USDC address for the network, the pinned Base chain id and USDC address unchanged, and (at runtime) an RPC on the right chain that finds deployed bytecode at that address whose usdc() and operations() match the configuration.
What a mission's money state means#
| Field | Meaning |
|---|---|
funded / fundingUrl | A link the poster published. Birdfury does not check it. Never an escrow lock |
moneyTruth | What escrow actually did: NONE · MANUAL_ALPHA · LOCKED · RELEASED · REFUNDED · DRIFT |
settlement.mode | manual_alpha or escrow, decided per mission from its own escrow slots |
Each escrow label needs its own transaction: a lock tx for LOCKED, a payout tx for RELEASED, a refund tx for REFUNDED. A mission without a locked slot is paid by hand even on a deployment that has an escrow address.
Preview testnets#
| Network | Chain ID | Escrow | Token |
|---|---|---|---|
| Base Sepolia | 84532 | Preview only | Test USDC (6 decimals) |
| Arc Testnet | 5042002 | Preview only | ERC-20 USDC 0x3600… (6 decimals); native gas USDC has 18 decimals and is never the escrow token |
| Base mainnet | 8453 | Not deployed | — |
ESCROW_NETWORK selects the Preview rail (base-sepolia or arc-testnet); Production stays base with no escrow address. Testnet USDC has no value. Missions titled [TESTNET] or marked test: true are QA rows. See Networks for RPCs, explorers and the Preview contract address.
With BASE_BUILDER_CODE set (Preview uses bc_ybtb70ew), escrow calls on Base Sepolia end their calldata with an ERC-8021 attribution suffix. Arc Testnet calls never carry it. This documents the code value only, not any Base dashboard registration.
How the escrow path works (Preview)#
Prepare#
POST /api/missions/[id]/fund/prepare returns the chain, USDC token, escrow address and exact amount. It refuses with 503 while readiness is not clear.
Deposit#
The poster signs a USDC approval and the escrow deposit from their own wallet. The amount is the reward only; nothing is added on top.
Confirm#
POST /api/missions/[id]/fund/confirm with the transaction hash. The server reads the receipt, requires the escrow's Deposited event and a matching USDC transfer, then marks the slot LOCKED with the lock tx.
Settle#
After review, the settlement worker builds a plan from the weighted allocations and calls settle(). On a locked mission the shares must add up to the whole reward; a short split is refused, because the contract would pay the remainder as a fee. Recipients that nobody can spend from are refused.
Refund#
An expired, unawarded locked mission can be refunded to the depositor after deadline + ESCROW_REFUND_GRACE_MS. On Production refund stays disabled until ESCROW_REFUND_POLICY_APPROVED=true; Preview testnets default to approved with no grace period.
Every amount is checked in integer micro-USDC units. The worker is idempotent: an already settled mission is not paid again.
The settlement worker is /api/cron/escrow-settle, called every 10 minutes by a launchd job on Birdfury's ops machine (vercel.json has no crons key, so nothing runs it on Vercel). On Production it answers 503 blocked with the readiness blockers and does nothing.
Safety boundary#
- The contract enforces the arithmetic:
settle()reverts unless reward plus fee equals that mission's own deposit, and the fee goes only to theoperationsaddress fixed at deploy. The settlement agent's key can spend only its own gas; it cannot reach any other balance. - The escrow settlement cron is the only server code that can sign a transaction. Birdfury's wallets otherwise only receive.
- The Operations wallet receives fees; it is not an escrow. A plain wallet address must never be placed in
NEXT_PUBLIC_MISSION_ESCROW_ADDRESS. - Fee collection is off during alpha, so a settle carries a 0 fee today.
The live escrow configuration is public: GET https://birdfury.com/api/escrow/readiness reports the network, the escrow address (null in Production) and every blocker, without printing any secret.
Updated 2026-10-27