Pricing & Payout Math
All probabilities and payouts are fixed-point integers. 1 USDC = 1,000,000
micro-USDC, and a probability of 50% is stored as 500_000.
PROB_SCALE = 1e6 per-leg probabilities, and USDCHIGH_SCALE = 1e12 the combined-probability accumulatorPer-leg probability
Section titled “Per-leg probability”For a leg betting selected_outcome on market M:
price_yes = lmsr::price_yes(M.q_yes, M.q_no, M.b) // 0 … 1_000_000
prob = match selected_outcome { Yes => price_yes, No => 1_000_000 − price_yes,}
require config.prob_floor_bps ≤ prob ≤ config.prob_ceiling_bpsThat value is written into parlay.legs[i].probability_at_entry and never
recomputed. You are paid at the multiplier you locked, whatever the market
does afterward.
The band is admin config in basis points of PROB_SCALE (2000 bps = 20% =
200_000 units), bounded by the hard constants HARD_PROB_FLOOR_BPS = 100
(1%) and HARD_PROB_CEILING_BPS = 9900 (99%). Out-of-band legs fail with
ProbabilityOutOfRange (6014). The examples below use a 5% floor for
illustration. → Leg Eligibility
The spot price_yes above feeds a one-hour TWAP, which is what actually
gets locked — not the instantaneous LMSR price. That is what makes a leg
expensive to manipulate. → CLOB Markets as Legs
Combined probability
Section titled “Combined probability”const HIGH_SCALE: u128 = 1_000_000_000_000;const PROB_SCALE: u128 = 1_000_000;
let mut combined = HIGH_SCALE; // 1.0 in HIGH_SCALE unitsfor prob in legs.probs { combined = (combined * prob) / PROB_SCALE; // stays in HIGH_SCALE}require!(combined > 0); // collapse guardWhy 1e12 and not 1e6
Section titled “Why 1e12 and not 1e6”Accumulating in the legs’ native 1e6 scale truncates at every step, and the error compounds in the trader’s favour. A 4-leg slip at 5% each:
| Step | prob | Under PROB_SCALE | Under HIGH_SCALE |
|---|---|---|---|
| 0 | — | 1_000_000 | 1_000_000_000_000 |
| 1 | 50_000 | 50_000 | 50_000_000_000 |
| 2 | 50_000 | 2_500 | 2_500_000_000 |
| 3 | 50_000 | 125 | 125_000_000 |
| 4 | 50_000 | 6 (true: 6.25) | 6_250_000 (exact) |
Multiplier is HIGH_SCALE / combined:
| Scale | Multiplier | Error |
|---|---|---|
| PROB_SCALE | 166,666× | +4.17% overpay |
| HIGH_SCALE | 160,000× | exact |
A 4% systematic overpay on every longshot slip is the difference between a solvent vault and a drained one.
The collapse guard
Section titled “The collapse guard”combined > 0 catches the case where truncation flattens the accumulator to
zero. Enough legs at a low floor would do it. Failing loudly beats emitting a
junk multiplier from a division by a truncated value — so the probability
floor plus this guard is what practically caps leg count at deep-longshot
probabilities.
The hard floor of 1% is set exactly where the comment in config.rs puts it:
below that, per-leg multipliers blow up — a 1% leg is 100×, so two of them is
already 10,000×.
Overflow envelope
Section titled “Overflow envelope”combined ≤ 1e12combined × prob ≤ 1e12 × 1e6 = 1e18 u128::MAX ≈ 3.4e38 ✓stake × HIGH_SCALE ≤ 1.8e19 × 1e12 = 1.8e31 ✓Potential payout
Section titled “Potential payout”let potential_payout_u128 = stake * HIGH_SCALE / combined;let potential_payout = u64::try_from(potential_payout_u128)?;
require!(potential_payout <= config.max_payout); // PayoutExceedsMaxrequire!(potential_payout >= min_payout); // SlippageExceededThat u64::try_from is the hard ceiling. Together with max_payout, it is
the program’s only protection against a multiplier explosion from a
degenerate combined.
Worked example
Section titled “Worked example”stake $10 = 10_000_000 micro-USDClegs 50%, 40%, 25%
combined = 1e12 × 500_000 / 1e6 = 500_000_000_000 × 400_000 / 1e6 = 200_000_000_000 × 250_000 / 1e6 = 50_000_000_000
payout = 10_000_000 × 1e12 / 50_000_000_000 = 200_000_000 → $200 (20×)Cancelled-leg recompute
Section titled “Cancelled-leg recompute”When a leg’s market cancels, the leg goes Voided and is divided out of the multiplier:
new_payout = old_payout × prob_i / PROB_SCALEThe algebra: the original payout was stake × HIGH_SCALE / ∏ pⱼ. Removing
leg i — treating it as a 1× contribution — gives
stake × HIGH_SCALE / ∏_{j≠i} pⱼ= (stake × HIGH_SCALE / ∏ pⱼ) × pᵢ / PROB_SCALE= old_payout × pᵢ / PROB_SCALEContinuing the example, if the 25% leg cancels:
new_payout = 200_000_000 × 250_000 / 1_000_000 = 50_000_000 → $50 (5×)You keep a slip on the two surviving legs at their honest 5× multiplier.
Down-rounding from integer division is biased toward the house — the
recomputed payout is at most one micro-USDC below the true value — which only
strengthens vault solvency. total_exposure decreases by
old_payout − new_payout.
fee = floor(potential_payout × fee_bps_at_create / 10_000)fee = min(fee, potential_payout)net = potential_payout − feefee_bps_at_create is snapshotted into the Parlay account at create time.
An admin update_config between create and claim cannot change what a
winning user pays. The min is belt-and-braces against a config that somehow
exceeded the 10% cap.
There is no fee on the stake — only on a winning gross payout.
Exposure arithmetic
Section titled “Exposure arithmetic”create total_exposure += potential_payoutleg voided total_exposure −= (old_payout − new_payout)slip lost total_exposure −= potential_payout (immediately)all voided potential_payout reset to stake; exposure followsclaim total_exposure −= potential_payoutadmin void total_exposure −= potential_payoutReleasing the full exposure the instant a slip goes Lost is conservative —
it can no longer pay out, so the reservation is dead weight.
Rounding, summarised
Section titled “Rounding, summarised”| Operation | Direction | Who benefits |
|---|---|---|
| Combined-probability accumulation | truncates | house (payout rounds down) |
potential_payout u64 conversion | truncates | house |
| Cancelled-leg recompute | truncates | house |
| Fee | floor | trader |
Every material rounding decision favours the vault by at most one micro-USDC, and the 20% solvency buffer is sized to absorb the accumulated drift.