Payments
Conversion drift: when multi-currency balances stop adding up
Floats, rounding and rates applied at the wrong moment make multi-currency casino balances drift. How to store money, record conversions and reconcile per currency.
- Author
- Gamicy Engineering
- Published
- Reading time
- 3 min
A crypto casino might accept BTC, ETH, USDT and a few other assets, display balances in USD or EUR, and play games with providers that only settle in a fiat currency. Every one of those boundaries is a conversion. Every conversion is a chance to lose a fraction of a cent.
Individually the errors are invisible. Across millions of game rounds they add up to a ledger that doesn't match the wallets, finance reports that don't match provider invoices, and player balances that change by tiny amounts when nothing happened. We call this conversion drift.
Where drift comes from
- Floating-point money. Binary floats can't represent most decimal fractions exactly. Summing them accumulates error.
- Storing converted balances. If the balance of a BTC deposit is stored in USD, its value changes with the market — but the stored number doesn't, and the real BTC no longer matches it.
- Converting at display time and at settlement time with different rates. The player sees one number, the ledger records another.
- Inconsistent rounding. Rounding half-up in one service and truncating in another leaks value in one direction on every round trip.
- Round-trip conversions for providers. A bet converted from USDT to USD for the provider and the win converted back loses precision twice per round.
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
// Integer minor units are exact:
10n + 20n === 30n; // true (amounts in cents, satoshis, wei…)Store amounts in native minor units
Every amount should be stored in the smallest unit of its own currency, as an integer: satoshis for BTC, wei for ETH, the token's base units for ERC-20 tokens, cents for USD. Use arbitrary-precision integers in code (bigint in TypeScript, NUMERIC in PostgreSQL) — wei amounts overflow 64-bit integers easily.
A player's BTC balance is a BTC balance. Showing it in USD is a presentation concern, calculated on the fly from the current rate and never written back.
Make conversions explicit ledger events
When money genuinely changes currency — a player swaps USDT to BTC, or a bet must be settled in another currency — record it as a conversion with the exact rate, its source and timestamp, and the rounding applied. In a double-entry ledger that means entries against an FX account in both currencies:
conversion c_7731 rate 1 USDT = 0.00001045 BTC source: <rate feed> rounding: floor
player:usdt -100_000_000 (100.000000 USDT, 6 decimals)
fx:usdt +100_000_000
fx:btc -104_500 (0.00104500 BTC, 8 decimals)
player:btc +104_500Each currency balances on its own. The FX account's position tells you exactly how much exposure the platform holds, and any rounding remainder is visible instead of disappearing.
Settle with providers in as few conversions as possible
Where a provider supports the player's currency, settle in it directly. Where it doesn't, lock a rate per game session or per round and apply it to both the bet and the win, so the player is never exposed to two different rates within one round. Record the locked rate with the round.
Reconcile per currency, not in a reporting currency
Reconciling in USD hides drift behind price movements. Reconcile each currency in its native units: total player balances plus house and FX positions should equal what the wallets hold, to the last minor unit. A mismatch of one satoshi is a signal, not noise.
Ledger design is at the centre of our wallet and payment work and of every casino platform we build.
Related services
- Wallets and crypto payments
Multi-chain deposits, withdrawals, hot/cold wallet architecture and a double-entry ledger that reconciles.
- Casino platform development
Player accounts, back office, bonus logic and reporting — built as one system rather than a pile of plugins.