diff --git a/src/bridges/base.ts b/src/bridges/base.ts index 6a4ba8c9a..30e95c3d1 100644 --- a/src/bridges/base.ts +++ b/src/bridges/base.ts @@ -126,6 +126,10 @@ export abstract class BaseBridge { return this.targetChain; } + getCrossInfo() { + return this.crossInfo; + } + getEstimateTime() { return this.estimateTime; } diff --git a/src/components/balance-input.tsx b/src/components/balance-input.tsx index 00e012f4c..bc924255e 100644 --- a/src/components/balance-input.tsx +++ b/src/components/balance-input.tsx @@ -3,17 +3,24 @@ import Input from "@/ui/input"; import InputAlert from "@/ui/input-alert"; import { formatBalance, getTokenLogoSrc } from "@/utils"; import Image from "next/image"; -import { ChangeEventHandler, MouseEventHandler, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { formatUnits, parseUnits } from "viem"; +import { ChangeEventHandler, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { parseUnits } from "viem"; + +enum ErrorCode { + INSUFFICIENT = 1, + REQUIRE_MORE, + REQUIRE_LESS, +} interface Props { placeholder?: string; balance?: bigint; max?: bigint; + min?: bigint; compact?: boolean; autoFocus?: boolean; disabled?: boolean; - suffix?: "symbol" | "max"; + suffix?: "symbol"; enabledDynamicStyle?: boolean; value: InputValue; token: Token | undefined; @@ -28,6 +35,7 @@ export function BalanceInput({ placeholder, balance, max, + min, compact, autoFocus, disabled, @@ -41,16 +49,13 @@ export function BalanceInput({ onChange = () => undefined, onTokenChange = () => undefined, }: Props) { - const tokenRef = useRef(); const spanRef = useRef(null); const inputRef = useRef(null); - const insufficientRef = useRef(false); - const exceededRef = useRef(false); + const balanceRef = useRef(balance); + const tokenRef = useRef(token); - const [enablingMax, setEnablingMax] = useState(false); const [dynamicStyle, setDynamicStyle] = useState("text-sm font-medium"); - - const isExceeded = useMemo(() => (typeof max === "bigint" && max < value.value ? true : false), [max, value]); + const [errorCode, setErrorCode] = useState(); const _placeholder = useMemo(() => { if (token && compact) { @@ -71,36 +76,50 @@ export function BalanceInput({ if (input) { if (token && !Number.isNaN(Number(input))) { - parsed = parseValue(input, token.decimals); - insufficientRef.current = balance !== undefined && balance < parsed.value ? true : false; - exceededRef.current = typeof max === "bigint" && max < parsed.value ? true : false; - valid = !(insufficientRef.current || exceededRef.current); + parsed = parseAmount(input, token.decimals); + if (typeof min === "bigint" && parsed.value < min) { + valid = false; + setErrorCode(ErrorCode.REQUIRE_MORE); + } else if (typeof max === "bigint" && max < parsed.value) { + valid = false; + setErrorCode(ErrorCode.REQUIRE_LESS); + } else if (typeof balance === "bigint" && balance < parsed.value) { + valid = false; + setErrorCode(ErrorCode.INSUFFICIENT); + } else { + setErrorCode(undefined); + } onChange({ valid, ...parsed }); } } else { + setErrorCode(undefined); onChange({ valid, ...parsed }); } - - setEnablingMax(false); }, - [token, max, balance, onChange], + [token, max, min, balance, onChange], ); - const handleMax = useCallback>( - (e) => { - e.stopPropagation(); - if (typeof max === "bigint" && token) { - const decimals = token.decimals; - const parsed = parseValue(formatUnits(max, decimals), decimals); - insufficientRef.current = balance !== undefined && balance < parsed.value ? true : false; - exceededRef.current = max && max < parsed.value ? true : false; - const valid = !(insufficientRef.current || exceededRef.current); - onChange({ valid, ...parsed }); - setEnablingMax(true); + useEffect(() => { + if (balance !== balanceRef.current) { + balanceRef.current = balance; + if (typeof balance === "bigint") { + if (balance < value.value) { + setErrorCode(ErrorCode.INSUFFICIENT); + onChange({ ...value, valid: false }); + } + } else { + setErrorCode(undefined); + onChange({ ...value, valid: true }); } - }, - [max, token, balance, onChange], - ); + } + }, [balance, value, onChange]); + + useEffect(() => { + if (token?.decimals !== tokenRef.current?.decimals) { + tokenRef.current = token; + onChange({ valid: true, input: "", value: 0n }); + } + }, [token, onChange]); useEffect(() => { if (enabledDynamicStyle) { @@ -123,31 +142,7 @@ export function BalanceInput({ setDynamicStyle("text-[1.125rem] font-medium"); } } - }, [value, enabledDynamicStyle]); - - useEffect(() => { - // Fire onChange to update value - if (token && token.decimals !== tokenRef.current?.decimals) { - const parsed = parseValue(value.input, token.decimals); - insufficientRef.current = balance !== undefined && balance < parsed.value ? true : false; - exceededRef.current = typeof max === "bigint" && max < parsed.value ? true : false; - const valid = !(insufficientRef.current || exceededRef.current); - onChange({ valid, ...parsed }); - } - tokenRef.current = token; - }, [value, token, balance, max, onChange]); - - useEffect(() => { - // Recalculate the maximum value - if (token && enablingMax && isExceeded) { - const decimals = token.decimals; - const parsed = parseValue(formatUnits(max ?? 0n, decimals), decimals); - insufficientRef.current = balance !== undefined && balance < parsed.value ? true : false; - exceededRef.current = typeof max === "bigint" && max < parsed.value ? true : false; - const valid = !(insufficientRef.current || exceededRef.current); - onChange({ valid, ...parsed }); - } - }, [token, max, balance, enablingMax, isExceeded, onChange]); + }, [value.input, enabledDynamicStyle]); return (
{token.symbol} - ) : suffix === "max" ? ( - ) : null ) : (
@@ -218,21 +205,15 @@ export function BalanceInput({ > Refresh - {typeof max === "bigint" ? ( - - ) : null}
) : null} - {value.valid ? null : exceededRef.current ? ( - - ) : insufficientRef.current ? ( + {errorCode === ErrorCode.INSUFFICIENT ? ( + ) : errorCode === ErrorCode.REQUIRE_LESS ? ( + + ) : errorCode === ErrorCode.REQUIRE_MORE ? ( + ) : null} @@ -242,7 +223,7 @@ export function BalanceInput({ ); } -function parseValue(source: string, decimals: number) { +function parseAmount(source: string, decimals: number) { let input = ""; let value = 0n; const [i, d] = source.replace(/,/g, "").split(".").concat("-1"); // The commas must be removed or parseUnits will error diff --git a/src/components/transfer-action.tsx b/src/components/transfer-action.tsx index 7a5205692..70f2b690f 100644 --- a/src/components/transfer-action.tsx +++ b/src/components/transfer-action.tsx @@ -52,7 +52,8 @@ export default function TransferAction({ recipient, transferable, transferAmount bridgeInstance && (bridgeInstance.getCategory().startsWith("xtoken") || bridgeFee) && transferable && - transferAmount.value && + transferAmount.input && + transferAmount.valid && transferAmount.value < transferable && isAddress(recipient ?? "") ) diff --git a/src/components/transfer.tsx b/src/components/transfer.tsx index 1bfcfa363..e55c50cf1 100644 --- a/src/components/transfer.tsx +++ b/src/components/transfer.tsx @@ -316,6 +316,7 @@ export default function Transfer() { placeholder="0" enabledDynamicStyle balance={sourceBalance?.value} + min={bridgeInstance?.getCrossInfo()?.min} value={transferAmount} token={sourceToken} balanceLoading={balanceLoading} diff --git a/src/config/chains/darwinia.ts b/src/config/chains/darwinia.ts index a46bbea20..75ec5f206 100644 --- a/src/config/chains/darwinia.ts +++ b/src/config/chains/darwinia.ts @@ -69,6 +69,7 @@ export const darwiniaChain: ChainConfig = { target: { network: "ethereum", symbol: "RING" }, bridge: { category: "helix-sub2ethv2(lock)" }, action: "issue", + min: 1000000000000000000000000n, }, { target: { network: "polygon", symbol: "RING" }, diff --git a/src/types/cross-chain.ts b/src/types/cross-chain.ts index 72e940fb1..1cdd7c528 100644 --- a/src/types/cross-chain.ts +++ b/src/types/cross-chain.ts @@ -26,6 +26,7 @@ export type CrossChain = baseFee?: never; action?: never; hidden?: boolean; + min?: bigint; // Minimum transfer amount } | { target: Target; @@ -35,6 +36,7 @@ export type CrossChain = baseFee?: never; action?: never; hidden?: boolean; + min?: bigint; // Minimum transfer amount } | { target: Target; @@ -44,6 +46,7 @@ export type CrossChain = baseFee?: never; action: Action; hidden?: boolean; + min?: bigint; // Minimum transfer amount } | { target: Target; @@ -53,6 +56,7 @@ export type CrossChain = baseFee: bigint; action: Action; hidden?: boolean; + min?: bigint; // Minimum transfer amount }; export type AvailableBridges = {