Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GSW-1984] swap route debounce #597

Merged
merged 5 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
},
};
};
Loading