CONTRACT · DUAL-RAIL

Currency

Crypto wallets switch currency mid-session, so currency is session state, not tenant state. Four locked rules make that safe: queued switches, immutable history, no cross-currency sums, versioned schema.

LAST UPDATED 2026-07-27

Declare it at mount

Fidra.mount(el, {
  // …
  currency: {
    code: 'USDT',        // REQUIRED: uppercase ticker, 2–8 chars (USD, GEL, USDT, BTC)
    symbol: 'USDT',      // display symbol; default = code
    decimals: 2,         // fraction digits for display, 0–8; default 2
    position: 'after',   // 'before' ($12.00) | 'after' (125.50 USDT)
  },
})

No currency declared → the feed behaves exactly as before: bare numbers, two decimals, no event stamp. Tenant config supplies defaults only — the live value is session state.

Switch it mid-session

feedWindow.postMessage({
  source: 'fidra-host', proto: 'fidra:v1',
  type: 'fidra:v1:currency_update',
  payload: { code: 'BTC', symbol: '₿', decimals: 8 },
}, fidraOrigin)

The four rules (locked in CI)

rulewhat it means for you
1 · Queued switchesa running round settles in its own currency; the switch lands after settle — books never mix, the player is never harmed
2 · Immutable historyevery event stamps the currency in force at emit time; a later switch never rewrites earlier events
3 · No cross-currency sumssession recap / breather / Wrapped show the current currency segment, labeled — never a sum across currencies (the same refusal discipline as the mixed-currency measurement readout)
4 · Versioned schemathe raw-event envelope gained optional currency → event schemaVersion is now 7; branch on it if you consume raw events

What carries currency — and what deliberately does not

surfacecarries currency?
Raw event relay (envelope currency)YES v7, stamped at track time
Feed display (balance, net)YES code/symbol + decimals
Semantic fidra:v1 messagesNO shapes are additive-frozen; correlate by roundId — your wallet knows its own currency
Wallet RPC (bet_intent/settle_intent)NO unchanged — see Wallet
Network pool (clips/ghosts)NEVER money-free by construction; the allowlist rejects a currency field (tested)
Outcomes rows (/api/outcomes)YES its own required currency (v2) — Measurement

Exact money — integer minor units

decimals is not just a display hint: internally the feed stores and mutates money as an integer count of minor units (10^decimals per whole unit; scale 2 when no currency is declared), converting to decimal only at the wire/display edge. Sub-cent arithmetic is exact at up to 8 decimals — 0.1 + 0.2 of BTC is 30000000 minor units, never 0.30000000000000004 — and outbound bet_intent/settle_intent amounts are snapped to the same grid before posting.

An applied currency_update that changes decimals re-scales the stored integer to the new grid in the same atomic update as the segment re-baseline (upscaling exact, downscaling rounds to the coarser grid).

Stakes follow the currency

Bet presets, min/max and stepper increments are a property of the currency, not the tenant — 25–500 coins is sane for a GEL-class unit and absurd for BTC. Supply config.stakes at mount, keyed by uppercase currency code:

stakes: {
  GEL: { presets: [1, 2, 5, 10], min: 1, max: 200, step: 1, defaultBet: 2 },
  BTC: { presets: [0.0001, 0.0005, 0.001], min: 0.0001, max: 0.01, step: 0.0001, defaultBet: 0.0005 },
}

The active set follows the active session currency — an applied currency_update switches stakes together with the balance, honoring the same open-round queue. defaultBet seats the dial games and is the fixed ritual stake. A malformed entry is dropped whole; that currency falls back to the built-in coin defaults (chips 25/50/100/250, range 25–500, default 50). presets needs 2–6 values inside [min, max].

Host balance authority (bridge mode): in wallet: 'operator' mode the balance is your number — after an applied switch the feed's re-scaled integer is only a temporary projection. The authoritative value is your next balance assertion (wallet_update, or a bet_result/settle_result balance). If none arrives within 3 s the feed shows the balance as unknown (—) rather than a number your books may not match. Always pair a currency_update with a wallet_updatecurrency_update first.

source of truth: docs/integration/CURRENCY.md · apps/web/src/currency.ts fidra is a B2B software supplier — no gambling services operated, no player funds held. g.jgerenaia@tomsons.co