open_wager, a wager needs two more transactions. Both are permissionless and prepaid, so anyone can send them: the app, the user, the public keeper, or any other cranker.
The timeline
T_SETTLE is 150 slots, about a minute. It bounds how long Fade waits for randomness, never how long anyone has to settle: a fulfilled wager stays settleable indefinitely, and expire_wager refuses a wager whose randomness has arrived (AlreadyFulfilled).
Why “open slot + 2”
The oracle seed must come from a block hash that did not exist when the wager opened.request_randomness takes s*, the first slot present in SlotHashes after the open slot, and its hash h*, and derives the seed on-chain:
Expiry is forfeiture
If the randomness is never fulfilled withinT_SETTLE, expire_wager closes the wager and the pool keeps the pool credit. The stake is not returned: a return would let anyone who can withhold a draw (an oracle key holder colluding with a user) cancel every losing wager for free. Under forfeiture, withholding gains nothing. The guardian can pause new wagers when the oracle stalls, with an operating target of 60 slots, well before 150; that bounds how many honest wagers a long oracle outage can reach, it does not remove the exposure. The SOL side still comes back to the payer at expiry, except what was spent.
A beneficiary that cannot receive
If the beneficiary’s USDC account cannot receive the payout at settlement (closed, frozen, or not a USDC account any more), settlement still succeeds: the payout moves to the program’s payout escrow, the wager entersPayoutPending, and anyone can later call claim_payout to deliver it to the same beneficiary. Settlement never fails because of the beneficiary: a wager that could not settle would block the pool’s epoch pricing for everyone.
The public keeper
Fade runs a keeper athttps://api.fade.finance. It holds no authority over any wager; it is simply a cranker that is always on. On devnet it also serves the test USDC faucet.
Browsers can call the API only from Fade’s own app origins (CORS). Call it from your server, or run your own keeper.
POST /v1/wagers
Hands a wager to the keeper. It checks that the address is a Fade wager account and queues it.
Posting is optional: the keeper also sweeps the chain about every 30 seconds for open wagers nobody posted. Posting right after the open confirms starts it at once.
GET /v1/wagers/:address
The keeper’s record of a wager. An untracked wager that is still open on-chain is queued on the way.
GET /v1/health
lastTick is a Unix time in milliseconds; queue is the number of wagers in progress. rpcError appears when the keeper cannot read the chain.
Faucet (devnet)
One claim per wallet and per IP address every 5 minutes, and a global daily limit.
Running your own keeper
A keeper needs a funded key and a loop. It earns the crank fees of the wagers it advances.- Find work. Fetch the program’s
Wageraccounts (getProgramAccountsfiltered on theWagerdiscriminator) or subscribe to the program’s transactions and decodeWagerOpenedevents. - Request. For each wager with
status = OpenandcurrentSlot ≥ openSlot + 2, derive the seed and sendrequest_randomness(see the Quickstart code). - Settle. For each wager with
status = Requestedwhose ORAO request account is fulfilled, sendsettle_wager. ForPayoutPendingwagers, retryclaim_payoutfrom time to time. - Expire. For each wager past
expirySlotwhose randomness is unfulfilled, sendexpire_wager. - Liquidity side. Strike the live epoch once its close has passed and no wager opened before the close is still open (
strike_epoch), then claim struck requests (claim_deposit,claim_withdraw). Each step is paid; see How the pool works.