Skip to main content
After 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:
That hash is available from two slots after the open. The caller never supplies the seed: the program derives it and checks that the ORAO request account passed is the one for that seed. See Randomness.

Expiry is forfeiture

If the randomness is never fulfilled within T_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 enters PayoutPending, 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 at https://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.
  1. Find work. Fetch the program’s Wager accounts (getProgramAccounts filtered on the Wager discriminator) or subscribe to the program’s transactions and decode WagerOpened events.
  2. Request. For each wager with status = Open and currentSlot ≥ openSlot + 2, derive the seed and send request_randomness (see the Quickstart code).
  3. Settle. For each wager with status = Requested whose ORAO request account is fulfilled, send settle_wager. For PayoutPending wagers, retry claim_payout from time to time.
  4. Expire. For each wager past expirySlot whose randomness is unfulfilled, send expire_wager.
  5. 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.
Simulate before sending: several keepers may race for the same step, and only the first one lands.