Hardcoding 18 decimals is a bug that only surfaces in production, and only with real money.
ERC-20 does not fix precision. Each contract reports its own decimals value, and the client is expected to ask. Most tokens answer 18. Several of the ones people actually move — USDT among them — answer 6. Same interface, different arithmetic.
Get that wrong and nothing throws. The transfer encodes cleanly, the transaction succeeds, and the amount is off by twelve orders of magnitude in one direction or the other. On-chain that is final.
So I stopped treating precision as a constant. In erc20-transfers, an open-source project I maintain, decimals is read from the token contract and cached per token per chain — never assumed, never inherited from a config file someone copied between environments.
Conversion happens once, at the edge. Human-readable decimal in, integer base units through the whole system, human-readable out. No float touches the middle.
The part that took longer to accept: the encoder refuses a value the token cannot represent. Ask it to send an amount with more precision than the token has, and it errors instead of truncating. Rounding money is a business decision. A transfer library is the wrong place to make it silently — a rounded-down remainder that no one chose becomes a reconciliation ticket weeks later, when the only evidence left is a hash.
Most integration bugs I have hit in crypto payments are not cryptography. They are unit conversion wearing a serious hat.
I keep the longer write-ups on my blog, if this is the kind of thing you deal with: https://polycratia.com/c/60418fe5
For those running token transfers in production — where do you draw the line on rounding? Reject at the edge, or accept and record the remainder somewhere explicit?