Skip to content

fix(xrpl): return string from dropsToXrp to preserve precision (#3316)#3341

Open
achowdhry-ripple wants to merge 1 commit into
mainfrom
fix/dropsToXrp-precision-loss-3316
Open

fix(xrpl): return string from dropsToXrp to preserve precision (#3316)#3341
achowdhry-ripple wants to merge 1 commit into
mainfrom
fix/dropsToXrp-precision-loss-3316

Conversation

@achowdhry-ripple
Copy link
Copy Markdown
Collaborator

High Level Overview of Change

Fixes xrpl.js#3316.

  • dropsToXrp() now returns a base-10 decimal string instead of a JavaScript number. The internal .toNumber() call is replaced with .toString(BASE_TEN).

  • Client.getXrpBalance() (which forwards the value) now returns Promise<string> instead of Promise<number>.

  • Client.getBalances() is updated for the new return type (the branch that pushes the XRP balance no longer needs a redundant .toString(), and the !== 0 sentinel check becomes !== '0').

  • getBalanceChanges() drops a now-redundant .toString() on the already-string XRP value.

  • Existing dropsToXrp tests updated for the new return type; new regression test added per the issue:

    it('round-trips large amounts without precision loss (issue #3316)', function () {
      const drops = '9999999999999999'
      const xrp = dropsToXrp(drops)
      assert.strictEqual(xrp, '9999999999.999999')
      assert.strictEqual(xrpToDrops(xrp), drops)
    })
  • HISTORY.md updated under Unreleased > BREAKING CHANGES.

Context of Change

dropsToXrp converts a drops integer (≤ ~10^17 for the XRP total supply) into an XRP amount by dividing by 1,000,000. The old implementation called .toNumber() on the BigNumber result, coercing to IEEE-754 double. For amounts above ~9 billion XRP, the double cannot represent every drop exactly, so the conversion silently loses precision.

Repro from the issue:

dropsToXrp('9999999999999999')              // returned 9999999999.999998 (should be 9999999999.999999)
xrpToDrops(dropsToXrp('9999999999999999'))  // returned '9999999999999998' — 1 drop silently lost on round-trip

The fix is to return the BigNumber's exact base-10 string representation. Drops are at most 6 fractional digits of XRP, so the division terminates exactly within BigNumber's default precision and never produces exponential notation.

This is a breaking API change (return type numberstring), which is why it is grouped with the other Unreleased breaking changes in HISTORY.md. Callers that rely on a JS number can wrap the result in Number(...) — this is already done at the two internal call sites in Wallet/fundWallet.ts.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Tests (You added tests for code that already exists, or your new feature included in this PR)
  • Documentation Updates
  • Release

Did you update HISTORY.md?

  • Yes
  • No, this change does not impact library users

Test Plan

  • New unit test dropsToXrp > round-trips large amounts without precision loss (issue #3316) directly exercises the regression case from the issue.
  • Existing dropsToXrp tests were updated to assert against the new string return type (mechanical change — same values, now quoted).
  • getXrpBalance mock test updated to assert the string return.
  • getBalanceChanges fixture-driven tests are unchanged (the fixture already expected string values; the new code path produces the same strings).
  • Did not re-run the full suite locally — npm install is currently hitting the npm "Exit handler never called!" bug on my machine. Relying on CI; happy to push fixups if anything fails.

`dropsToXrp` previously returned a JavaScript `number` via `.toNumber()`,
which silently lost precision for amounts approaching the XRP supply
(~10^17 drops). `xrpToDrops(dropsToXrp('9999999999999999'))` returned
`'9999999999999998'`, dropping one drop on the round-trip.

Return a base-10 decimal string from `dropsToXrp` (and from
`Client.getXrpBalance`, which forwards the value) so the full precision
of the input is preserved. Callers that need a JS number can wrap the
result in `Number(...)`.

BREAKING CHANGE: `dropsToXrp()` now returns `string` instead of `number`;
`Client.getXrpBalance()` now returns `Promise<string>`.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f0cae0e2-a210-474a-95be-eab024684bb6

📥 Commits

Reviewing files that changed from the base of the PR and between d739d47 and 70d5790.

📒 Files selected for processing (6)
  • packages/xrpl/HISTORY.md
  • packages/xrpl/src/client/index.ts
  • packages/xrpl/src/utils/getBalanceChanges.ts
  • packages/xrpl/src/utils/xrpConversion.ts
  • packages/xrpl/test/client/getXrpBalance.test.ts
  • packages/xrpl/test/utils/dropsToXrp.test.ts

Walkthrough

This PR resolves precision loss in XRP balance handling by changing dropsToXrp() to return base-10 decimal strings instead of JavaScript numbers. The change propagates through Client.getXrpBalance(), Client.getBalances(), helper functions, and all dependent tests, with a regression test ensuring large amounts round-trip without data loss.

Changes

XRP Conversion String Return Type

Layer / File(s) Summary
dropsToXrp return type and implementation
packages/xrpl/src/utils/xrpConversion.ts
The core fix: updated JSDoc and return type signature to string, and implementation now calls .toString(BASE_TEN) instead of .toNumber() on the divided BigNumber, preserving full precision without exponential notation.
Client balance methods
packages/xrpl/src/client/index.ts
Updated getXrpBalance return type from Promise<number> to Promise<string>, and adjusted getBalances to initialize xrpPromise with string default '0', compare against string '0', and push string values directly.
Balance change calculation
packages/xrpl/src/utils/getBalanceChanges.ts
Updated getXRPQuantity to use dropsToXrp(value) directly without wrapping in .toString(), aligning with the new string return type.
Tests and changelog
packages/xrpl/test/utils/dropsToXrp.test.ts, packages/xrpl/test/client/getXrpBalance.test.ts, packages/xrpl/HISTORY.md
Updated all dropsToXrp assertions to expect string outputs, switched getXrpBalance test to strict string equality, added regression test for round-trip precision preservation of large amounts (issue #3316), and documented the breaking change in the changelog.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly Related Issues

Suggested Reviewers

  • Patel-Raj11
  • kuan121
  • mvadari

Poem

🐰 A rabbit's ode to precision true:
Drops of XRP, no more .toNumber() blues—
Strings preserve each precious grain,
No exponential loss again!
Round-trip journeys prove the way,
From drops to XRP, come what may. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: converting dropsToXrp to return a string for precision preservation, directly referencing the issue number.
Description check ✅ Passed The PR description comprehensively covers all required template sections: high-level overview with issue link, detailed context explaining the precision loss problem and the fix, type of change clearly marked, HISTORY.md update confirmed, and a complete test plan with specific test cases.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dropsToXrp-precision-loss-3316

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/xrpl/src/client/index.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:

https://eslint.org/docs/latest/use/configure/migration-guide

If you still have problems after following the migration guide, please stop by
https://eslint.org/chat/help to chat with the team.

packages/xrpl/src/utils/xrpConversion.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:

https://eslint.org/docs/latest/use/configure/migration-guide

If you still have problems after following the migration guide, please stop by
https://eslint.org/chat/help to chat with the team.

packages/xrpl/src/utils/getBalanceChanges.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

ESLint couldn't find an eslint.config.(js|mjs|cjs) file.

From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:

https://eslint.org/docs/latest/use/configure/migration-guide

If you still have problems after following the migration guide, please stop by
https://eslint.org/chat/help to chat with the team.

  • 2 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

dropsToXrp() returns number, causing precision loss for large amounts

1 participant