Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/format-input-amount-exponent.md
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 19 additions & 0 deletions packages/widget/src/utils/format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('')
Expand Down
35 changes: 33 additions & 2 deletions packages/widget/src/utils/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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..')
Expand Down Expand Up @@ -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) {
Expand All @@ -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+)$/, '')

Expand Down