Skip to main content
counsel applies a single flat fee to the gross pool before distributing winnings. There are no entry fees, withdrawal fees, or hidden charges. Understanding the fee and payout math lets you verify your expected return before signing a bet, or build a bot that prices positions accurately.

Fee

The protocol fee is a flat 3% (fee_rate: 0.03) taken from the total gross pool before any split. The fee is not charged selectively on winners’ returned stakes, it is deducted once from the entire pool. This means losing-side stakes contribute to covering the fee alongside winning-side stakes. On a void, no fee is deducted. Every bettor receives their exact original stake back.

Formulas

T   = gross_pool            (sum of all stakes, all outcomes, in drops)
r   = fee_rate              (currently 0.03)

N   = T × (1 − r)           net pool after fee
P   = N / W                 payout per unit  (W = total staked on winning outcome)

payout_i = floor(s_i × N / W)   individual payout, floored to whole drops
All arithmetic on the money path is performed in drops (1 XRP = 1,000,000 drops) using integer arithmetic to eliminate floating-point rounding errors. Individual payouts are floored to the nearest drop; the rounding residue (dust) accrues to the operator reserve.

Worked example

A market closes with 100 XRP total, split 60 XRP on Yes and 40 XRP on No. Fee rate: 3%.
Value
Gross pool (T)100 XRP
Fee (3%)3 XRP
Net pool (N)97 XRP
Yes pool (W)60 XRP
No pool (W)40 XRP
Yes payout/unit (N/W)97 ÷ 60 = 1.617×
No payout/unit (N/W)97 ÷ 40 = 2.425×
Scenario: Yes wins. A bettor who staked 20 XRP on Yes receives:
payout_drops = floor(20,000,000 × 97,000,000 / 60,000,000)
             = floor(32,333,333.33…)
             = 32,333,333 drops  (32.333333 XRP)
The floor is applied at the drop level, not the XRP level. Dust (the sub-drop residue across all winners) accrues to the operator.

TypeScript implementation

The following is taken directly from the counsel settlement library (src/lib/settlement/parimutuel.ts). All amounts are bigint drops.
export interface FeeFraction {
  num: bigint;
  den: bigint;
}

/** Gross pool T = sum of all stakes across all outcomes. */
export function grossPool(stakes: bigint[]): bigint {
  return stakes.reduce((a, b) => a + b, 0n);
}

/**
 * Convert a decimal fee rate to an exact integer fraction.
 * Supports up to 6 decimal places; avoids floating-point on the money path.
 */
export function feeToFraction(feeRate: number): FeeFraction {
  const den = 1_000_000n;
  const num = BigInt(Math.round(feeRate * Number(den)));
  return { num, den };
}

/** N = floor(T × (den − num) / den) */
export function netPool(total: bigint, fee: FeeFraction): bigint {
  return (total * (fee.den - fee.num)) / fee.den;
}

/**
 * Indicative payout per unit for each outcome if it were to win.
 * Returns null for an outcome with zero stake (odds undefined).
 */
export function indicativePayoutPerUnit(
  stakes: bigint[],
  feeRate: number,
): (number | null)[] {
  const T = grossPool(stakes);
  const net = Number(T) * (1 - feeRate);
  return stakes.map((w) => (w === 0n ? null : net / Number(w)));
}

// Individual payout (integer arithmetic, floors to whole drops):
// payout_i = (s_i * N) / W

Void: no fee charged

When a market voids, no fee is applied. The settlement engine refunds every public stake at face value: the exact number of drops sent in is returned to the sender’s account via a native XRP Payment. Market-maker seed stakes are house-neutral and are not refunded. drops_in === drops_out for every bettor on a voided market. See Resolution for the conditions that trigger a void.

Where to find these numbers in the API

The fee_rate and per-outcome payout figures are part of the market object returned by GET /api/v1/markets/:id:
{
  "id": "a1b2c3d4e5f60001",
  "fee_rate": 0.03,
  "total_xrp": 100,
  "outcomes": [
    {
      "index": 0,
      "label": "Yes",
      "destination_tag": 0,
      "pool_xrp": 60,
      "implied_prob": 0.6,
      "payout_per_unit": 1.617
    },
    {
      "index": 1,
      "label": "No",
      "destination_tag": 1,
      "pool_xrp": 40,
      "implied_prob": 0.4,
      "payout_per_unit": 2.425
    }
  ]
}
payout_per_unit is null for any outcome with zero stake (odds are undefined until at least one bet lands on that side). These figures are indicative until bet_cutoff passes, see Parimutuel Pools for a full explanation of how odds move before close.