calcComboOdds
Deprecated since Toolkit v7.2.0 (still exported and working). The bets subgraph records every bet’s
total odds, potential payout and payout already priced by the protocol’s rule: read a bet’s odds /
settledOdds and potentialPayout / payout (or rawPotentialPayout / rawPayout) instead of
re-pricing its legs on the client. useBets and getBetsReport no longer use calcComboOdds.
MARGIN_APPLIED_AT is deprecated with it.
Calculates the total odds of a bet, reproducing on the client the way the protocol priced it when the bet
was placed. For the minimum odds to place a new bet with, use
calcMinOdds, which applies slippage on top.
Usage
import { calcComboOdds } from '@azuro-org/toolkit'
const totalOdds = calcComboOdds({
odds: [ 1.5, 2 ], // leave voided legs out
createdAt: bet.createdAt,
})Props
type CalcComboOddsParams = {
/** the leg odds as the protocol recorded them; a voided leg must be left out by the caller */
odds: number[]
/**
* unix seconds, when the bet was placed - it decides whether the leg odds carry the feed's fee and
* whether a single leg is priced as a combo of one
* */
createdAt: number
}Return Value
string (.toFixed(ODDS_DECIMALS) called on the number)
Why a combo is not the product of its legs
The feed applies its fee to every outcome’s odds. Multiplying N legs that each carry the fee would compound it N times, which is not what the bettor is charged. A combo is therefore priced:
remove the fee from each leg → multiply → apply the fee once to the product
ceil(1.5 / 0.99) * ceil(2 / 0.99) * 0.99 = 3.05
1.5 * 2 = 3.00 ← the plain product, too lowThe gap widens with every extra leg, so a long combo priced as a plain product is materially understated.
Leave voided legs out of the array — never pass 1.0 for one. 1.0 would be de-margined to
1.02, inflating the total by about 1.8% per void. An empty array prices at 1, so a bet with
nothing left standing returns its stake.
The fee has a start date
The feed did not always apply a fee. For a bet placed before MARGIN_APPLIED_AT the leg odds are raw,
so a combo of them really is the plain product — and removing a fee that was never charged, then
re-applying it once, would overstate the odds by roughly (1/0.99)^(legs-1).
calcComboOdds branches on createdAt for exactly that reason. Pass the bet’s real creation time; do
not default it.
import { calcComboOdds, MARGIN_APPLIED_AT } from '@azuro-org/toolkit'
// same legs, different eras
calcComboOdds({ odds: [ 2, 1.75 ], createdAt: MARGIN_APPLIED_AT }) // '3.55...' - fee removed per leg
calcComboOdds({ odds: [ 2, 1.75 ], createdAt: MARGIN_APPLIED_AT - 1 }) // '3.50...' - plain productSingles from SINGLE_MARGIN_REMOVED_AT
The rule depends on when the bet was placed:
- before
MARGIN_APPLIED_ATthe legs carry no fee, so they are multiplied as they are; - from
MARGIN_APPLIED_ATevery bet goes through the combo rule, a single leg included: the fee is removed from each leg, the result rounded up onto the two-decimal grid the protocol prices on, multiplied, and the fee taken off once - which can move a single leg’s odds off what the bettor was quoted; - from
SINGLE_MARGIN_REMOVED_ATa single leg is its quoted odds again, while two or more legs keep the combo rule.
SINGLE_MARGIN_REMOVED_AT is a new export in Toolkit v7.2.0 (unix seconds). It is one constant for every
chain, since the switch happened at one moment everywhere.
import { calcComboOdds, SINGLE_MARGIN_REMOVED_AT } from '@azuro-org/toolkit'
calcComboOdds({ odds: [ 1.6789 ], createdAt: SINGLE_MARGIN_REMOVED_AT - 1 }) // '1.68...' - a single priced as a combo of one
calcComboOdds({ odds: [ 1.6789 ], createdAt: SINGLE_MARGIN_REMOVED_AT }) // '1.6789...' - its quoted odds
calcComboOdds({ odds: [ 2, 1.5 ], createdAt: SINGLE_MARGIN_REMOVED_AT }) // '3.05...' - two legs keep the fee ruleReading a bet’s figures instead
For a bet that already exists, read what the subgraph recorded:
const totalOdds = bet.settledOdds ?? bet.odds // the settled odds once settled, the placed odds before
const possibleWin = bet.potentialPayout // what the bet pays if it wins
const payout = bet.payout // once settled: 0 if lost, the stake if canceled, the amount paid once redeemedA voided leg is one whose selection.result === SelectionResult.Canceled. For a freebet, the pool’s payout
is not what the bettor receives: the freebet contract passes the bettor only a share of it, which
calcFreebetBettorShare computes. getBetsReport and the SDK’s
useBets already read these figures for you.