Skip to main content
A paytable is an array of 1 to 32 buckets { p, m }:
  • p: probability as a u64 in billionths. Every p is greater than zero, and they sum to exactly 1 000 000 000.
  • m: multiplier as a u64 in basis points of the stake. 10 000 is 1×, 0 pays nothing, and the ceiling is 100 000 000 (10 000×).
A bucket’s payout is stake × m / 10 000, rounded down, in USDC base units.

The invariant

open_wager computes, in 128-bit integers with no rounding:
and admits the paytable only if, for the app fee f it passes:
That is the edge floor: the declared edge must cover the 100 bps LP floor, the 20 bps protocol fee and the app’s own fee. The program also derives the declared edge it stores in the wager:
No floating-point value is involved anywhere, and your client should not use one either. Build paytables in bigint.
Round in the house’s favour: round probabilities down, and if you round a multiplier up, derive the probability from the rounded multiplier. Put the remainder of 1 000 000 000 on the zero bucket so the sum is exact.

Examples

Win or nothing. For a target payout T on stake s at edge e bps:
Three tiers at a 3.5 % edge. Refused: pays out more than it takes in. [{ p: 500_000_000n, m: 21_000n }, { p: 500_000_000n, m: 0n }] returns 105 % on average. open_wager fails with PaytableOverpays. Refused: fair, but the edge does not cover the split. A 1 % edge paytable with no app fee is fair to the user but leaves the pool less than its 100 bps floor plus the 20 bps protocol fee. open_wager fails with EdgeFloor.

How much a pool can back

The pool reserves each wager’s liability, its worst case net of what the stake brought in:
Admission then checks, against the pool as it stands at open: free_balance = assets − reserved liability − open pool credits. The per-wager cap uses the pool edge, net of fees, so inflating the declared edge does not widen it, and every basis point of app fee narrows it. After a drawdown trip the cap is halved for 48 hours.

Sizing a prize

Two consequences matter for an interface:
  1. Only the top multiplier costs capital. A paytable that pays something 90 % of the time can be trivial to back; a single large prize decides what the pool must hold.
  2. The cap bounds the liability, not the multiplier. On a fixed paytable, a cap C admits a stake of about C / (max(m) − 1). The same pool that refuses 25 USDC at 1 000× accepts a few cents at 1 000×.
On an idle pool with a 3.5 % declared edge and no app fee (pool edge 330 bps), the 150 bps bound on assets binds first. The smallest pool that carries a win-or-nothing ticket is roughly liability / 0.015: As the pool fills up, the Kelly term on the free balance can bind instead, so the largest prize the pool accepts falls as utilisation rises.

Show the limit, do not hit it

Read Config (for the parameters) and Pool (for totalAssets, totalReservedLiability, openPoolCredits) and apply the checks above in integers to show users the largest prize, or the largest stake, the pool accepts right now. Then simulate the transaction before asking for a signature: the program is the authority, and the pool can move between your read and the user’s click.

Common refusals

The full list is on Errors.