> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fade.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Settlement and keepers

> Requesting randomness, settling, expiring, the public keeper API, and running your own.

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

| Step | Instruction | Allowed | Paid |
| - | - | - | - |
| Open | `open_wager` | Signed by the app (and the stake owner) | Prepays the crank fee |
| Request | `request_randomness` | From `open_slot + 2` until `open_slot + T_SETTLE` | 50 000 lamports to the caller |
| Fulfil | ORAO writes the randomness | ORAO's authorities, typically within seconds | ORAO's fee, from the prefund |
| Settle | `settle_wager` | Any time after fulfilment, **no deadline** | 200 000 lamports to the caller |
| Or expire | `expire_wager` | After `open_slot + T_SETTLE`, only if the randomness was never fulfilled | 200 000 lamports to the caller |

`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:

```
seed = SHA256("FADE/ORAO-SEED/v1" ‖ program ‖ wager ‖ open_slot ‖ s* ‖ h*)
```

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](/protocol/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.

<Note>
  Browsers can call the API only from Fade's own app origins (CORS). Call it from your server, or run your own keeper.
</Note>

### `POST /v1/wagers`

Hands a wager to the keeper. It checks that the address is a Fade wager account and queues it.

```bash theme={null}
curl -X POST https://api.fade.finance/v1/wagers \
  -H 'content-type: application/json' \
  -d '{"wager":"<wager address>"}'
```

| Status | Body |
| - | - |
| `202` | `{ "status": "queued" }` |
| `400` | `{ "error": "not a base58 address" }`, `"missing wager"` or `"body must be JSON"` |
| `404` | `{ "error": "account not found" }` |
| `422` | `{ "error": "not a Fade wager account" }` |
| `429` | `{ "error": "rate limited" }` |

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.

```json theme={null}
{
  "state": "settled",
  "openSlot": 412345678,
  "requestSignature": "…",
  "settleSignature": "…",
  "bucket": 1,
  "payout": "0",
  "updatedAt": "2026-10-07T13:35:59.159Z"
}
```

| `state` | Meaning |
| - | - |
| `queued` | Open on-chain; the keeper requests randomness from `open_slot + 2` |
| `requested` | Randomness requested; waiting for ORAO |
| `fulfilled` | ORAO has fulfilled; the keeper is settling |
| `settled` | Closed by a settlement (`bucket`, `payout` in USDC base units). Also covers a parked payout. |
| `expired` | Closed by `expire_wager` after `T_SETTLE` |
| `unknown` | Not a wager the keeper knows, or closed with no settlement or expiry found |

### `GET /v1/health`

```json theme={null}
{ "keeper": "<keeper address>", "lamports": "1004904000", "lastTick": 1791380158617, "queue": 0 }
```

`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)

| Route | Does |
| - | - |
| `GET /v1/faucet` | `{ "usdc": "1000000000", "perDay": 300, "remainingToday": 296 }` |
| `POST /v1/faucet` | Body `{ "owner": "<wallet>" }`. Sends 1 000 test USDC (and 0.01 SOL to a wallet holding less than 0.005, at most once a day). Returns `{ "signature", "usdc", "sol" }`, or `{ "error", "retryAt" }` with `retryAt` in Unix milliseconds. |

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](/build/quickstart#settling-without-a-keeper) 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](/liquidity/pool).

Simulate before sending: several keepers may race for the same step, and only the first one lands.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.