From f274044f46f370d98a1484f8b170cf108d411f3b Mon Sep 17 00:00:00 2001 From: bigboydiamonds <57741810+bigboydiamonds@users.noreply.github.com> Date: Wed, 8 May 2024 11:28:45 -0700 Subject: [PATCH] Fe/format amount (#2598) * formatAmount util function, use rounded shortened values in Bridge/Swap Input * Update Bridge/Swap page with parsed / formatted balances * Clean imports --- .../components/PortfolioTokenAsset.tsx | 10 +-- .../StateManagedBridge/InputContainer.tsx | 17 +++-- .../StateManagedSwap/SwapInputContainer.tsx | 14 ++-- .../ui/SelectSpecificTokenButton.tsx | 14 ++-- .../synapse-interface/utils/formatAmount.ts | 69 +++++++++++++++++++ .../utils/getParsedBalance.ts | 9 ++- .../utils/trimTrailingZeroesAfterDecimal.ts | 2 +- 7 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 packages/synapse-interface/utils/formatAmount.ts diff --git a/packages/synapse-interface/components/Portfolio/components/PortfolioTokenAsset.tsx b/packages/synapse-interface/components/Portfolio/components/PortfolioTokenAsset.tsx index 75dfa98294..171826c312 100644 --- a/packages/synapse-interface/components/Portfolio/components/PortfolioTokenAsset.tsx +++ b/packages/synapse-interface/components/Portfolio/components/PortfolioTokenAsset.tsx @@ -13,6 +13,8 @@ import { getParsedBalance } from '@/utils/getParsedBalance' import { useGasEstimator } from '@/utils/hooks/useGasEstimator' import GasIcon from '@/components/icons/GasIcon' import { trimTrailingZeroesAfterDecimal } from '@/utils/trimTrailingZeroesAfterDecimal' +import { formatAmount } from '@/utils/formatAmount' +import { formatBigIntToString } from '@/utils/bigint/format' const handleFocusOnBridgeInput = () => { inputRef.current?.focus() @@ -39,8 +41,8 @@ export const PortfolioTokenAsset = ({ ? decimals : decimals[portfolioChainId] - const parsedBalance = getParsedBalance(balance, tokenDecimals, 4) - const parsedBalanceLong = getParsedBalance(balance, tokenDecimals, 8) + const parsedBalance = getParsedBalance(balance, tokenDecimals) + const formattedBalance = formatAmount(parsedBalance) const isDisabled = false const isPortfolioChainSelected = fromChainId === portfolioChainId @@ -80,13 +82,13 @@ export const PortfolioTokenAsset = ({ ) : (
- {parsedBalanceLong} {symbol} + {parsedBalance} {symbol}
) } >
- {parsedBalance} {symbol} + {formattedBalance} {symbol}
diff --git a/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx b/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx index 144f256f06..9317d19d02 100644 --- a/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx +++ b/packages/synapse-interface/components/StateManagedBridge/InputContainer.tsx @@ -12,7 +12,6 @@ import { import { ChainSelector } from '@/components/ui/ChainSelector' import { TokenSelector } from '@/components/ui/TokenSelector' import { AmountInput } from '@/components/ui/AmountInput' -import { formatBigIntToString } from '@/utils/bigint/format' import { cleanNumberInput } from '@/utils/cleanNumberInput' import { ConnectToNetworkButton, @@ -30,6 +29,7 @@ import { AvailableBalance } from './AvailableBalance' import { useGasEstimator } from '../../utils/hooks/useGasEstimator' import { getParsedBalance } from '@/utils/getParsedBalance' import { MaxButton } from './MaxButton' +import { formatAmount } from '../../utils/formatAmount' export const inputRef = React.createRef() @@ -47,8 +47,8 @@ export const InputContainer = () => { const balance: bigint = balances[fromChainId]?.find( (token) => token.tokenAddress === addresses?.[fromChainId] )?.balance - const parsedBalance = getParsedBalance(balance, tokenDecimals, 4) - const fullParsedBalance = formatBigIntToString(balance, tokenDecimals) + const parsedBalance = getParsedBalance(balance, tokenDecimals) + const formattedBalance = formatAmount(parsedBalance) const hasValidFromSelections: boolean = useMemo(() => { return Boolean(fromChainId && fromToken) @@ -68,15 +68,14 @@ export const InputContainer = () => { } = useGasEstimator() const isInputMax = - maxBridgeableGas?.toString() === fromValue || - fullParsedBalance === fromValue + maxBridgeableGas?.toString() === fromValue || parsedBalance === fromValue const onMaxBalance = useCallback(async () => { if (hasValidGasEstimateInputs()) { const bridgeableBalance = await estimateBridgeableBalanceCallback() if (isNull(bridgeableBalance)) { - dispatch(updateFromValue(fullParsedBalance)) + dispatch(updateFromValue(parsedBalance)) } else if (bridgeableBalance > 0) { dispatch(updateFromValue(bridgeableBalance?.toString())) } else { @@ -87,12 +86,12 @@ export const InputContainer = () => { }) } } else { - dispatch(updateFromValue(fullParsedBalance)) + dispatch(updateFromValue(parsedBalance)) } }, [ fromChainId, fromToken, - fullParsedBalance, + parsedBalance, hasValidGasEstimateInputs, estimateBridgeableBalanceCallback, ]) @@ -156,7 +155,7 @@ export const InputContainer = () => { />
{ const inputRef = useRef(null) @@ -53,14 +55,12 @@ export const SwapInputContainer = () => { (token) => token.tokenAddress === swapFromToken?.addresses[swapChainId] ) - const parsedBalance = tokenData?.parsedBalance const balance = tokenData?.balance - const parsedFullBalance = formatBigIntToString( - balance, - tokenData?.token?.decimals[swapChainId] - ) + const decimals = tokenData?.token?.decimals[swapChainId] + const parsedBalance = getParsedBalance(balance, decimals) + const formattedBalance = formatAmount(parsedBalance) - const isInputMax = parsedFullBalance === swapFromValue + const isInputMax = parsedBalance === swapFromValue useEffect(() => { if ( @@ -143,7 +143,7 @@ export const SwapInputContainer = () => { Available:{' '} - {parsedBalance ?? '0.0'} + {formattedBalance ?? '0.0'} { const portfolioBalances = usePortfolioBalances() - const parsedBalance = portfolioBalances[chainId]?.find( + const tokenData = portfolioBalances[chainId]?.find( (tb) => tb.token.addresses[chainId] === token.addresses[chainId] - )?.parsedBalance + ) + const decimals = tokenData?.token?.decimals[chainId] + const balance = tokenData?.balance + const parsedBalance = getParsedBalance(balance, decimals) + const formattedBalance = formatAmount(parsedBalance) return (
@@ -91,7 +97,7 @@ const ButtonContent = memo( token={token} showAllChains={showAllChains} isOrigin={isOrigin} - parsedBalance={parsedBalance} + parsedBalance={formattedBalance} />
) diff --git a/packages/synapse-interface/utils/formatAmount.ts b/packages/synapse-interface/utils/formatAmount.ts new file mode 100644 index 0000000000..19f9c36d54 --- /dev/null +++ b/packages/synapse-interface/utils/formatAmount.ts @@ -0,0 +1,69 @@ +interface FormatOptions { + fullAmount?: boolean + standardDigits?: number + useCompactNotation?: boolean + compactDigits?: number + minimumAmount?: number + roundingMode?: string +} + +export const formatAmount = ( + amount: string, + options?: FormatOptions +): string => { + if (amount === '') return '' + + const floatAmount = parseFloat(amount) + + try { + if (!Number.isFinite(floatAmount)) { + throw new TypeError(`"${amount}" is not a finite number`) + } + } catch ({ name, message }) { + console.error(name, message) + return amount + } + + const fullAmount = options?.fullAmount ?? false + const standardDigits = options?.standardDigits ?? 4 + const useCompactNotation = options?.useCompactNotation ?? true + const compactDigits = options?.compactDigits ?? useCompactNotation ? 2 : 0 + const minimumAmount = options?.minimumAmount ?? 0.0001 + + const locales = 'en-UK' + + if (!floatAmount) return '0.0' + + if (fullAmount) return Intl.NumberFormat(locales).format(floatAmount) + + if (floatAmount < minimumAmount) return `< ${minimumAmount}` + + const absAmount = Math.abs(floatAmount) + + if (absAmount < 0.0001) + return Intl.NumberFormat(locales, { + maximumSignificantDigits: 1, + }).format(floatAmount) + + if (absAmount < 1) + return Intl.NumberFormat(locales, { + minimumFractionDigits: standardDigits, + }).format(floatAmount) + + if (absAmount < 1000) + return Intl.NumberFormat(locales, { + minimumSignificantDigits: standardDigits, + maximumSignificantDigits: standardDigits, + }).format(floatAmount) + + if (absAmount < 1000000) + return Intl.NumberFormat(locales, { + maximumFractionDigits: 0, + }).format(floatAmount) + + return Intl.NumberFormat(locales, { + minimumFractionDigits: compactDigits, + maximumFractionDigits: compactDigits, + notation: useCompactNotation ? 'compact' : 'standard', + }).format(floatAmount) +} diff --git a/packages/synapse-interface/utils/getParsedBalance.ts b/packages/synapse-interface/utils/getParsedBalance.ts index 7c76b042f8..4772339db7 100644 --- a/packages/synapse-interface/utils/getParsedBalance.ts +++ b/packages/synapse-interface/utils/getParsedBalance.ts @@ -1,13 +1,12 @@ +import { trimTrailingZeroesAfterDecimal } from '@/utils/trimTrailingZeroesAfterDecimal' import { formatBigIntToString } from './bigint/format' -import { hasOnlyZeroes } from './hasOnlyZeroes' export const getParsedBalance = ( balance: bigint, decimals: number, places?: number ) => { - const formattedBalance = formatBigIntToString(balance, decimals, places) - const verySmallBalance = balance > 0n && hasOnlyZeroes(formattedBalance) - - return verySmallBalance ? '< 0.001' : formattedBalance + return trimTrailingZeroesAfterDecimal( + formatBigIntToString(balance, decimals, places) + ) } diff --git a/packages/synapse-interface/utils/trimTrailingZeroesAfterDecimal.ts b/packages/synapse-interface/utils/trimTrailingZeroesAfterDecimal.ts index a627a4361f..ee9e9850c1 100644 --- a/packages/synapse-interface/utils/trimTrailingZeroesAfterDecimal.ts +++ b/packages/synapse-interface/utils/trimTrailingZeroesAfterDecimal.ts @@ -1,7 +1,7 @@ export const trimTrailingZeroesAfterDecimal = (input: string): string => { const parts = input?.split('.') - if (parts.length === 2) { + if (parts?.length === 2) { const integerPart = parts[0] let fractionalPart = parts[1]