Skip to content

Commit

Permalink
[GSW-1984] swap route debounce (#597)
Browse files Browse the repository at this point in the history
* feat: [GSW-1984] Swap Value Debounce

* fix: SwapSummaryInfo UI (u-units)

* refactor: [SonarQube] Ternary operators should not be nested

Extract this nested ternary operation into an independent statement.
Ternary operators should not be nested typescript:S3358
Software qualities impacted:
Maintainability

* fix: MissingLogo UI (font-size)

* fix: [GSW-1984] Improve State management (User now typing)
  • Loading branch information
tfrg authored Dec 31, 2024
1 parent c2ca3fd commit ea9ea82
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ export const LogoWrapper = styled.div<Props>`
font-weight: 600;
font-size: ${({ width, placeholderFontSize }) => {
if (placeholderFontSize) return `${placeholderFontSize}px`;
return `${getFontSize(width)}`;
return `${getFontSize(width)}px`;
}};
${media.mobile} {
font-size: ${({ mobileWidth, placeholderFontSize }) => {
if (placeholderFontSize) return `${placeholderFontSize}px`;
return `${getFontSize(mobileWidth)}`;
return `${getFontSize(mobileWidth)}px`;
}};
height: ${({ mobileWidth }) => {
return `${mobileWidth}px`;
Expand Down
11 changes: 7 additions & 4 deletions packages/web/src/hooks/swap/data/use-swap-handler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export const useSwapHandler = () => {
unwrap,
updateSwapAmount,
resetSwapAmount,
isTyping,
} = useSwap({
tokenA,
tokenB,
Expand Down Expand Up @@ -300,7 +301,7 @@ export const useSwapHandler = () => {
);
prevPriceImpact.current = BigNumber(priceImpactNum.toFixed(2));
return BigNumber(priceImpactNum.toFixed(2));
}, [estimatedRoutes, swapFee, tokenA, tokenAAmount, tokenB, tokenBAmount, tokenPrices]);
}, [estimatedRoutes, swapFee, tokenA?.path, tokenAAmount, tokenB?.path, tokenBAmount, tokenPrices]);

const priceImpactStatus: PriceImpactStatus = useMemo(() => {
if (!priceImpact) return "NONE";
Expand Down Expand Up @@ -592,7 +593,9 @@ export const useSwapHandler = () => {
const changeTokenAAmount = useCallback(
(changed: string, none?: boolean) => {
const value = handleAmount(changed, tokenA);
updateSwapAmount(value);
if (tokenA && tokenB) {
updateSwapAmount(value);
}

if (isSameToken) {
setTokenAAmount(value);
Expand Down Expand Up @@ -1033,7 +1036,7 @@ export const useSwapHandler = () => {
}

if (swapState !== "SUCCESS" && estimatedAmount === null) {
if (swapState === "NO_LIQUIDITY") {
if (swapState === "NO_LIQUIDITY" || swapState === "NONE") {
if (type === "EXACT_IN") {
setTokenBAmount("");
} else {
Expand Down Expand Up @@ -1106,7 +1109,7 @@ export const useSwapHandler = () => {
executeSwap,
isSwitchNetwork,
switchNetwork,
isLoading: swapState === "LOADING",
isLoading: swapState === "LOADING" || isTyping,
setSwapValue,
tokenA,
tokenB,
Expand Down
106 changes: 79 additions & 27 deletions packages/web/src/hooks/swap/data/use-swap.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import BigNumber from "bignumber.js";

import { SwapDirectionType } from "@common/values";
import { useGnoswapContext } from "@hooks/common/use-gnoswap-context";
import { useWallet } from "@hooks/wallet/data/use-wallet";
import { TokenModel, isNativeToken } from "@models/token/token-model";
import { EstimatedRoute } from "@models/swap/swap-route-info";
import { makeDisplayTokenAmount } from "@utils/token-utils";
import BigNumber from "bignumber.js";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useGetRoutes } from "@query/router";
import useDebounce from "@hooks/common/use-debounce";

interface UseSwapProps {
tokenA: TokenModel | null;
Expand All @@ -19,18 +21,31 @@ interface UseSwapProps {
export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: UseSwapProps) => {
const { account } = useWallet();
const { swapRouterRepository } = useGnoswapContext();

const SWAP_AMOUNT_DEBOUNCE_TIME_MS = 500;
const [swapAmount, setSwapAmount] = useState<number | null>(null);
const debouncedAmount = useDebounce(swapAmount, SWAP_AMOUNT_DEBOUNCE_TIME_MS);
const [estimatedLiquidityMax, setEstimatedLiquidityMax] = useState<number | null>(null);
const [isTyping, setIsTyping] = useState(false);
const typingTimeoutRef = useRef<NodeJS.Timeout>();

const debouncedSwapAmount = useMemo(() => {
if (!swapAmount || swapAmount === 0) {
return swapAmount;
}
return debouncedAmount;
}, [swapAmount, debouncedAmount]);

const shouldFetchData = useCallback(
(amount: number | null) => {
if (!tokenA || !tokenB) return false;
if (!amount) return false;
if (!estimatedLiquidityMax) return true;
return amount < estimatedLiquidityMax;
},
[estimatedLiquidityMax],
[estimatedLiquidityMax, tokenA, tokenB],
);
const shouldFetch = shouldFetchData(swapAmount);
const shouldFetch = shouldFetchData(debouncedSwapAmount);

const selectedTokenPair = tokenA !== null && tokenB !== null;

Expand All @@ -49,12 +64,20 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
return false;
}, [tokenA, tokenB]);

const hasValidSwapAmount = Boolean(swapAmount && swapAmount > 0);
const hasValidSwapAmount = Boolean(debouncedSwapAmount && debouncedSwapAmount > 0);
const hasValidTokenPaths = Boolean(tokenA?.path) && Boolean(tokenB?.path);
const isDifferentTokens = !isSameToken;

const isEnabledQuery = shouldFetch && hasValidSwapAmount && hasValidTokenPaths && isDifferentTokens;

const getTokenAmount = useMemo(() => {
if (direction === "EXACT_IN") {
return debouncedSwapAmount;
}

return debouncedSwapAmount ? debouncedSwapAmount * exactOutPadding : debouncedSwapAmount;
}, [debouncedSwapAmount, direction, exactOutPadding]);

const {
data: estimatedSwapResult,
isLoading: isEstimatedSwapLoading,
Expand All @@ -64,15 +87,15 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
inputToken: tokenA,
outputToken: tokenB,
exactType: direction,
tokenAmount: direction === "EXACT_IN" ? swapAmount : swapAmount ? swapAmount * exactOutPadding : swapAmount,
tokenAmount: getTokenAmount,
},
{
enabled: isEnabledQuery,
},
);

const swapState: "NONE" | "LOADING" | "NO_LIQUIDITY" | "SUCCESS" = useMemo(() => {
if (!selectedTokenPair || !swapAmount) {
if (!selectedTokenPair || !debouncedSwapAmount) {
return "NONE";
}

Expand All @@ -89,10 +112,10 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
}

return "SUCCESS";
}, [swapAmount, error, estimatedSwapResult?.amount, isEstimatedSwapLoading, isSameToken, selectedTokenPair]);
}, [debouncedSwapAmount, error, estimatedSwapResult?.amount, isEstimatedSwapLoading, isSameToken, selectedTokenPair]);

const estimatedRoutes: EstimatedRoute[] | null = useMemo(() => {
if (swapState === "LOADING" || !swapAmount) {
if (swapState === "LOADING" || !debouncedSwapAmount || isTyping) {
return null;
}

Expand All @@ -101,14 +124,14 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
}

return estimatedSwapResult.estimatedRoutes;
}, [swapState, estimatedSwapResult, swapAmount]);
}, [swapState, estimatedSwapResult, debouncedSwapAmount, isTyping]);

const estimatedAmount: string | null = useMemo(() => {
if (!tokenA || !tokenB) {
return null;
}

if (!swapAmount || error) {
if (!debouncedSwapAmount || error || isTyping) {
return null;
}

Expand All @@ -120,7 +143,7 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
return direction === "EXACT_IN"
? makeDisplayTokenAmount(tokenB, amount)?.toString() || null
: makeDisplayTokenAmount(tokenA, amount)?.toString() || null;
}, [swapAmount, error, swapState, estimatedSwapResult]);
}, [debouncedSwapAmount, error, swapState, estimatedSwapResult, isTyping]);

const tokenAmountLimit = useMemo(() => {
if (estimatedAmount && !Number.isNaN(slippage)) {
Expand All @@ -142,17 +165,41 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
return 0;
}, [direction, estimatedAmount, slippage, tokenA]);

const updateSwapAmount = useCallback((amount: string) => {
if (!amount) return setSwapAmount(null);
const updateSwapAmount = useCallback(
(amount: string) => {
if (!amount) {
setSwapAmount(null);
setIsTyping(false);
return;
}

let newAmount = 0;
if (BigNumber(amount).isZero()) {
newAmount = 0;
}
newAmount = BigNumber(amount).toNumber();
let newAmount = 0;
if (BigNumber(amount).isZero()) {
newAmount = 0;
}
newAmount = BigNumber(amount).toNumber();

setSwapAmount(newAmount);
}, []);
setSwapAmount(newAmount);
if (tokenA && tokenB) {
setIsTyping(true);
}

if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
}

typingTimeoutRef.current = setTimeout(() => {
setIsTyping(false);
}, SWAP_AMOUNT_DEBOUNCE_TIME_MS + 100);
},
[tokenA, tokenB],
);

useEffect(() => {
if (debouncedSwapAmount !== null) {
setIsTyping(false);
}
}, [debouncedSwapAmount]);

const wrap = useCallback(
async (tokenAmount: string) => {
Expand Down Expand Up @@ -208,18 +255,18 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
);

useEffect(() => {
if (estimatedRoutes === null) return;
if (estimatedRoutes === null || !tokenA || !tokenB) return;

if (estimatedRoutes.length === 0) {
if (!estimatedLiquidityMax) {
setEstimatedLiquidityMax(swapAmount || null);
} else if (swapAmount && swapAmount < estimatedLiquidityMax) {
setEstimatedLiquidityMax(swapAmount);
setEstimatedLiquidityMax(debouncedSwapAmount || null);
} else if (debouncedSwapAmount && debouncedSwapAmount < estimatedLiquidityMax) {
setEstimatedLiquidityMax(debouncedSwapAmount);
}
} else {
setEstimatedLiquidityMax(null);
}
}, [estimatedRoutes, swapAmount, estimatedLiquidityMax]);
}, [estimatedRoutes, debouncedSwapAmount, estimatedLiquidityMax]);

/**
* Reset estimatedLiquidityMax to null after specified delay
Expand Down Expand Up @@ -247,6 +294,11 @@ export const useSwap = ({ tokenA, tokenB, direction, slippage, swapFee = 15 }: U
unwrap,
updateSwapAmount,
isEstimatedSwapLoading,
resetSwapAmount: () => setSwapAmount(0),
isTyping,
resetSwapAmount: () => {
setSwapAmount(0);
setIsTyping(false);
setEstimatedLiquidityMax(null);
},
};
};

0 comments on commit ea9ea82

Please sign in to comment.