From fd8c2c9eb3d63503c7b65a2425db18cf3624486e Mon Sep 17 00:00:00 2001 From: jorgen Date: Wed, 17 Dec 2025 14:25:05 +0100 Subject: [PATCH 1/5] it works! --- package.json | 1 + src/hooks/index.ts | 1 + src/hooks/useTokenInput.ts | 186 ++---------------- .../TokenInput/DestinationTokenDisplay.tsx | 103 ++++++++-- .../TokenInput/OriginTokenInput.tsx | 87 ++++++-- yarn.lock | 5 + 6 files changed, 183 insertions(+), 200 deletions(-) diff --git a/package.json b/package.json index 7591cb046..94f37ebf2 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "react-dom": "v18", "react-feather": "^2.0.9", "react-hotkeys-hook": "^5.1.0", + "react-number-format": "^5.4.4", "react-pro-sidebar": "^1.1.0", "react-router-dom": "v5", "react-tooltip": "^5.18.0", diff --git a/src/hooks/index.ts b/src/hooks/index.ts index daec305b5..06c3e66af 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -20,3 +20,4 @@ export * from "./useRewardSummary"; export * from "./useElapsedSeconds"; export * from "./feature-flags/useFeatureFlag"; export * from "./useTokenInput"; +export { default as useTokenInput } from "./useTokenInput"; diff --git a/src/hooks/useTokenInput.ts b/src/hooks/useTokenInput.ts index 75d935550..3c44b6791 100644 --- a/src/hooks/useTokenInput.ts +++ b/src/hooks/useTokenInput.ts @@ -1,215 +1,69 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useState } from "react"; import { BigNumber, utils } from "ethers"; -import { convertTokenToUSD, convertUSDToToken } from "utils"; +import { convertUSDToToken } from "utils"; import { EnrichedToken } from "views/SwapAndBridge/components/ChainTokenSelector/ChainTokenSelectorModal"; -import { formatUnitsWithMaxFractions } from "utils"; export type UnitType = "usd" | "token"; type UseTokenInputProps = { token: EnrichedToken | null; setAmount: (amount: BigNumber | null) => void; - expectedAmount: BigNumber | undefined; - shouldUpdate: boolean; - isUpdateLoading: boolean; - // Optional: Allow unit state to be controlled from parent unit?: UnitType; setUnit?: (unit: UnitType) => void; }; type UseTokenInputReturn = { - amountString: string; - setAmountString: (value: string) => void; unit: UnitType; - convertedAmount: BigNumber | undefined; toggleUnit: () => void; - handleInputChange: (value: string) => void; - handleBalanceClick: (amount: BigNumber, decimals: number) => void; + handleInputChange: (value: number | undefined) => void; + handleBalanceClick: (amount: BigNumber) => void; }; -export function useTokenInput({ +export default function useTokenInput({ token, setAmount, - expectedAmount, - shouldUpdate, - isUpdateLoading, unit: externalUnit, setUnit: externalSetUnit, }: UseTokenInputProps): UseTokenInputReturn { - const [amountString, setAmountString] = useState(""); const [internalUnit, setInternalUnit] = useState("token"); - const [convertedAmount, setConvertedAmount] = useState(); - const [justTyped, setJustTyped] = useState(false); - // Use external unit if provided, otherwise use internal state const unit = externalUnit ?? internalUnit; const setUnit = externalSetUnit ?? setInternalUnit; - // Handle user input changes - propagate to parent - useEffect(() => { - if (!justTyped) { - return; - } - setJustTyped(false); - try { - if (!token) { + const toggleUnit = useCallback(() => { + setUnit(unit === "token" ? "usd" : "token"); + }, [unit, setUnit]); + + const handleInputChange = useCallback( + (value: number | undefined) => { + if (value === undefined || value <= 0) { setAmount(null); return; } - // If the input is empty or effectively zero, set amount to null - if (!amountString || !Number(amountString)) { + if (!token) { setAmount(null); return; } if (unit === "token") { - const parsed = utils.parseUnits(amountString, token.decimals); - // If parsed amount is zero or negative, set to null - if (parsed.lte(0)) { - setAmount(null); - return; - } + const parsed = utils.parseUnits(value.toString(), token.decimals); setAmount(parsed); } else { - const tokenValue = convertUSDToToken(amountString, token); - // If converted value is zero or negative, set to null - if (tokenValue.lte(0)) { - setAmount(null); - return; - } + const tokenValue = convertUSDToToken(value.toString(), token); setAmount(tokenValue); } - } catch (e) { - setAmount(null); - } - }, [amountString, justTyped, token, unit, setAmount]); - - // Reset amount when token changes - useEffect(() => { - if (token) { - setAmountString(""); - setConvertedAmount(undefined); - setAmount(null); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [token?.chainId, token?.symbol]); - - // Handle quote updates - only update the field that should receive the quote - useEffect(() => { - if (shouldUpdate && isUpdateLoading) { - setAmountString(""); - } - - if (shouldUpdate && token) { - // Clear the field when there's no expected amount and not loading - if (!expectedAmount && !isUpdateLoading) { - setAmountString(""); - } else { - if (expectedAmount) { - if (unit === "token") { - // Display as token amount - setAmountString( - formatUnitsWithMaxFractions(expectedAmount, token.decimals) - ); - } else { - // Display as USD amount - convert token to USD - const tokenAmountFormatted = formatUnitsWithMaxFractions( - expectedAmount, - token.decimals - ); - const usdValue = convertTokenToUSD(tokenAmountFormatted, token); - // convertTokenToUSD returns in 18 decimal precision - setAmountString(utils.formatUnits(usdValue, 18)); - } - } - } - } - }, [expectedAmount, isUpdateLoading, shouldUpdate, token, unit]); - - // Set converted value for display - useEffect(() => { - if (!token || !amountString) { - setConvertedAmount(undefined); - return; - } - try { - if (unit === "token") { - // User typed token amount - convert to USD for display - const usdValue = convertTokenToUSD(amountString, token); - setConvertedAmount(usdValue); - } else { - // User typed USD amount - convert to token for display - const tokenValue = convertUSDToToken(amountString, token); - setConvertedAmount(tokenValue); - } - } catch (e) { - // getting an underflow error here - setConvertedAmount(undefined); - } - }, [token, amountString, unit]); - - // Toggle between token and USD units - const toggleUnit = useCallback(() => { - if (unit === "token") { - // Convert token amount to USD string for display - if (amountString && token && convertedAmount) { - try { - // convertedAmount is USD value in 18 decimals - const a = utils.formatUnits(convertedAmount, 18); - setAmountString(a); - } catch (e) { - setAmountString("0"); - } - } - setUnit("usd"); - } else { - // Convert USD amount to token string for display - if (amountString && token && convertedAmount) { - try { - // convertedAmount is token value in token's native decimals - const a = utils.formatUnits(convertedAmount, token.decimals); - setAmountString(a); - } catch (e) { - setAmountString("0"); - } - } - setUnit("token"); - } - }, [unit, amountString, token, convertedAmount, setUnit]); - - // Handle input field changes - const handleInputChange = useCallback((value: string) => { - if (value === "" || /^\d*\.?\d*$/.test(value)) { - setJustTyped(true); - setAmountString(value); - } - }, []); + }, + [setAmount, token, unit] + ); - // Handle balance selector click const handleBalanceClick = useCallback( - (amount: BigNumber, decimals: number) => { + (amount: BigNumber) => { setAmount(amount); - if (unit === "usd" && token) { - // Convert token amount to USD for display - const tokenAmountFormatted = formatUnitsWithMaxFractions( - amount, - decimals - ); - const usdValue = convertTokenToUSD(tokenAmountFormatted, token); - // convertTokenToUSD returns in 18 decimal precision - setAmountString(utils.formatUnits(usdValue, 18)); - } else { - // Display as token amount - setAmountString(formatUnitsWithMaxFractions(amount, decimals)); - } }, - [setAmount, unit, token] + [setAmount] ); return { - amountString, - setAmountString, unit, - convertedAmount, toggleUnit, handleInputChange, handleBalanceClick, diff --git a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx index 94fcfab0d..742f2af7d 100644 --- a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx @@ -1,3 +1,5 @@ +import { useCallback, useMemo, useState } from "react"; +import { NumericFormat } from "react-number-format"; import { UnitType, useTokenInput } from "hooks"; import { ChangeAccountModal } from "../ChangeAccountModal"; import SelectorButton from "../ChainTokenSelector/SelectorButton"; @@ -11,8 +13,14 @@ import { TokenSelectorColumn, } from "./styles"; import { useQuoteRequestContext } from "../../hooks/useQuoteRequest/QuoteRequestContext"; -import { BigNumber } from "ethers"; +import { BigNumber, utils } from "ethers"; import { ToggleUnitButton } from "./ToggleUnit/ToggleUnitButton"; +import { EnrichedToken } from "../ChainTokenSelector/ChainTokenSelectorModal"; +import { + convertTokenToUSD, + convertUSDToToken, + formatUnitsWithMaxFractions, +} from "../../../../utils"; type DestinationTokenDisplayProps = { expectedOutputAmount: BigNumber | undefined; @@ -35,21 +43,13 @@ export const DestinationTokenDisplay = ({ } = useQuoteRequestContext(); const shouldUpdate = quoteRequest.tradeType === "exactInput"; + const [isFocused, setIsFocused] = useState(false); const { destinationToken, originToken } = quoteRequest; - const { - amountString, - convertedAmount, - toggleUnit, - handleInputChange, - handleBalanceClick, - } = useTokenInput({ + const { toggleUnit, handleInputChange, handleBalanceClick } = useTokenInput({ token: destinationToken, setAmount: setDestinationAmount, - expectedAmount: expectedOutputAmount, - shouldUpdate, - isUpdateLoading, unit, setUnit, }); @@ -59,6 +59,67 @@ export const DestinationTokenDisplay = ({ return Boolean(shouldUpdate && isUpdateLoading); })(); + const getAmount = useCallback(() => { + if (shouldUpdate) { + return fromBignumberToString( + unit, + expectedOutputAmount, + destinationToken + ); + } else { + return fromBignumberToString(unit, quoteRequest.amount, destinationToken); + } + }, [ + expectedOutputAmount, + quoteRequest.amount, + destinationToken, + shouldUpdate, + unit, + ]); + + function fromBignumberToString( + unit: "usd" | "token", + amount: BigNumber | null | undefined, + token: EnrichedToken | null + ) { + if (!amount || !token) { + return ""; + } + if (unit === "token") { + // Display as token amount + return formatUnitsWithMaxFractions(amount, token.decimals); + } else { + // Display as USD amount - convert token to USD + const tokenAmountFormatted = formatUnitsWithMaxFractions( + amount, + token.decimals + ); + const usdValue = convertTokenToUSD(tokenAmountFormatted, token); + // convertTokenToUSD returns in 18 decimal precision + return utils.formatUnits(usdValue, 18); + } + } + + const convertedAmount = useMemo(() => { + const amount = shouldUpdate ? expectedOutputAmount : quoteRequest.amount; + if (!amount || !destinationToken) return undefined; + const amountStr = formatUnitsWithMaxFractions( + amount, + destinationToken.decimals + ); + if (unit === "token") { + return convertTokenToUSD(amountStr, destinationToken); + } else { + return convertUSDToToken(amountStr, destinationToken); + } + }, [ + shouldUpdate, + expectedOutputAmount, + quoteRequest.amount, + destinationToken, + unit, + ]); + return ( @@ -69,15 +130,25 @@ export const DestinationTokenDisplay = ({ - handleInputChange(e.target.value)} + value={isFocused && !shouldUpdate ? undefined : getAmount()} + onValueChange={(values, sourceInfo) => { + if (sourceInfo.source === "event" && isFocused) { + handleInputChange(values.floatValue); + } + }} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + thousandSeparator="," + decimalSeparator="." + allowNegative={false} disabled={inputDisabled} error={false} /> @@ -106,7 +177,7 @@ export const DestinationTokenDisplay = ({ error={false} setAmount={(amount) => { if (amount) { - handleBalanceClick(amount, destinationToken.decimals); + handleBalanceClick(amount); } }} /> diff --git a/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx b/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx index dc42ffb53..3270a50e4 100644 --- a/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx @@ -1,4 +1,5 @@ -import { useEffect, useRef } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { NumericFormat } from "react-number-format"; import { UnitType, useTokenInput } from "hooks"; import SelectorButton from "../ChainTokenSelector/SelectorButton"; import { BalanceSelector } from "../BalanceSelector"; @@ -11,10 +12,16 @@ import { TokenSelectorColumn, } from "./styles"; import { useQuoteRequestContext } from "../../hooks/useQuoteRequest/QuoteRequestContext"; -import { BigNumber } from "ethers"; +import { BigNumber, utils } from "ethers"; import { hasInsufficientBalance } from "../../utils/balance"; import { useTokenBalance } from "views/SwapAndBridge/hooks/useTokenBalance"; import { ToggleUnitButton } from "./ToggleUnit/ToggleUnitButton"; +import { EnrichedToken } from "../ChainTokenSelector/ChainTokenSelectorModal"; +import { + convertTokenToUSD, + convertUSDToToken, + formatUnitsWithMaxFractions, +} from "../../../../utils"; type OriginTokenInputProps = { expectedAmount: BigNumber | undefined; @@ -37,19 +44,11 @@ export const OriginTokenInput = ({ const { originToken, destinationToken } = quoteRequest; const shouldUpdate = quoteRequest.tradeType === "minOutput"; + const [isFocused, setIsFocused] = useState(false); - const { - amountString, - convertedAmount, - toggleUnit, - handleInputChange, - handleBalanceClick, - } = useTokenInput({ + const { toggleUnit, handleInputChange, handleBalanceClick } = useTokenInput({ token: originToken, setAmount: setOriginAmount, - expectedAmount, - shouldUpdate, - isUpdateLoading, unit, setUnit, }); @@ -78,6 +77,48 @@ export const OriginTokenInput = ({ } }, [inputDisabled]); + const getAmount = useCallback(() => { + if (shouldUpdate) { + return fromBignumberToString(unit, expectedAmount, originToken); + } else { + return fromBignumberToString(unit, quoteRequest.amount, originToken); + } + }, [expectedAmount, quoteRequest.amount, originToken, shouldUpdate, unit]); + + function fromBignumberToString( + unit: "usd" | "token", + amount: BigNumber | null | undefined, + token: EnrichedToken | null + ) { + if (!amount || !token) { + return ""; + } + if (unit === "token") { + // Display as token amount + return formatUnitsWithMaxFractions(amount, token.decimals); + } else { + // Display as USD amount - convert token to USD + const tokenAmountFormatted = formatUnitsWithMaxFractions( + amount, + token.decimals + ); + const usdValue = convertTokenToUSD(tokenAmountFormatted, token); + // convertTokenToUSD returns in 18 decimal precision + return utils.formatUnits(usdValue, 18); + } + } + + const convertedAmount = useMemo(() => { + const amount = shouldUpdate ? expectedAmount : quoteRequest.amount; + if (!amount || !originToken) return undefined; + const amountStr = formatUnitsWithMaxFractions(amount, originToken.decimals); + if (unit === "token") { + return convertTokenToUSD(amountStr, originToken); + } else { + return convertUSDToToken(amountStr, originToken); + } + }, [shouldUpdate, expectedAmount, quoteRequest.amount, originToken, unit]); + return ( @@ -85,17 +126,27 @@ export const OriginTokenInput = ({ - handleInputChange(e.target.value)} + value={isFocused && !shouldUpdate ? undefined : getAmount()} + onValueChange={(values, sourceInfo) => { + if (sourceInfo.source === "event" && isFocused) { + handleInputChange(values.floatValue); + } + }} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + thousandSeparator="," + decimalSeparator="." + allowNegative={false} disabled={inputDisabled} error={insufficientBalance} /> @@ -124,7 +175,7 @@ export const OriginTokenInput = ({ error={insufficientBalance} setAmount={(amount) => { if (amount) { - handleBalanceClick(amount, originToken.decimals); + handleBalanceClick(amount); } }} /> diff --git a/yarn.lock b/yarn.lock index 157a095f5..4e7919594 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23278,6 +23278,11 @@ react-modal@^3.12.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" +react-number-format@^5.4.4: + version "5.4.4" + resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.4.tgz#d31f0e260609431500c8d3f81bbd3ae1fb7cacad" + integrity sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA== + react-pro-sidebar@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-pro-sidebar/-/react-pro-sidebar-1.1.0.tgz#e8f4ca0d7c4ff9fd2c38f8a0b85664083173b7ac" From 0cba444d6ea60f03bc99078329e629d1dcf66c29 Mon Sep 17 00:00:00 2001 From: jorgen Date: Wed, 17 Dec 2025 15:21:12 +0100 Subject: [PATCH 2/5] refactor --- .../components/TokenInput/AmountInput.tsx | 89 ++++++++++++++++ .../TokenInput/DestinationTokenDisplay.tsx | 100 ++++-------------- .../TokenInput/OriginTokenInput.tsx | 89 ++++------------ 3 files changed, 130 insertions(+), 148 deletions(-) create mode 100644 src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx diff --git a/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx b/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx new file mode 100644 index 000000000..039955f45 --- /dev/null +++ b/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx @@ -0,0 +1,89 @@ +import { useState } from "react"; +import { NumericFormat } from "react-number-format"; +import { BigNumber, utils } from "ethers"; +import { UnitType } from "hooks"; +import { EnrichedToken } from "../ChainTokenSelector/ChainTokenSelectorModal"; +import { TokenAmountInput, TokenAmountInputWrapper } from "./styles"; +import { + convertTokenToUSD, + formatUnitsWithMaxFractions, +} from "../../../../utils"; + +export function formatAmountForDisplay( + amount: BigNumber | null | undefined, + token: EnrichedToken | null, + unit: UnitType +): string { + if (!amount || !token) return ""; + if (unit === "token") { + return formatUnitsWithMaxFractions(amount, token.decimals); + } + const tokenAmountFormatted = formatUnitsWithMaxFractions( + amount, + token.decimals + ); + const usdValue = convertTokenToUSD(tokenAmountFormatted, token); + return utils.formatUnits(usdValue, 18); +} + +type AmountInputProps = { + id: string; + name: string; + testId?: string; + amount: BigNumber | null | undefined; + token: EnrichedToken | null; + unit: UnitType; + shouldUpdate: boolean; + disabled: boolean; + error: boolean; + onInputChange: (value: number | undefined) => void; + inputRef?: React.RefObject; +}; + +export const AmountInput = ({ + id, + name, + testId, + amount, + token, + unit, + shouldUpdate, + disabled, + error, + onInputChange, + inputRef, +}: AmountInputProps) => { + const [isFocused, setIsFocused] = useState(false); + + const displayValue = formatAmountForDisplay(amount, token, unit); + + return ( + + { + if (sourceInfo.source === "event" && isFocused) { + onInputChange(values.floatValue); + } + }} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + thousandSeparator="," + decimalSeparator="." + allowNegative={false} + disabled={disabled} + error={error} + /> + + ); +}; diff --git a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx index 742f2af7d..509ea1dad 100644 --- a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx @@ -1,26 +1,23 @@ -import { useCallback, useMemo, useState } from "react"; -import { NumericFormat } from "react-number-format"; +import { useMemo } from "react"; import { UnitType, useTokenInput } from "hooks"; import { ChangeAccountModal } from "../ChangeAccountModal"; import SelectorButton from "../ChainTokenSelector/SelectorButton"; import { BalanceSelector } from "../BalanceSelector"; import { - TokenAmountInput, TokenAmountInputTitle, - TokenAmountInputWrapper, TokenAmountStack, TokenInputWrapper, TokenSelectorColumn, } from "./styles"; import { useQuoteRequestContext } from "../../hooks/useQuoteRequest/QuoteRequestContext"; -import { BigNumber, utils } from "ethers"; +import { BigNumber } from "ethers"; import { ToggleUnitButton } from "./ToggleUnit/ToggleUnitButton"; -import { EnrichedToken } from "../ChainTokenSelector/ChainTokenSelectorModal"; import { convertTokenToUSD, convertUSDToToken, formatUnitsWithMaxFractions, } from "../../../../utils"; +import { AmountInput } from "./AmountInput"; type DestinationTokenDisplayProps = { expectedOutputAmount: BigNumber | undefined; @@ -43,7 +40,6 @@ export const DestinationTokenDisplay = ({ } = useQuoteRequestContext(); const shouldUpdate = quoteRequest.tradeType === "exactInput"; - const [isFocused, setIsFocused] = useState(false); const { destinationToken, originToken } = quoteRequest; @@ -59,52 +55,14 @@ export const DestinationTokenDisplay = ({ return Boolean(shouldUpdate && isUpdateLoading); })(); - const getAmount = useCallback(() => { - if (shouldUpdate) { - return fromBignumberToString( - unit, - expectedOutputAmount, - destinationToken - ); - } else { - return fromBignumberToString(unit, quoteRequest.amount, destinationToken); - } - }, [ - expectedOutputAmount, - quoteRequest.amount, - destinationToken, - shouldUpdate, - unit, - ]); - - function fromBignumberToString( - unit: "usd" | "token", - amount: BigNumber | null | undefined, - token: EnrichedToken | null - ) { - if (!amount || !token) { - return ""; - } - if (unit === "token") { - // Display as token amount - return formatUnitsWithMaxFractions(amount, token.decimals); - } else { - // Display as USD amount - convert token to USD - const tokenAmountFormatted = formatUnitsWithMaxFractions( - amount, - token.decimals - ); - const usdValue = convertTokenToUSD(tokenAmountFormatted, token); - // convertTokenToUSD returns in 18 decimal precision - return utils.formatUnits(usdValue, 18); - } - } + const displayAmount = shouldUpdate + ? expectedOutputAmount + : quoteRequest.amount; const convertedAmount = useMemo(() => { - const amount = shouldUpdate ? expectedOutputAmount : quoteRequest.amount; - if (!amount || !destinationToken) return undefined; + if (!displayAmount || !destinationToken) return undefined; const amountStr = formatUnitsWithMaxFractions( - amount, + displayAmount, destinationToken.decimals ); if (unit === "token") { @@ -112,13 +70,7 @@ export const DestinationTokenDisplay = ({ } else { return convertUSDToToken(amountStr, destinationToken); } - }, [ - shouldUpdate, - expectedOutputAmount, - quoteRequest.amount, - destinationToken, - unit, - ]); + }, [displayAmount, destinationToken, unit]); return ( @@ -128,31 +80,17 @@ export const DestinationTokenDisplay = ({ - - { - if (sourceInfo.source === "event" && isFocused) { - handleInputChange(values.floatValue); - } - }} - onFocus={() => setIsFocused(true)} - onBlur={() => setIsFocused(false)} - thousandSeparator="," - decimalSeparator="." - allowNegative={false} - disabled={inputDisabled} - error={false} - /> - + onInputChange={handleInputChange} + /> { - if (shouldUpdate) { - return fromBignumberToString(unit, expectedAmount, originToken); - } else { - return fromBignumberToString(unit, quoteRequest.amount, originToken); - } - }, [expectedAmount, quoteRequest.amount, originToken, shouldUpdate, unit]); - - function fromBignumberToString( - unit: "usd" | "token", - amount: BigNumber | null | undefined, - token: EnrichedToken | null - ) { - if (!amount || !token) { - return ""; - } - if (unit === "token") { - // Display as token amount - return formatUnitsWithMaxFractions(amount, token.decimals); - } else { - // Display as USD amount - convert token to USD - const tokenAmountFormatted = formatUnitsWithMaxFractions( - amount, - token.decimals - ); - const usdValue = convertTokenToUSD(tokenAmountFormatted, token); - // convertTokenToUSD returns in 18 decimal precision - return utils.formatUnits(usdValue, 18); - } - } + const displayAmount = shouldUpdate ? expectedAmount : quoteRequest.amount; const convertedAmount = useMemo(() => { - const amount = shouldUpdate ? expectedAmount : quoteRequest.amount; - if (!amount || !originToken) return undefined; - const amountStr = formatUnitsWithMaxFractions(amount, originToken.decimals); + if (!displayAmount || !originToken) return undefined; + const amountStr = formatUnitsWithMaxFractions( + displayAmount, + originToken.decimals + ); if (unit === "token") { return convertTokenToUSD(amountStr, originToken); } else { return convertUSDToToken(amountStr, originToken); } - }, [shouldUpdate, expectedAmount, quoteRequest.amount, originToken, unit]); + }, [displayAmount, originToken, unit]); return ( From - - { - if (sourceInfo.source === "event" && isFocused) { - handleInputChange(values.floatValue); - } - }} - onFocus={() => setIsFocused(true)} - onBlur={() => setIsFocused(false)} - thousandSeparator="," - decimalSeparator="." - allowNegative={false} - disabled={inputDisabled} - error={insufficientBalance} - /> - + onInputChange={handleInputChange} + inputRef={amountInputRef} + /> Date: Thu, 18 Dec 2025 13:29:33 +0100 Subject: [PATCH 3/5] refactor2 --- src/hooks/index.ts | 2 - src/hooks/useTokenInput.ts | 71 ------------------- .../SwapAndBridge/components/InputForm.tsx | 2 +- .../components/TokenInput/AmountInput.tsx | 32 +++++++-- .../TokenInput/DestinationTokenDisplay.tsx | 40 ++--------- .../TokenInput/OriginTokenInput.tsx | 42 ++--------- .../ToggleUnit/ToggleUnitButton.tsx | 35 +++++++-- src/views/SwapAndBridge/types.ts | 1 + 8 files changed, 68 insertions(+), 157 deletions(-) delete mode 100644 src/hooks/useTokenInput.ts create mode 100644 src/views/SwapAndBridge/types.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 06c3e66af..a406ea5ac 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -19,5 +19,3 @@ export * from "./useAmplitude"; export * from "./useRewardSummary"; export * from "./useElapsedSeconds"; export * from "./feature-flags/useFeatureFlag"; -export * from "./useTokenInput"; -export { default as useTokenInput } from "./useTokenInput"; diff --git a/src/hooks/useTokenInput.ts b/src/hooks/useTokenInput.ts deleted file mode 100644 index 3c44b6791..000000000 --- a/src/hooks/useTokenInput.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { useCallback, useState } from "react"; -import { BigNumber, utils } from "ethers"; -import { convertUSDToToken } from "utils"; -import { EnrichedToken } from "views/SwapAndBridge/components/ChainTokenSelector/ChainTokenSelectorModal"; - -export type UnitType = "usd" | "token"; - -type UseTokenInputProps = { - token: EnrichedToken | null; - setAmount: (amount: BigNumber | null) => void; - unit?: UnitType; - setUnit?: (unit: UnitType) => void; -}; - -type UseTokenInputReturn = { - unit: UnitType; - toggleUnit: () => void; - handleInputChange: (value: number | undefined) => void; - handleBalanceClick: (amount: BigNumber) => void; -}; - -export default function useTokenInput({ - token, - setAmount, - unit: externalUnit, - setUnit: externalSetUnit, -}: UseTokenInputProps): UseTokenInputReturn { - const [internalUnit, setInternalUnit] = useState("token"); - - const unit = externalUnit ?? internalUnit; - const setUnit = externalSetUnit ?? setInternalUnit; - - const toggleUnit = useCallback(() => { - setUnit(unit === "token" ? "usd" : "token"); - }, [unit, setUnit]); - - const handleInputChange = useCallback( - (value: number | undefined) => { - if (value === undefined || value <= 0) { - setAmount(null); - return; - } - if (!token) { - setAmount(null); - return; - } - if (unit === "token") { - const parsed = utils.parseUnits(value.toString(), token.decimals); - setAmount(parsed); - } else { - const tokenValue = convertUSDToToken(value.toString(), token); - setAmount(tokenValue); - } - }, - [setAmount, token, unit] - ); - - const handleBalanceClick = useCallback( - (amount: BigNumber) => { - setAmount(amount); - }, - [setAmount] - ); - - return { - unit, - toggleUnit, - handleInputChange, - handleBalanceClick, - }; -} diff --git a/src/views/SwapAndBridge/components/InputForm.tsx b/src/views/SwapAndBridge/components/InputForm.tsx index 683232c5c..30e2c1bdf 100644 --- a/src/views/SwapAndBridge/components/InputForm.tsx +++ b/src/views/SwapAndBridge/components/InputForm.tsx @@ -2,11 +2,11 @@ import { COLORS } from "utils"; import styled from "@emotion/styled"; import { useState } from "react"; import { ReactComponent as ArrowDown } from "assets/icons/arrow-down.svg"; -import { UnitType } from "hooks"; import { useQuoteRequestContext } from "../hooks/useQuoteRequest/QuoteRequestContext"; import { BigNumber } from "ethers"; import { OriginTokenInput } from "./TokenInput/OriginTokenInput"; import { DestinationTokenDisplay } from "./TokenInput/DestinationTokenDisplay"; +import { UnitType } from "../types"; export const InputForm = ({ isQuoteLoading, diff --git a/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx b/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx index 039955f45..c369b5c96 100644 --- a/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/AmountInput.tsx @@ -1,13 +1,14 @@ -import { useState } from "react"; +import { useCallback, useState } from "react"; import { NumericFormat } from "react-number-format"; import { BigNumber, utils } from "ethers"; -import { UnitType } from "hooks"; import { EnrichedToken } from "../ChainTokenSelector/ChainTokenSelectorModal"; import { TokenAmountInput, TokenAmountInputWrapper } from "./styles"; import { convertTokenToUSD, + convertUSDToToken, formatUnitsWithMaxFractions, } from "../../../../utils"; +import { UnitType } from "../../types"; export function formatAmountForDisplay( amount: BigNumber | null | undefined, @@ -36,7 +37,7 @@ type AmountInputProps = { shouldUpdate: boolean; disabled: boolean; error: boolean; - onInputChange: (value: number | undefined) => void; + setAmount: (amount: BigNumber | null) => void; inputRef?: React.RefObject; }; @@ -50,13 +51,34 @@ export const AmountInput = ({ shouldUpdate, disabled, error, - onInputChange, + setAmount, inputRef, }: AmountInputProps) => { const [isFocused, setIsFocused] = useState(false); const displayValue = formatAmountForDisplay(amount, token, unit); + const handleValueChange = useCallback( + (value: number | undefined) => { + if (value === undefined || value <= 0) { + setAmount(null); + return; + } + if (!token) { + setAmount(null); + return; + } + if (unit === "token") { + const parsed = utils.parseUnits(value.toString(), token.decimals); + setAmount(parsed); + } else { + const tokenValue = convertUSDToToken(value.toString(), token); + setAmount(tokenValue); + } + }, + [setAmount, token, unit] + ); + return ( { if (sourceInfo.source === "event" && isFocused) { - onInputChange(values.floatValue); + handleValueChange(values.floatValue); } }} onFocus={() => setIsFocused(true)} diff --git a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx index 509ea1dad..75645269c 100644 --- a/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/DestinationTokenDisplay.tsx @@ -1,5 +1,3 @@ -import { useMemo } from "react"; -import { UnitType, useTokenInput } from "hooks"; import { ChangeAccountModal } from "../ChangeAccountModal"; import SelectorButton from "../ChainTokenSelector/SelectorButton"; import { BalanceSelector } from "../BalanceSelector"; @@ -12,12 +10,8 @@ import { import { useQuoteRequestContext } from "../../hooks/useQuoteRequest/QuoteRequestContext"; import { BigNumber } from "ethers"; import { ToggleUnitButton } from "./ToggleUnit/ToggleUnitButton"; -import { - convertTokenToUSD, - convertUSDToToken, - formatUnitsWithMaxFractions, -} from "../../../../utils"; import { AmountInput } from "./AmountInput"; +import { UnitType } from "../../types"; type DestinationTokenDisplayProps = { expectedOutputAmount: BigNumber | undefined; @@ -43,13 +37,6 @@ export const DestinationTokenDisplay = ({ const { destinationToken, originToken } = quoteRequest; - const { toggleUnit, handleInputChange, handleBalanceClick } = useTokenInput({ - token: destinationToken, - setAmount: setDestinationAmount, - unit, - setUnit, - }); - const inputDisabled = (() => { if (!quoteRequest.destinationToken) return true; return Boolean(shouldUpdate && isUpdateLoading); @@ -59,19 +46,6 @@ export const DestinationTokenDisplay = ({ ? expectedOutputAmount : quoteRequest.amount; - const convertedAmount = useMemo(() => { - if (!displayAmount || !destinationToken) return undefined; - const amountStr = formatUnitsWithMaxFractions( - displayAmount, - destinationToken.decimals - ); - if (unit === "token") { - return convertTokenToUSD(amountStr, destinationToken); - } else { - return convertUSDToToken(amountStr, destinationToken); - } - }, [displayAmount, destinationToken, unit]); - return ( @@ -89,13 +63,13 @@ export const DestinationTokenDisplay = ({ shouldUpdate={shouldUpdate} disabled={inputDisabled} error={false} - onInputChange={handleInputChange} + setAmount={setDestinationAmount} /> @@ -113,11 +87,7 @@ export const DestinationTokenDisplay = ({ token={destinationToken} disableHover={true} error={false} - setAmount={(amount) => { - if (amount) { - handleBalanceClick(amount); - } - }} + setAmount={setDestinationAmount} /> )} diff --git a/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx b/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx index e20640776..cd1ba353c 100644 --- a/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/OriginTokenInput.tsx @@ -1,5 +1,4 @@ -import { useEffect, useMemo, useRef } from "react"; -import { UnitType, useTokenInput } from "hooks"; +import { useEffect, useRef } from "react"; import SelectorButton from "../ChainTokenSelector/SelectorButton"; import { BalanceSelector } from "../BalanceSelector"; import { @@ -13,12 +12,8 @@ import { BigNumber } from "ethers"; import { hasInsufficientBalance } from "../../utils/balance"; import { useTokenBalance } from "views/SwapAndBridge/hooks/useTokenBalance"; import { ToggleUnitButton } from "./ToggleUnit/ToggleUnitButton"; -import { - convertTokenToUSD, - convertUSDToToken, - formatUnitsWithMaxFractions, -} from "../../../../utils"; import { AmountInput } from "./AmountInput"; +import { UnitType } from "../../types"; type OriginTokenInputProps = { expectedAmount: BigNumber | undefined; @@ -42,13 +37,6 @@ export const OriginTokenInput = ({ const shouldUpdate = quoteRequest.tradeType === "minOutput"; - const { toggleUnit, handleInputChange, handleBalanceClick } = useTokenInput({ - token: originToken, - setAmount: setOriginAmount, - unit, - setUnit, - }); - const inputDisabled = (() => { if (!quoteRequest.destinationToken) return true; return Boolean(shouldUpdate && isUpdateLoading); @@ -75,24 +63,10 @@ export const OriginTokenInput = ({ const displayAmount = shouldUpdate ? expectedAmount : quoteRequest.amount; - const convertedAmount = useMemo(() => { - if (!displayAmount || !originToken) return undefined; - const amountStr = formatUnitsWithMaxFractions( - displayAmount, - originToken.decimals - ); - if (unit === "token") { - return convertTokenToUSD(amountStr, originToken); - } else { - return convertUSDToToken(amountStr, originToken); - } - }, [displayAmount, originToken, unit]); - return ( From - @@ -128,11 +102,7 @@ export const OriginTokenInput = ({ token={originToken} disableHover={false} error={insufficientBalance} - setAmount={(amount) => { - if (amount) { - handleBalanceClick(amount); - } - }} + setAmount={setOriginAmount} /> )} diff --git a/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx b/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx index c93b3c523..11d96dbc7 100644 --- a/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx +++ b/src/views/SwapAndBridge/components/TokenInput/ToggleUnit/ToggleUnitButton.tsx @@ -1,25 +1,46 @@ -import { UnitType } from "../../../../../hooks"; +import { useCallback, useMemo } from "react"; import { BigNumber } from "ethers"; import { EnrichedToken } from "../../ChainTokenSelector/ChainTokenSelectorModal"; -import { formatUSD, withOpacity } from "../../../../../utils"; +import { + convertTokenToUSD, + convertUSDToToken, + formatUnitsWithMaxFractions, + formatUSD, + withOpacity, +} from "../../../../../utils"; import { formatUnits } from "ethers/lib/utils"; import styled from "@emotion/styled"; import { useTrackToggleUnit } from "./useTrackToggleUnit"; import { ReactComponent as ArrowsCross } from "assets/icons/arrows-cross.svg"; +import { UnitType } from "../../../types"; export function ToggleUnitButton({ - onClick, unit, - convertedAmount, + setUnit, + amount, token, }: { - onClick: () => void; unit: UnitType; - convertedAmount: BigNumber | undefined; + setUnit: (unit: UnitType) => void; + amount: BigNumber | null | undefined; token: EnrichedToken | null; }) { const trackToggleUnit = useTrackToggleUnit(); + const convertedAmount = useMemo(() => { + if (!amount || !token) return undefined; + const amountStr = formatUnitsWithMaxFractions(amount, token.decimals); + if (unit === "token") { + return convertTokenToUSD(amountStr, token); + } else { + return convertUSDToToken(amountStr, token); + } + }, [amount, token, unit]); + + const toggleUnit = useCallback(() => { + setUnit(unit === "token" ? "usd" : "token"); + }, [unit, setUnit]); + const formattedConvertedAmount = (() => { if (unit === "token") { if (!convertedAmount) return "$0.00"; @@ -34,7 +55,7 @@ export function ToggleUnitButton({ { trackToggleUnit(); - onClick(); + toggleUnit(); }} > {" "} diff --git a/src/views/SwapAndBridge/types.ts b/src/views/SwapAndBridge/types.ts new file mode 100644 index 000000000..f17595248 --- /dev/null +++ b/src/views/SwapAndBridge/types.ts @@ -0,0 +1 @@ +export type UnitType = "usd" | "token"; From afedc8962d3ac2af63253f884a73fa5833696957 Mon Sep 17 00:00:00 2001 From: jorgen Date: Thu, 18 Dec 2025 13:43:37 +0100 Subject: [PATCH 4/5] add story --- .../ConfirmationButton.stories.tsx | 2 +- .../components/InputForm.stories.tsx | 182 ++++++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/views/SwapAndBridge/components/InputForm.stories.tsx diff --git a/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx b/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx index 194f30b2a..150cca1ea 100644 --- a/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx +++ b/src/views/SwapAndBridge/components/Confirmation/ConfirmationButton.stories.tsx @@ -207,7 +207,7 @@ const createQuoteWithProvider = ( const meta: Meta = { component: ConfirmationButton, - title: "Stories/ConfirmationButton", + title: "Bridge/ConfirmationButton", argTypes: { isQuoteLoading: { control: { type: "boolean" }, diff --git a/src/views/SwapAndBridge/components/InputForm.stories.tsx b/src/views/SwapAndBridge/components/InputForm.stories.tsx new file mode 100644 index 000000000..17d55c889 --- /dev/null +++ b/src/views/SwapAndBridge/components/InputForm.stories.tsx @@ -0,0 +1,182 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { userEvent, within } from "@storybook/testing-library"; +import { BigNumber } from "ethers"; +import { InputForm } from "./InputForm"; +import { EnrichedToken } from "./ChainTokenSelector/ChainTokenSelectorModal"; +import { QuoteRequestProvider } from "../hooks/useQuoteRequest/QuoteRequestContext"; +import { QuoteRequest } from "../hooks/useQuoteRequest/quoteRequestAction"; + +const mockInputToken: EnrichedToken = { + chainId: 1, + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + symbol: "USDC", + name: "USD Coin", + decimals: 6, + priceUSD: "1.00", + coinKey: "usdc", + logoURI: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + routeSource: "bridge", + balance: BigNumber.from("1000000000"), + balanceUsd: 1000, +}; + +const mockOutputToken: EnrichedToken = { + chainId: 42161, + address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + symbol: "USDC", + name: "USD Coin", + decimals: 6, + priceUSD: "1.00", + coinKey: "usdc", + logoURI: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + routeSource: "bridge", + balance: BigNumber.from("500000000"), + balanceUsd: 500, +}; + +const mockQuoteRequest: QuoteRequest = { + tradeType: "exactInput", + originToken: mockInputToken, + destinationToken: mockOutputToken, + customDestinationAccount: null, + amount: BigNumber.from("100000000"), +}; + +const mockQuoteRequestNoTokens: QuoteRequest = { + tradeType: "exactInput", + originToken: null, + destinationToken: null, + customDestinationAccount: null, + amount: null, +}; + +const mockQuoteRequestMinOutput: QuoteRequest = { + tradeType: "minOutput", + originToken: mockInputToken, + destinationToken: mockOutputToken, + customDestinationAccount: null, + amount: BigNumber.from("99500000"), +}; + +const meta: Meta = { + component: InputForm, + title: "Bridge/InputForm", + argTypes: { + isQuoteLoading: { + control: { type: "boolean" }, + }, + }, + decorators: [ + (Story, context) => ( + +
+ +
+
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: BigNumber.from("99500000"), + }, +}; + +export const Loading: Story = { + args: { + isQuoteLoading: true, + expectedInputAmount: undefined, + expectedOutputAmount: undefined, + }, +}; + +export const WithQuoteResult: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: BigNumber.from("99500000"), + }, +}; + +export const MinOutputMode: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: BigNumber.from("100500000"), + expectedOutputAmount: undefined, + }, + parameters: { + quoteRequest: mockQuoteRequestMinOutput, + }, +}; + +export const NoTokensSelected: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: undefined, + }, + parameters: { + quoteRequest: mockQuoteRequestNoTokens, + }, +}; + +export const LargeAmount: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: BigNumber.from("1234567890123"), + }, + parameters: { + quoteRequest: { + ...mockQuoteRequest, + amount: BigNumber.from("1234567890123"), + }, + }, +}; + +const mockQuoteRequestEmpty: QuoteRequest = { + tradeType: "exactInput", + originToken: mockInputToken, + destinationToken: mockOutputToken, + customDestinationAccount: null, + amount: null, +}; + +export const ThousandSeparatorTest: Story = { + args: { + isQuoteLoading: false, + expectedInputAmount: undefined, + expectedOutputAmount: undefined, + }, + parameters: { + quoteRequest: mockQuoteRequestEmpty, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const originInput = canvas.getByTestId("bridge-amount-input"); + + await userEvent.clear(originInput); + await userEvent.type(originInput, "1234567.89", { delay: 50 }); + }, +}; From ea97d2467e6ff1503ba4d583490aa3ecdf001b7c Mon Sep 17 00:00:00 2001 From: jorgen Date: Thu, 18 Dec 2025 13:54:50 +0100 Subject: [PATCH 5/5] add test --- .../quoteRequestReducer.test.ts | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts b/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts index 901d15760..21bc177c1 100644 --- a/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts +++ b/src/views/SwapAndBridge/hooks/useQuoteRequest/quoteRequestReducer.test.ts @@ -3,8 +3,13 @@ import { quoteRequestReducer } from "./quoteRequestReducer"; import { QuoteRequest } from "./quoteRequestAction"; import { initialQuote } from "./initialQuote"; -const mockToken = (symbol: string) => - ({ symbol, chainId: 1, address: "0x" }) as QuoteRequest["originToken"]; +const mockToken = (symbol: string, decimals = 18) => + ({ + symbol, + chainId: 1, + address: "0x", + decimals, + }) as QuoteRequest["originToken"]; const mockAccount = (address: string) => ({ accountType: "evm", address }) as QuoteRequest["customDestinationAccount"]; @@ -104,6 +109,26 @@ describe("quoteRequestReducer", () => { }); expect(result.tradeType).toBe("exactInput"); }); + + it("preserves amount when swapping", () => { + const usdc1 = mockToken("USDC", 6); + const usdc2 = mockToken("USDC.e", 6); + + const state: QuoteRequest = { + ...initialQuote, + originToken: usdc1, + destinationToken: usdc2, + tradeType: "exactInput", + amount: BigNumber.from("10000000"), + }; + + const result = quoteRequestReducer(state, { + type: "QUICK_SWAP", + payload: undefined, + }); + + expect(result.amount?.toString()).toBe("10000000"); + }); }); it("returns previous state for unknown action", () => {