From 483a605d024d52d70812604206b738b8c3bd5856 Mon Sep 17 00:00:00 2001 From: Kanan <93033289+kriss39@users.noreply.github.com> Date: Fri, 11 Sep 2026 01:07:29 +0400 Subject: [PATCH] fix(widget): stop formatInputAmount flipping a negative exponent The leading-zero cleanup stripped the minus with an unanchored alternation, `/^0+|-/`. With no `g` flag the engine skips past `^0+` when the value does not start with a zero and removes the first `-` it finds anywhere, which for an exponential value is the one in the exponent. `1e-1` normalized to `1e1` and `1e-7` to `1e7`. Only values whose whole string lands in the integer part are affected, so `2.5e-8` survived while `1e-1` did not, and the corruption appeared on blur rather than while typing. Anchor the sign strip to the start and expand exponential notation into plain decimal digits. The expansion shifts the point textually so no precision is lost, and it makes the value usable: parseUnits rejects every exponential string, `1e1` included, so leaving the value in that form only traded a wrong amount for a failing route request. --- .changeset/format-input-amount-exponent.md | 5 ++++ packages/widget/src/utils/format.test.ts | 19 ++++++++++++ packages/widget/src/utils/format.ts | 35 ++++++++++++++++++++-- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 .changeset/format-input-amount-exponent.md diff --git a/.changeset/format-input-amount-exponent.md b/.changeset/format-input-amount-exponent.md new file mode 100644 index 000000000..b08255ca2 --- /dev/null +++ b/.changeset/format-input-amount-exponent.md @@ -0,0 +1,5 @@ +--- +'@lifi/widget': patch +--- + +Fix `formatInputAmount` flipping the sign of a negative exponent, so `1e-1` no longer normalizes to `1e1`. Exponential input is now expanded to plain decimal digits, which `parseUnits` accepts. diff --git a/packages/widget/src/utils/format.test.ts b/packages/widget/src/utils/format.test.ts index 0bb26b907..7da7a04e6 100644 --- a/packages/widget/src/utils/format.test.ts +++ b/packages/widget/src/utils/format.test.ts @@ -22,6 +22,25 @@ describe('formatInputAmount', () => { expect(formatInputAmount('123.45000', 6, false)).toBe('123.45') }) + it('should expand exponential notation without flipping the exponent', () => { + expect(formatInputAmount('1e-1', 18)).toBe('0.1') + expect(formatInputAmount('9e-1', 18)).toBe('0.9') + expect(formatInputAmount('1e-7', 18)).toBe('0.0000001') + expect(formatInputAmount('2.5e-8', 18)).toBe('0.000000025') + expect(formatInputAmount('1E-1', 18)).toBe('0.1') + expect(formatInputAmount('1e2', 18)).toBe('100') + expect(formatInputAmount('1.5e3', 18)).toBe('1500') + }) + + it('should keep exponential notation intact while typing', () => { + expect(formatInputAmount('1e-1', 18, true)).toBe('1e-1') + expect(formatInputAmount('2.5e-8', 18, true)).toBe('2.5e-8') + }) + + it('should drop leading zeros after a minus sign', () => { + expect(formatInputAmount('-00.5', 18)).toBe('0.5') + }) + it('should handle invalid input', () => { expect(formatInputAmount('abc')).toBe('') expect(formatInputAmount('-')).toBe('') diff --git a/packages/widget/src/utils/format.ts b/packages/widget/src/utils/format.ts index 9287b3a63..28fa6ef00 100644 --- a/packages/widget/src/utils/format.ts +++ b/packages/widget/src/utils/format.ts @@ -47,6 +47,31 @@ export function formatSlippage( return parsedSlippage.toString() } +/** + * Rewrites exponential notation into plain decimal digits, shifting the point + * textually so no precision is lost. `parseUnits` rejects exponential strings, + * so a value kept in that form would be unusable downstream. + */ +function expandExponential(amount: string): string { + const match = /^([+-]?)(\d*)(?:\.(\d*))?[eE]([+-]?\d+)$/.exec(amount) + if (!match) { + return amount + } + const [, sign, integer = '', fraction = '', exponent] = match + const digits = `${integer}${fraction}` + if (!digits) { + return amount + } + const pointIndex = integer.length + Number(exponent) + if (pointIndex <= 0) { + return `${sign}0.${'0'.repeat(-pointIndex)}${digits}` + } + if (pointIndex >= digits.length) { + return `${sign}${digits}${'0'.repeat(pointIndex - digits.length)}` + } + return `${sign}${digits.slice(0, pointIndex)}.${digits.slice(pointIndex)}` +} + /** * Formats a user input amount string, normalizing it and optionally limiting decimal places. * @param amount - The amount string to format (e.g., '123.45', '1,23', '0..') @@ -91,6 +116,11 @@ export function formatInputAmount( return '' } + // Left in exponential form while typing so the caret is not moved mid-entry + if (!returnInitial) { + formattedAmount = expandExponential(formattedAmount) + } + // Split and limit decimals let [integer, fraction = ''] = formattedAmount.split('.') if (decimals !== null && fraction.length > decimals) { @@ -104,8 +134,9 @@ export function formatInputAmount( return `${integer}${fraction ? `.${fraction}` : ''}` } - // Remove leading zeros and minus sign - integer = integer.replace(/^0+|-/, '') + // Remove the minus sign and leading zeros. The sign is anchored: an unanchored + // `-` also matched the one in an exponent, turning '1e-1' into '1e1'. + integer = integer.replace(/^-/, '').replace(/^0+/, '') // Remove trailing zeros fraction = fraction.replace(/(0+)$/, '')