Skip to content

fix(widget): parse fee/gas wei amounts without overflowing BigInt - #860

Open
gomesalexandre wants to merge 1 commit into
lifinance:mainfrom
gomesalexandre:fix_bigint_toFixed_fee_overflow
Open

gomesalexandre wants to merge 1 commit into
lifinance:mainfrom
gomesalexandre:fix_bigint_toFixed_fee_overflow

Conversation

@gomesalexandre

Copy link
Copy Markdown

What

getAccumulatedFeeCostsBreakdown (exported publicly from @lifi/widget/shared) and useGasSufficiency throw SyntaxError: Cannot convert Xe+Y to a BigInt for any real fee or gas amount at or above 1e21 wei. This is not a corner case — routes involving high-supply, low-value tokens (SHIB, PEPE, and similar) routinely cross this threshold.

Root cause

Three sites (packages/widget/src/utils/fees.ts:117, packages/widget/src/hooks/useGasSufficiency.ts:109 and :137) parsed integer wei strings like this:

const feeAmount = BigInt(Number(feeCost.amount).toFixed(0) || 0)

Number.prototype.toFixed switches to exponential notation for values >= 1e21 (e.g. "1.25e+24"), and BigInt() rejects exponential-notation strings. Separately, round-tripping through Number at all loses precision above Number.MAX_SAFE_INTEGER (2^53) even for values that don't crash.

Evidence — real production data, not synthetic

Live GET https://li.quest/v1/quote for SHIB → USDT (chain 1 → 56):

{
  "name": "LIFI Fixed Fee",
  "token": { "symbol": "SHIB", "decimals": 18 },
  "amount": "2500000000000000000000000",
  "included": true
}

2500000000000000000000000 = 2.5e24, well past the crash threshold.

node -e "BigInt(Number('2500000000000000000000000').toFixed(0))"
Uncaught SyntaxError: Cannot convert 2.5e+24 to a BigInt

Confirmed this crashes getAccumulatedFeeCostsBreakdown(route, true) when fed a route carrying this real fee.

Fix

Added parseAmountToBigInt(amount: string): bigint in fees.ts, exported and reused at all three sites:

export const parseAmountToBigInt = (amount: string): bigint => {
  const [whole] = String(amount).split('.')
  return whole ? BigInt(whole) : 0n
}

No Number round-trip, so no exponential-notation crash and no precision loss. Grepped the whole repo (grep -rn "BigInt(Number" --include="*.ts" --include="*.tsx") to confirm these were the only three occurrences of the buggy pattern — all three are fixed.

Honest scoping

All current in-widget render call sites of getAccumulatedFeeCostsBreakdown (RouteCard.tsx, RouteCardEssentials.tsx, RouteDetails.tsx, TransactionReview.tsx, TokenValueBottomSheet.tsx, TransactionFailedButtons.tsx, RouteProviderCard.tsx) use the default included = false, and across five routes I probed I could not produce a non-included fee crossing the threshold — so this is a proven crash on the public getAccumulatedFeeCostsBreakdown(route, true) API surface, not an observed in-widget render crash today. Worth noting: there is no ErrorBoundary anywhere in packages/widget/src (confirmed via grep), so if a non-included fee or a gas cost ever does cross the threshold, the crash would unmount the whole integrator tree with no recovery — getGasCostsBreakdown in particular processes gas costs through the same buggy expression completely unfiltered by included (that field doesn't apply to gas costs at all), on every single call regardless of the included param passed in.

One minor, unreachable-with-real-data behavior change worth flagging in review: "5.999".split('.') truncates to 5, where the old Number(...).toFixed(0) rounded to 6. LI.FI wei amounts are always integers on the wire (confirmed via the live capture above), so this isn't reachable in practice, but noting it for transparency.

receipts

$ pnpm vitest run src/utils/fees.test.ts
 Test Files  1 passed (1)
      Tests  8 passed (8)

Red-before/green-after independently confirmed: stashed the fix and re-ran the same test file against the unmodified code — 7/8 tests failed, including the exact production crash:

FAIL src/utils/fees.test.ts > getAccumulatedFeeCostsBreakdown > does not throw for a route carrying a real above-1e21 included fee
AssertionError: expected [Function] to not throw an error but 'SyntaxError: Cannot convert 2.5e+24 t…' was thrown

Full package suite after the fix:

$ pnpm vitest run
 Test Files  10 passed (10)
      Tests  92 passed (92)

pnpm check:types and biome check clean on all changed files (pre-existing implicit-any warnings elsewhere in useGasSufficiency.ts, unrelated to the two lines touched here, are unchanged from baseline).

Codex adversarial review timed out with no output after 5 minutes; killed it and did a thorough self-review instead (edge cases tested directly: negative amounts, leading +, scientific-notation input, whitespace, null/undefined, leading zeros — all match or strictly improve on prior behavior, no new failure modes; confirmed via repo-wide grep that no other instance of the buggy pattern remains).

risk

Low — pure parsing fix, no behavior change for any value below the crash threshold, no new dependencies.

fees.ts and useGasSufficiency.ts round-tripped LI.FI's integer wei strings
through Number().toFixed(0) before parsing to BigInt. toFixed() switches to
exponential notation above 1e21 (e.g. "1.25e+24"), and BigInt() rejects
exponential-notation strings, throwing SyntaxError.

A live GET https://li.quest/v1/quote for SHIB -> USDT returns a "LIFI Fixed
Fee" of 2500000000000000000000000 wei (2.5e24) with included: true -
confirmed this crashes getAccumulatedFeeCostsBreakdown(route, true), the
public @lifi/widget/shared export, on real production data.

Added parseAmountToBigInt(amount: string): bigint, which parses the integer
part of the wei string directly (no Number round-trip), and replaced all
three call sites of the buggy pattern (grepped the whole repo to confirm no
others remain). Also fixes the sub-2^53 precision drift the Number
round-trip caused on smaller amounts (confirmed with a real Polygon gas
cost above Number.MAX_SAFE_INTEGER).

Honest scoping: all current in-widget render call sites use the default
included=false and none of five probed routes produced a non-included fee
crossing the threshold, so this is a proven crash on the public
getAccumulatedFeeCostsBreakdown(route, true) API surface, not an observed
in-widget render crash - though there is no ErrorBoundary anywhere in
packages/widget/src, so an included:false fee crossing the threshold in
the future would take down the whole tree with no recovery.
@gomesalexandre
gomesalexandre marked this pull request as ready for review September 1, 2026 14:42
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b631d94

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
@lifi/widget Patch
@lifi/widget-checkout Patch
connectkit Patch
deposit-flow Patch
dynamic Patch
nextjs Patch
nextjs15 Patch
nft-checkout Patch
nuxt-app Patch
privy-ethers-example Patch
privy Patch
rainbowkit Patch
react-router Patch
remix Patch
reown Patch
svelte Patch
tanstack-router-example Patch
vite-project Patch
vue Patch
zustand-widget-config Patch

Not sure what this means? Click here to learn what changesets are.

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

This branch has not been deployed

No deployments
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