Why integers
A decimal number stored as a floating-point value is an approximation.0.1 + 0.2 does not equal 0.3 in most languages, and the error compounds across a ledger.
10 + 20 is exactly 30 kobo, every time, in every language.
The rule is short:
Smallest units
Token decimals are a property of the token, not of the chain. USDT uses 6 decimals on both Tron and Ethereum, but many ERC-20 tokens use 18. Read the value from configuration rather than assuming it.
Keep the multiplier for each asset in one place in your code. A multiplier hard-coded at three call sites will eventually disagree with itself.
Units by product
When an endpoint takes or returns major-unit decimal strings, parse them with a decimal library straight into your smallest-unit integer. Never pass them through a float.
Convert at the edges only
Keep every internal calculation in the smallest unit. Convert to a human-readable string at the last possible moment, in the display layer.Display
Two habits prevent most user confusion:- Label the currency every time.
500is ambiguous.₦500.00and0.005 BTCare not. - Round only for display, and keep the stored value exact. A balance of 99,987,600 satoshis can display as
0.9999 BTC. It must still be stored as 99,987,600.
Exchange rates
A conversion has two parts: a rate, and the moment it applied. Record both.1
Get a quote
Request a rate through Get exchange rate, Convert amount, or a product quote.
2
Store the rate with the transaction
Save the rate, the timestamp, and the quote identifier alongside your record.
3
Apply it in the smallest unit
Multiply integers. Convert to a decimal form only if you must.
4
Use a decimal library if you must divide
Use
decimal.js or big.js on JavaScript, the decimal module on Python, or BigDecimal on Java. Never a float.Fees
The amount a recipient gets is not the amount you sent. Network fees, chain gas, and service fees all reduce it. Decide explicitly which side pays, then make that visible:- Show the fee as its own line, not folded into the total.
- Show the amount the recipient will actually receive.
- Store both the gross amount and the fee, not just the net.
amount is what leaves your balance, settlement_amount is what arrives, and exchange_rate.effective_rate is the rate after fees. Show users the effective rate.
Multi-currency records
If you hold more than one currency, keep each balance in its own field with its own unit. Do not store a single number and a currency code that varies by row, because the smallest unit differs per currency and the arithmetic will silently mix scales. Tie the legs of a conversion together with a shared reference, so that a single transaction reads as one event rather than two unrelated rows.Test the edges
Precision bugs hide at the extremes. Cover these cases:Summary
- Check which unit each endpoint uses, then store integers in the smallest unit.
- Calculate in that unit.
- Convert and round only for display.
- Store the rate and the time with every conversion.
- Read token decimals from configuration.
- Test the smallest and largest amounts you support.