Skip to content

fix(wallet): round instead of truncate in micro-denom conversion - #495

Open
memosr wants to merge 1 commit into
canopy-network:mainfrom
memosr:fix/micro-denom-rounding
Open

fix(wallet): round instead of truncate in micro-denom conversion#495
memosr wants to merge 1 commit into
canopy-network:mainfrom
memosr:fix/micro-denom-rounding

Conversation

@memosr

@memosr memosr commented Aug 4, 2026

Copy link
Copy Markdown

Summary

toMicroDenom (templaterFunctions.ts) and toMicro (useDenom.ts) convert a
user-entered display amount into the chain's base unit by multiplying by the denom
factor and calling Math.floor. The intermediate product is an IEEE-754 double, so
amounts whose decimal expansion is not exactly representable land just below the true
integer and are truncated one micro-unit low.

The deprecated toBaseDenom, which toMicroDenom was introduced to replace, used
.toFixed(0) and rounded correctly. This change restores that behavior.

Reproduction

input current (Math.floor) expected
2.01 2009999 2010000
4.02 4019999 4020000
8.29 8289999 8290000
16.08 16079999 16080000

Across all 100,000 two-decimal values from 0.01 to 1000.00, Math.floor is off by one
micro-unit for 1,196 of them (1.20%). Math.round is exact for all 100,000.

Why it matters

toMicroDenom is referenced 25+ times in manifest.json across the send, stake, DAO
deposit and swap flows, and for transaction fees.

  • A user who types 2.01 CNPY signs a transaction for 2009999 uCNPY, with no indication
    that the amount was adjusted.
  • Swap and order flows compare requestedAmount for an exact match, so an order that is
    one micro-unit short does not match.
  • Fees are checked against minFee in fsm/swap.go; a fee at the boundary rounds down
    and the transaction is rejected without a clear reason.

Changes

  • cmd/rpc/web/wallet/src/core/templaterFunctions.ts - toMicroDenom uses Math.round.
  • cmd/rpc/web/wallet/src/hooks/useDenom.ts - toMicro uses Math.round.

Notes

Math.round on the double product is exact for the value range the wallet handles. A
fully exact fix would use string-based decimal shifting and avoid floating point
entirely. I kept this minimal and behavior-preserving, but happy to follow up with that
if you would prefer it.

tsc --noEmit passes on the wallet package.

toMicroDenom and toMicro used Math.floor on a floating-point product,
so values whose decimal expansion is not exactly representable in
IEEE-754 were truncated one micro-unit low. For example 2.01 CNPY
converted to 2009999 uCNPY instead of 2010000.

The deprecated toBaseDenom, which toMicroDenom replaced, used
.toFixed(0) and rounded correctly. Switching to Math.round restores
that behavior and makes the two conversion helpers agree.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant