Skip to content

fix(send): use string type for sendTokenAmount to preserve precision - #5618

Merged
svenvoskamp merged 2 commits into
chore/khizr-fixesfrom
chore/khizr-fixes-REOWN-3617
Apr 13, 2026
Merged

fix(send): use string type for sendTokenAmount to preserve precision#5618
svenvoskamp merged 2 commits into
chore/khizr-fixesfrom
chore/khizr-fixes-REOWN-3617

Conversation

@Khizr97

@Khizr97 Khizr97 commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes Send/Swap max amount of token throwing transaction errors due to JavaScript floating-point precision loss (REOWN-3617).

Technical Report

Problem

When sending the max amount of a token with 18 decimals, the transaction fails because the amount exceeds the actual on-chain balance. The sent amount differs from the intended amount due to JS number precision limits.

Root Cause Analysis

sendTokenAmount was typed as number throughout the send flow (SendController, UI components, event tracking). JavaScript numbers (IEEE 754 double-precision) can only represent ~15-16 significant digits accurately. Tokens with 18 decimals can require up to 18+ significant digits. For example, a balance of 1.123456789012345678 ETH would be truncated/rounded by JS number arithmetic, and the resulting amount could exceed the actual balance by a tiny margin — causing the transaction to revert.

The critical point was w3m-input-token's onMaxClick handler: maxValue.toFixed(20) produced the correct string, but wrapping it in Number(...) immediately lost precision before it reached SendController.

Approach & Reasoning

Changed sendTokenAmount from number to string across the entire flow:

  1. Types — TxParams, ContractWriteParams, SendControllerState interfaces updated
  2. SendController — setTokenAmount accepts string, state stores string
  3. w3m-input-token@Property changed to { type: String }. onMaxClick now passes maxValue.toFixed(20) directly as string (the key fix — no more Number() roundtrip). Input changes wrapped with String(event.detail)
  4. w3m-wallet-send-view — Balance comparison changed from this.sendTokenAmount > Number(quantity) to NumberUtil.bigNumber(this.sendTokenAmount).gt(quantity) — uses Big.js arbitrary-precision math instead of lossy JS number comparison
  5. w3m-wallet-send-preview-view — Display values use Number() only for UiHelperUtil.roundNumber (display-only, precision loss acceptable)

Number() conversion only at boundaries where it's safe:

  • Event tracking amount properties (analytics only — precision loss acceptable)
  • Display values via UiHelperUtil.roundNumber (human-readable rounding)
  • Solana sendTransaction value (SOL has 9 decimals, fits within JS number safely)

Critical EVM path preserved full precision:

  • ConnectionController.parseUnits(params.sendTokenAmount.toString(), decimals) — parseUnits takes a string and converts to BigInt, so full precision flows to the contract

Why string and not BigInt: The value needs to flow through UI input fields, Lit property bindings, and valtio state — all of which handle strings naturally. BigInt would require constant conversion at every UI boundary.

Files Changed

  • packages/controllers/src/controllers/SendController.ts — types + state
  • packages/scaffold-ui/src/partials/w3m-input-token/index.ts — input handling
  • packages/scaffold-ui/src/views/w3m-wallet-send-view/index.ts — balance comparison
  • packages/scaffold-ui/src/views/w3m-wallet-send-preview-view/index.ts — display
  • 5 test files updated to use string literals

Verification

  • 60/60 tests pass across 5 test files (SendController, input-token, send-view, send-preview-view, send-view-params)
  • Type check clean on controllers and scaffold-ui packages
  • TxParams and ContractWriteParams are internal to SendController — no external consumers affected

Test plan

  • Send max amount of a token with 18 decimals — transaction should succeed
  • Send partial amounts — verify correct precision
  • Verify balance comparison works (insufficient funds warning)
  • Verify Solana send still works

🤖 Generated with Claude Code

Fixes REOWN-3617: Send/Swap max amount of token throws transaction error

Root cause: sendTokenAmount was typed as number, causing JavaScript
floating-point precision loss (~15-16 significant digits). For tokens
with 18 decimals, amounts like 1.234567890123456789 became
1.2345678901234568, causing gas estimation to fail when the slightly
incorrect amount exceeded the actual balance.

Changes:
- Changed sendTokenAmount type from number to string in SendController
- Updated onMaxClick() to pass string directly (no Number() conversion)
- Updated onInputChange() to convert input to string
- Added NumberUtil.bigNumber() for balance comparisons
- Aligned with SwapController.sourceTokenAmount pattern

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@linear

linear Bot commented Apr 2, 2026

Copy link
Copy Markdown

@vercel

vercel Bot commented Apr 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
appkit-demo Building Building Preview, Comment Apr 13, 2026 4:32pm
appkit-gallery Building Building Preview, Comment Apr 13, 2026 4:32pm
appkit-headless-sample-app Building Building Preview, Comment Apr 13, 2026 4:32pm
appkit-laboratory Building Building Preview, Comment Apr 13, 2026 4:32pm
9 Skipped Deployments
Project Deployment Actions Updated (UTC)
appkit-basic-example Ignored Ignored Apr 13, 2026 4:32pm
appkit-basic-sign-client-example Ignored Ignored Apr 13, 2026 4:32pm
appkit-basic-up-example Ignored Ignored Apr 13, 2026 4:32pm
appkit-ethers5-bera Ignored Ignored Apr 13, 2026 4:32pm
appkit-nansen-demo Ignored Ignored Apr 13, 2026 4:32pm
appkit-wagmi-cdn-example Ignored Ignored Apr 13, 2026 4:32pm
ethereum-provider-wagmi-example Ignored Ignored Apr 13, 2026 4:32pm
next-wagmi-solana-bitcoin-example Ignored Ignored Apr 13, 2026 4:32pm
vue-wagmi-example Ignored Ignored Apr 13, 2026 4:32pm

Request Review

@changeset-bot

changeset-bot Bot commented Apr 2, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7fc256c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CTA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@Khizr97

Khizr97 commented Apr 13, 2026

Copy link
Copy Markdown
Contributor Author

I have read the CTA Document and I hereby sign the CTA

@svenvoskamp
svenvoskamp merged commit fdf31f5 into chore/khizr-fixes Apr 13, 2026
13 of 17 checks passed
@svenvoskamp
svenvoskamp deleted the chore/khizr-fixes-REOWN-3617 branch April 13, 2026 16:32
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants