Skip to content

js,js-legacy: Match on-chain semantics in amountToUiAmount helpers - #1345

Open
plutohan wants to merge 2 commits into
solana-program:mainfrom
plutohan:plutohan/issue-1325-js-exact-conversions
Open

js,js-legacy: Match on-chain semantics in amountToUiAmount helpers#1345
plutohan wants to merge 2 commits into
solana-program:mainfrom
plutohan:plutohan/issue-1325-js-exact-conversions

Conversation

@plutohan

Copy link
Copy Markdown
Contributor

Follow-up to #1340, for the client side of the same problem. The amountToUiAmount / uiAmountToAmount helpers in both JS clients diverged from the program in several ways:

  • Plain-mint conversions went through Number and parseFloat, losing precision above 2^53 (u64::MAX printed as 18446744073.709553) and silently accepting malformed input ("1.5oops" parsed as 1, "1e30" returned a value above u64::MAX).
  • Extension paths truncated at base-unit precision before dividing, so a multiplier below one rendered nonzero amounts as "0", and small amounts printed in scientific notation ("1e-10") which the program never emits.
  • The interest-bearing ui-amount path truncated where the program rounds.

Plain-mint conversions now use exact bigint string arithmetic ported from amount_to_ui_amount_string_trimmed and try_ui_amount_into_amount. Extension paths mirror the fixed on-chain behavior: scale by the total multiplier, format to the mint's decimals with an exact round-half-even formatter (matching Rust's float formatting, where toFixed rounds half up), validate input like an f64 parse, range-check against u64 and saturate the way the program's cast does.

Existing test expectations that asserted the old behavior were updated to the values the program produces (verified against the interface crate), e.g. "1e-10" is now "0.0000000001", "Infinity" is now "inf", and the interest-bearing round-trip returns the rounded principal. New tests cover sub-unit multipliers, u64-range exactness, malformed input rejection and round-trips.

Fixes #1325

plutohan added 2 commits July 24, 2026 16:21
The conversion helpers in both clients diverged from the program:
plain-mint paths lost precision above 2^53 and accepted malformed or
out-of-range input, extension paths truncated at base-unit precision so
multipliers below one rendered nonzero amounts as "0", and the
interest-bearing ui-amount path truncated where the program rounds.

Plain-mint conversions now use exact bigint string arithmetic ported
from the program. Extension paths scale with the total multiplier like
the program, format with an exact round-half-even formatter, validate
input, range-check against u64 and saturate like the program's cast.
The cjs tsconfig targets ES2016 where bigint literals are a syntax
error. Use the BigInt constructor instead; behavior is unchanged.
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.

amountToUiAmount / uiAmountToAmount helpers are lossy in both JS clients

1 participant