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+)$/, '')