diff --git a/packages/frontend/app/components/LiquidityDialog.tsx b/packages/frontend/app/components/LiquidityDialog.tsx index edc4963f39..786ea76376 100644 --- a/packages/frontend/app/components/LiquidityDialog.tsx +++ b/packages/frontend/app/components/LiquidityDialog.tsx @@ -3,6 +3,7 @@ import type { ChangeEvent } from 'react' import { useEffect, useId, useRef, useState } from 'react' import { Button, Dialog, Flex, Text, TextField } from '@radix-ui/themes' import { renderFieldError } from '~/lib/form-errors' +import { parseAmount } from '~/shared/utils' type BasicAsset = { code: string @@ -22,24 +23,19 @@ export const LiquidityDialog = ({ type, asset }: LiquidityDialogProps) => { - const [actualAmount, setActualAmount] = useState(0) + const [actualAmount, setActualAmount] = useState('0') const [errorMessage, setErrorMessage] = useState('') const amountId = useId() const handleChange = (e: ChangeEvent) => { - const userInput = e.target.value - const scaledInput = parseFloat(userInput) * Math.pow(10, asset.scale) - const integerScaledInput = Math.floor(scaledInput) - if (scaledInput < 0) { - const error = 'The amount should be a positive value' - setErrorMessage(error) - } else if (scaledInput !== integerScaledInput) { - const error = 'The asset scale cannot accomodate this value' - setErrorMessage(error) + const result = parseAmount(e.target.value, asset.scale) + if ('error' in result) { + setErrorMessage(result.error) + setActualAmount('0') } else { setErrorMessage('') + setActualAmount(result.amount.toString()) } - setActualAmount(integerScaledInput) } const inputRef = useRef(null) diff --git a/packages/frontend/app/shared/utils.ts b/packages/frontend/app/shared/utils.ts index bbcb147fc0..5348007eb2 100644 --- a/packages/frontend/app/shared/utils.ts +++ b/packages/frontend/app/shared/utils.ts @@ -55,6 +55,39 @@ export function formatAmount(amount: string, scale: number) { return `${integerPart}.${fractionalPart}` } +/** + * Converts a user-entered decimal amount (e.g. "0.29") into asset base units + * (e.g. 29n for scale 2) using string arithmetic, to avoid floating-point + * precision errors. + */ +export function parseAmount( + input: string, + scale: number +): { amount: bigint } | { error: string } { + const value = input.trim() + + if (value.startsWith('-')) { + return { error: 'The amount should be a positive value' } + } + + const match = /^(\d*)(?:\.(\d*))?$/.exec(value) + if (!value || !match || (!match[1] && !match[2])) { + return { error: 'The amount should be a valid number' } + } + + const [, whole, fraction = ''] = match + if (fraction.length > scale) { + return { error: 'The asset scale cannot accommodate this value' } + } + + const amount = BigInt((whole || '0') + fraction.padEnd(scale, '0')) + if (amount === BigInt(0)) { + return { error: 'The amount should be a positive value' } + } + + return { amount } +} + export function capitalize(str: string) { return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() }