From afa22b40f77b87dbb78845336214344e1ce88086 Mon Sep 17 00:00:00 2001 From: Jay Date: Tue, 20 Aug 2024 13:11:23 +0800 Subject: [PATCH] Fix decimals conversion (#800) * Create prettyNumber util * Fix decimals conversion --- .../src/components/modals/transfer-modal.tsx | 8 +-- .../src/components/transfer-amount-input.tsx | 72 ++++++++++++------- .../components/transfer-amount-section.tsx | 9 ++- apps/web/src/components/transfer.tsx | 5 +- apps/web/src/utils/balance.ts | 6 ++ 5 files changed, 67 insertions(+), 33 deletions(-) diff --git a/apps/web/src/components/modals/transfer-modal.tsx b/apps/web/src/components/modals/transfer-modal.tsx index b9021d98..4311705f 100644 --- a/apps/web/src/components/modals/transfer-modal.tsx +++ b/apps/web/src/components/modals/transfer-modal.tsx @@ -1,7 +1,7 @@ import { BaseBridge } from "../../bridges"; import { ChainConfig, Token } from "../../types"; import Modal from "../../ui/modal"; -import { formatBalance, getChainLogoSrc, toShortAdrress } from "../../utils"; +import { formatBalance, getChainLogoSrc, prettyNumber, toShortAdrress } from "../../utils"; import { Address } from "viem"; interface Props { @@ -13,7 +13,7 @@ interface Props { targetToken: Token; fee: { token: Token; value: bigint } | null | undefined; bridge: BaseBridge | undefined; - amount: bigint; + amount: { value: bigint; input: string }; busy: boolean; isOpen: boolean; onClose: () => void; @@ -73,7 +73,7 @@ function SourceTarget({ amount, }: { type: "source" | "target"; - amount: bigint; + amount: { value: bigint; input: string }; chain?: ChainConfig; token?: Token; address?: Address | null; @@ -89,7 +89,7 @@ function SourceTarget({ className={`max-w-[46%] truncate text-sm font-extrabold ${type === "source" ? "text-app-red" : "text-app-green"}`} > {type === "source" ? "-" : "+"} - {formatBalance(amount, token.decimals)} + {prettyNumber(amount.input)} diff --git a/apps/web/src/components/transfer-amount-input.tsx b/apps/web/src/components/transfer-amount-input.tsx index 121cd897..49bc2409 100644 --- a/apps/web/src/components/transfer-amount-input.tsx +++ b/apps/web/src/components/transfer-amount-input.tsx @@ -5,8 +5,8 @@ import { formatUnits, parseUnits } from "viem"; import Faucet from "./faucet"; interface Value { - input: string; - value: bigint; + input: string; // In units of ETH + value: bigint; // In units of wei valid: boolean; alert: string; } @@ -14,7 +14,8 @@ interface Value { interface Props { min?: bigint; max?: bigint; - token: Token; + sourceToken: Token; + targetToken: Token; value: Value; balance: bigint; loading?: boolean; @@ -27,7 +28,8 @@ export default function TransferAmountInput({ min, max, chain, - token, + sourceToken, + targetToken, value, balance, loading, @@ -37,7 +39,27 @@ export default function TransferAmountInput({ const [dynamicFont, setDynamicFont] = useState("text-[3rem] font-light"); const inputRef = useRef(null); const spanRef = useRef(null); - const tokenRef = useRef(token); + const sourceTokenRef = useRef(sourceToken); + + useEffect(() => { + if ( + sourceToken.decimals !== sourceTokenRef.current.decimals || + sourceToken.symbol !== sourceTokenRef.current.symbol + ) { + // Reset + sourceTokenRef.current = sourceToken; + onChange({ input: "", value: 0n, valid: true, alert: "" }); + } + }, [sourceToken, onChange]); + + const handleMaxInput = useCallback(() => { + const { value, input } = parseAmount( + formatUnits(max ?? 0n, sourceToken.decimals), + sourceToken.decimals, + minimumPrecision(sourceToken, targetToken), + ); + onChange({ valid: true, alert: "", value, input }); + }, [sourceToken, targetToken, max, onChange]); const handleChange = useCallback>( (e) => { @@ -48,16 +70,16 @@ export default function TransferAmountInput({ if (input) { if (!Number.isNaN(Number(input))) { - parsed = parseAmount(input, token.decimals); + parsed = parseAmount(input, sourceToken.decimals, minimumPrecision(sourceToken, targetToken)); if (balance < parsed.value) { valid = false; alert = "* Insufficient"; } else if (typeof min === "bigint" && parsed.value < min) { valid = false; - alert = `* Minimum transfer amount: ${formatBalance(min, token.decimals)}`; + alert = `* Minimum transfer amount: ${formatBalance(min, sourceToken.decimals)}`; } else if (typeof max === "bigint" && max < parsed.value) { valid = false; - alert = `* Maximum transfer amount: ${formatBalance(max, token.decimals, { + alert = `* Maximum transfer amount: ${formatBalance(max, sourceToken.decimals, { precision: 6, })}`; } @@ -67,13 +89,9 @@ export default function TransferAmountInput({ onChange({ valid, alert, ...parsed }); } }, - [min, max, balance, token.decimals, onChange], + [min, max, balance, sourceToken, targetToken, onChange], ); - const handleMaxInput = useCallback(() => { - onChange({ valid: true, alert: "", value: max ?? 0n, input: formatUnits(max ?? 0n, token.decimals) }); - }, [token.decimals, max, onChange]); - useEffect(() => { const inputWidth = inputRef.current?.clientWidth || 1; const spanWidth = spanRef.current?.clientWidth || 0; @@ -93,13 +111,6 @@ export default function TransferAmountInput({ } }, [value.input]); - useEffect(() => { - if (token.decimals !== tokenRef.current.decimals || token.symbol !== tokenRef.current.symbol) { - tokenRef.current = token; - onChange({ input: "", value: 0n, valid: true, alert: "" }); - } - }, [token, onChange]); - return (
- Balance: {formatBalance(balance, token.decimals)} + + Balance: {formatBalance(balance, sourceToken.decimals)} + - {chain.testnet ? : null} + {chain.testnet ? : null}
@@ -135,13 +148,24 @@ export default function TransferAmountInput({ ); } -function parseAmount(source: string, decimals: number) { +/** + * Parse amount + * @param source For example, the input value + * @param decimals The decimals value of token + * @param precision The precision you want to preserve + * @returns { value: bigint; input: string } + */ +function parseAmount(source: string, decimals: number, precision: number) { let input = ""; let value = 0n; const [i, d] = source.replace(/,/g, "").split(".").concat("-1"); // The commas must be removed or parseUnits will error if (i) { - input = d === "-1" ? i : d ? `${i}.${d.slice(0, decimals)}` : `${i}.`; + input = d === "-1" ? i : d ? `${i}.${d.slice(0, precision)}` : `${i}.`; value = parseUnits(input, decimals); } return { value, input }; } + +function minimumPrecision(token0: Token, token1: Token) { + return token0.decimals < token1.decimals ? token0.decimals : token1.decimals; +} diff --git a/apps/web/src/components/transfer-amount-section.tsx b/apps/web/src/components/transfer-amount-section.tsx index 1a9f7169..d6f36f8a 100644 --- a/apps/web/src/components/transfer-amount-section.tsx +++ b/apps/web/src/components/transfer-amount-section.tsx @@ -12,7 +12,8 @@ interface Amount { interface Props { min?: bigint; max?: bigint; - token: Token; + sourceToken: Token; + targetToken: Token; amount: Amount; balance: bigint; loading?: boolean; @@ -24,7 +25,8 @@ interface Props { export default function TransferAmountSection({ min, max, - token, + sourceToken, + targetToken, chain, amount, balance, @@ -38,7 +40,8 @@ export default function TransferAmountSection({ min={min} max={max ? BigInt(max) : undefined} loading={loading} - token={token} + sourceToken={sourceToken} + targetToken={targetToken} chain={chain} balance={balance} value={amount} diff --git a/apps/web/src/components/transfer.tsx b/apps/web/src/components/transfer.tsx index 8651272a..376aebf4 100644 --- a/apps/web/src/components/transfer.tsx +++ b/apps/web/src/components/transfer.tsx @@ -284,7 +284,8 @@ function Component() { amount={amount} loading={loadingBalance} balance={balance} - token={sourceToken} + sourceToken={sourceToken} + targetToken={targetToken} chain={sourceChain} max={maxTransfer} onChange={setAmount} @@ -318,7 +319,7 @@ function Component() { targetToken={targetToken} fee={fee} bridge={bridge} - amount={deferredAmount.value} + amount={deferredAmount} busy={isTransfering} isOpen={isOpen} onClose={() => setIsOpen(false)} diff --git a/apps/web/src/utils/balance.ts b/apps/web/src/utils/balance.ts index 81aff043..349aab29 100644 --- a/apps/web/src/utils/balance.ts +++ b/apps/web/src/utils/balance.ts @@ -17,6 +17,12 @@ export function formatBalance(value: bigint, decimals = 18, options?: { precisio return `${_integers}${_decimals.slice(1)}`; } +export function prettyNumber(value: string | number) { + const [i, d] = value.toString().split("."); + const integers = i.replace(/(?=(?!^)(\d{3})+$)/g, ","); + return d ? `${integers}.${d}` : integers; +} + export async function getBalance(address: Address, token: Token, publicClient: PublicClient) { let value = 0n; if (token.type === "native") {