diff --git a/packages/swap-router/.ultra.cache.json b/packages/swap-router/.ultra.cache.json index 61b1dcbe3..75a2852d6 100644 --- a/packages/swap-router/.ultra.cache.json +++ b/packages/swap-router/.ultra.cache.json @@ -1 +1 @@ -{"files":{"build":"1701307968994.8171","node_modules":"1701307913545.335",".eslintignore":"7de3bd702df2dee92c033c49abbedd0b0f7452e6",".eslintrc.js":"ca4815a5cf5cffa1d3fb4a014c8c602f852b002a",".gitignore":"c87c9b392c0200d9c9dafc444386ad3e15a85c64",".prettierignore":"47bb4656eb55860a075be7799cba7fa955b68141",".prettierrc":"fe5f744c7a08b128c935d1e1aed3e8a577e74507","jest.config.json":"2e496ee6bd64eb237161dcd69a6957eff2df584d","package.json":"4dda70ca76fb6022f6cb20852ecd8d2d95d638d2","src/common/array.util.ts":"9482ab121d34cc7e08a0cd33b49173b0b85823d8","src/common/bigint.util.ts":"343f4c85ca1f6c840ade68c259ed82bf5b159fb0","src/common/index.ts":"861a3167cddfc93e9eb1b6a4ae8229bb9dc2f8fd","src/common/mapper.ts":"f7d7b491dff443911a978b34e4cb378bee46c2b7","src/common/queue.ts":"ad096fefbe1c5a7339b536b0b8d39dba661b520e","src/common/test.util.ts":"3e500df6ec27ba98606acbfcaec9243e22862e43","src/constants/index.ts":"9605b149deb525c25f6e93420bbc433ff1c75cd0","src/constants/math.constant.ts":"b617851527cabcc9c3bce2c8e39208001d379ad6","src/constants/swap.constant.ts":"53e280147c68acfba490bc1f9a0b7bb1580e7280","src/index.ts":"691efb21a28bd3f0e6437edcb16192789dbbb7f6","src/swap-router/index.ts":"effaf28a826bdf1441437643e8343e7fbd8026b1","src/swap-router/swap-router-default.spec.ts":"f980bc94753df130c950eb80d8709b62ec3276fa","src/swap-router/swap-router-multi-higher-range-position-pool.spec.ts":"683ce3a82d73e915018e772723700905c3ff7c92","src/swap-router/swap-router-multi-lower-range-position-pool.spec.ts":"93bfd18d0a297f3c66743cddcf2fe10c3764e250","src/swap-router/swap-router-multi-pair-pools.spec.ts":"5fbd55be39012d46adbdc91a1111dca2aafd314d","src/swap-router/swap-router-multi-route.spec.ts":"771df497876b6bc027dc7b65d03e53209538bf2e","src/swap-router/swap-router-single-pair-pools.spec.ts":"3954e9057ef9534a3724dc960c291281d1a4ef52","src/swap-router/swap-router.ts":"606de3d518665d58f5cbafb55f45d92c30dab77d","src/swap-router/swap-router.types.ts":"2a48987217df238256a0721d227952142a37ce08","src/swap-router/utility/index.ts":"4973874904dd9d77d037d72b1ec8aed397716a38","src/swap-router/utility/route.util.ts":"d492418c9184e42d3ef29d7760eb18fa013f54c6","src/swap-simulator/index.ts":"ef493c5977bdf1123d1197b804f2344ee463870c","src/swap-simulator/swap-simulator-default.spec.ts":"867788b12ccc70abe676010aeed5d6b2b58e8036","src/swap-simulator/swap-simulator.ts":"48cc4c90a2622d806659fa2f8c4f48d600a2b2db","src/swap-simulator/swap-simulator.types.ts":"2b03d7e389ab8875852330c8fddf5a9bfe96f1c6","src/swap-simulator/utility/cache.util.ts":"c5af86d350610a80a6690846657a35635bbc752a","src/swap-simulator/utility/index.ts":"7272f5d23dfa7af2895fca9bb0b17985234ab6e1","src/swap-simulator/utility/math.util.ts":"96fbe38cbabe344f6f58220f46c20103e6df4225","src/swap-simulator/utility/swap-util.spec.ts":"b7fe082f3fef7e5a912959925924f56f8c36e020","src/swap-simulator/utility/swap.util.ts":"f49236f0d22f72b9283244e870d34401d42fe009","src/swap-simulator/utility/tick.util.ts":"4d9bf8ed782f07587c71009ad13cd249b5e3a840","tsconfig.json":"55467bd5c5eb1b501abe50b952d5f2d9fa0a0d69"},"deps":{}} \ No newline at end of file +{"files":{".vscode":"1700971955685.2515","build":"1701256570289.2512","node_modules":"1700891704503.8325",".eslintignore":"7de3bd702df2dee92c033c49abbedd0b0f7452e6",".eslintrc.js":"ca4815a5cf5cffa1d3fb4a014c8c602f852b002a",".gitignore":"c87c9b392c0200d9c9dafc444386ad3e15a85c64",".prettierignore":"47bb4656eb55860a075be7799cba7fa955b68141",".prettierrc":"fe5f744c7a08b128c935d1e1aed3e8a577e74507","jest.config.json":"2e496ee6bd64eb237161dcd69a6957eff2df584d","package.json":"4dda70ca76fb6022f6cb20852ecd8d2d95d638d2","src/common/array.util.ts":"9482ab121d34cc7e08a0cd33b49173b0b85823d8","src/common/bigint.util.ts":"343f4c85ca1f6c840ade68c259ed82bf5b159fb0","src/common/index.ts":"861a3167cddfc93e9eb1b6a4ae8229bb9dc2f8fd","src/common/mapper.ts":"f7d7b491dff443911a978b34e4cb378bee46c2b7","src/common/queue.ts":"ad096fefbe1c5a7339b536b0b8d39dba661b520e","src/common/test.util.ts":"3e500df6ec27ba98606acbfcaec9243e22862e43","src/constants/index.ts":"9605b149deb525c25f6e93420bbc433ff1c75cd0","src/constants/math.constant.ts":"b617851527cabcc9c3bce2c8e39208001d379ad6","src/constants/swap.constant.ts":"53e280147c68acfba490bc1f9a0b7bb1580e7280","src/index.ts":"691efb21a28bd3f0e6437edcb16192789dbbb7f6","src/swap-router/index.ts":"effaf28a826bdf1441437643e8343e7fbd8026b1","src/swap-router/swap-router-default.spec.ts":"f980bc94753df130c950eb80d8709b62ec3276fa","src/swap-router/swap-router-multi-higher-range-position-pool.spec.ts":"683ce3a82d73e915018e772723700905c3ff7c92","src/swap-router/swap-router-multi-lower-range-position-pool.spec.ts":"93bfd18d0a297f3c66743cddcf2fe10c3764e250","src/swap-router/swap-router-multi-pair-pools.spec.ts":"5fbd55be39012d46adbdc91a1111dca2aafd314d","src/swap-router/swap-router-multi-route.spec.ts":"771df497876b6bc027dc7b65d03e53209538bf2e","src/swap-router/swap-router-single-pair-pools.spec.ts":"3954e9057ef9534a3724dc960c291281d1a4ef52","src/swap-router/swap-router.ts":"606de3d518665d58f5cbafb55f45d92c30dab77d","src/swap-router/swap-router.types.ts":"2a48987217df238256a0721d227952142a37ce08","src/swap-router/utility/index.ts":"4973874904dd9d77d037d72b1ec8aed397716a38","src/swap-router/utility/route.util.ts":"d492418c9184e42d3ef29d7760eb18fa013f54c6","src/swap-simulator/index.ts":"ef493c5977bdf1123d1197b804f2344ee463870c","src/swap-simulator/swap-simulator-default.spec.ts":"867788b12ccc70abe676010aeed5d6b2b58e8036","src/swap-simulator/swap-simulator.ts":"48cc4c90a2622d806659fa2f8c4f48d600a2b2db","src/swap-simulator/swap-simulator.types.ts":"2b03d7e389ab8875852330c8fddf5a9bfe96f1c6","src/swap-simulator/utility/cache.util.ts":"c5af86d350610a80a6690846657a35635bbc752a","src/swap-simulator/utility/index.ts":"7272f5d23dfa7af2895fca9bb0b17985234ab6e1","src/swap-simulator/utility/math.util.ts":"96fbe38cbabe344f6f58220f46c20103e6df4225","src/swap-simulator/utility/swap-util.spec.ts":"b7fe082f3fef7e5a912959925924f56f8c36e020","src/swap-simulator/utility/swap.util.ts":"f49236f0d22f72b9283244e870d34401d42fe009","src/swap-simulator/utility/tick.util.ts":"4d9bf8ed782f07587c71009ad13cd249b5e3a840","tsconfig.json":"55467bd5c5eb1b501abe50b952d5f2d9fa0a0d69"},"deps":{}} \ No newline at end of file diff --git a/packages/web/jest.config.js b/packages/web/jest.config.js index b90fac151..7c949512c 100644 --- a/packages/web/jest.config.js +++ b/packages/web/jest.config.js @@ -17,6 +17,8 @@ const customJestConfig = { }), setupFilesAfterEnv: ["/jest.setup.js"], testEnvironment: "jest-environment-jsdom", + testMatch: ["/**/*.spec.(js|jsx|ts|tsx)"], + transformIgnorePatterns: ["/node_modules/"], }; // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/packages/web/src/components/swap/swap-button-tooltip/SwapButtonTooltip.tsx b/packages/web/src/components/swap/swap-button-tooltip/SwapButtonTooltip.tsx index 03d9736cc..a38ce4ef6 100644 --- a/packages/web/src/components/swap/swap-button-tooltip/SwapButtonTooltip.tsx +++ b/packages/web/src/components/swap/swap-button-tooltip/SwapButtonTooltip.tsx @@ -45,7 +45,7 @@ const SwapButtonTooltip: React.FC = ({
Gas Fee - {gasFeeStr} GNOT + {gasFeeStr}
); diff --git a/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.stories.tsx b/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.stories.tsx index 8c35893fd..898e40de3 100644 --- a/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.stories.tsx +++ b/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.stories.tsx @@ -3,9 +3,6 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; import SwapCardAutoRouter from "./SwapCardAutoRouter"; import { css } from "@emotion/react"; import { SwapRouteInfo } from "@models/swap/swap-route-info"; -import PoolData from "@repositories/pool/mock/pools.json"; - -const pools = PoolData.pools; const swapRouteInfos: SwapRouteInfo[] = [{ from: { @@ -35,7 +32,7 @@ const swapRouteInfos: SwapRouteInfo[] = [{ currency: "GNOT" }, gasFeeUSD: 0.1, - pools, + pools: [], version: "V1", weight: 100, }]; diff --git a/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.tsx b/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.tsx index e0c4b1ddc..ffe75a809 100644 --- a/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.tsx +++ b/packages/web/src/components/swap/swap-card-auto-router/SwapCardAutoRouter.tsx @@ -3,6 +3,7 @@ import { AutoRouterWrapper, DotLine } from "./SwapCardAutoRouter.styles"; import { SwapRouteInfo } from "@models/swap/swap-route-info"; import DoubleLogo from "@components/common/double-logo/DoubleLogo"; import { SwapSummaryInfo } from "@models/swap/swap-summary-info"; +import { useTokenImage } from "@hooks/token/use-token-image"; interface ContentProps { swapRouteInfos: SwapRouteInfo[]; @@ -13,6 +14,7 @@ const SwapCardAutoRouter: React.FC = ({ swapRouteInfos, swapSummaryInfo, }) => { + const bestGasFee = useMemo(() => { const totalGasFee = swapRouteInfos.reduce((prev, current) => prev + current.gasFeeUSD, 0); return `$${totalGasFee}`; @@ -39,10 +41,27 @@ const SwapCardAutoRouterItem: React.FC = ({ swapRouteInfo, swapSummaryInfo, }) => { + const { getTokenImage } = useTokenImage(); + const weightStr = useMemo(() => { return `${swapRouteInfo.weight}%`; }, [swapRouteInfo.weight]); + const routeInfos = useMemo(() => { + let currentFromToken = swapSummaryInfo.tokenA.path; + return swapRouteInfo.pools.map((pool) => { + const ordered = currentFromToken === pool.tokenAPath; + const fromToken = ordered ? pool.tokenAPath : pool.tokenBPath; + const toToken = ordered ? pool.tokenBPath : pool.tokenAPath; + currentFromToken = toToken; + return { + fee: `${(pool.fee / 10000).toFixed(2)}%`, + fromToken, + toToken + }; + }); + }, [swapRouteInfo.pools, swapSummaryInfo.tokenA.path]); + return (
token logo @@ -51,14 +70,14 @@ const SwapCardAutoRouterItem: React.FC = ({ {weightStr}
- {swapRouteInfo.pools.map((pool, index) => ( - <> -
- -

{pool.fee}

+ {routeInfos.map((routeInfo, index) => ( + +
+ +

{routeInfo.fee}

- - + +
))} token logo
diff --git a/packages/web/src/components/swap/swap-card-content-detail/SwapCardContentDetail.stories.tsx b/packages/web/src/components/swap/swap-card-content-detail/SwapCardContentDetail.stories.tsx index ebe04b1c7..08d0eb37f 100644 --- a/packages/web/src/components/swap/swap-card-content-detail/SwapCardContentDetail.stories.tsx +++ b/packages/web/src/components/swap/swap-card-content-detail/SwapCardContentDetail.stories.tsx @@ -3,11 +3,8 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; import SwapCardContentDetail from "./SwapCardContentDetail"; import { css } from "@emotion/react"; import { SwapRouteInfo } from "@models/swap/swap-route-info"; -import PoolData from "@repositories/pool/mock/pools.json"; import { SwapSummaryInfo } from "@models/swap/swap-summary-info"; -const pools = PoolData.pools; - const swapSummaryInfo: SwapSummaryInfo = { tokenA: { chainId: "test3", @@ -74,7 +71,7 @@ const swapRouteInfos: SwapRouteInfo[] = [{ currency: "GNOT" }, gasFeeUSD: 0.1, - pools, + pools: [], version: "V1", weight: 100, }]; diff --git a/packages/web/src/components/swap/swap-card-fee-info/SwapCardFeeInfo.tsx b/packages/web/src/components/swap/swap-card-fee-info/SwapCardFeeInfo.tsx index bb138dc70..83343d3ad 100644 --- a/packages/web/src/components/swap/swap-card-fee-info/SwapCardFeeInfo.tsx +++ b/packages/web/src/components/swap/swap-card-fee-info/SwapCardFeeInfo.tsx @@ -57,7 +57,7 @@ const SwapCardFeeInfo: React.FC = ({
Gas Fee - {gasFeeStr} GNOT + {gasFeeStr} {`(${gasFeeUSDStr})`}
diff --git a/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx b/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx index 78ed6bcb2..f5e323c09 100644 --- a/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx +++ b/packages/web/src/components/swap/swap-card/SwapCard.stories.tsx @@ -3,10 +3,8 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; import SwapCard from "./SwapCard"; import { css } from "@emotion/react"; import { SwapSummaryInfo } from "@models/swap/swap-summary-info"; -import PoolData from "@repositories/pool/mock/pools.json"; import { SwapRouteInfo } from "@models/swap/swap-route-info"; -const pools = PoolData.pools; const swapSummaryInfo: SwapSummaryInfo = { tokenA: { @@ -74,7 +72,7 @@ export const swapRouteInfos: SwapRouteInfo[] = [{ currency: "GNOT" }, gasFeeUSD: 0.1, - pools, + pools: [], version: "V1", weight: 100, }]; diff --git a/packages/web/src/containers/swap-container/SwapContainer.tsx b/packages/web/src/containers/swap-container/SwapContainer.tsx index c622e0428..a08cfc5cb 100644 --- a/packages/web/src/containers/swap-container/SwapContainer.tsx +++ b/packages/web/src/containers/swap-container/SwapContainer.tsx @@ -6,25 +6,24 @@ import { useTokenData } from "@hooks/token/use-token-data"; import BigNumber from "bignumber.js"; import { useWallet } from "@hooks/wallet/use-wallet"; import { SwapTokenInfo } from "@models/swap/swap-token-info"; -import { amountEmptyNumberInit, SwapDirectionType } from "@common/values"; +import { SwapDirectionType } from "@common/values"; import { SwapResultInfo } from "@models/swap/swap-result-info"; import { SwapSummaryInfo } from "@models/swap/swap-summary-info"; import { AmountModel } from "@models/common/amount-model"; import { SwapRouteInfo } from "@models/swap/swap-route-info"; import { SwapResponse } from "@repositories/swap"; import { matchInputNumber, numberToUSD } from "@utils/number-utils"; -import { SwapError } from "@common/errors/swap"; import { SwapState } from "@states/index"; import { useAtomValue, useAtom } from "jotai"; import { ThemeState } from "@states/index"; import { useRouter } from "next/router"; import { usePreventScroll } from "@hooks/common/use-prevent-scroll"; -import { swapRouteInfos as tempSwapRouteInfos } from "@components/swap/swap-card/SwapCard.stories"; import { useNotice } from "@hooks/common/use-notice"; import { useConnectWalletModal } from "@hooks/wallet/use-connect-wallet-modal"; +import { useSlippage } from "@hooks/common/use-slippage"; const SwapContainer: React.FC = () => { - const [swapValue , setSwapValue] = useAtom(SwapState.swap); + const [swapValue, setSwapValue] = useAtom(SwapState.swap); const { tokenA = null, tokenB = null, type = "EXACT_IN" } = swapValue; const themeKey = useAtomValue(ThemeState.themeKey); const router = useRouter(); @@ -33,63 +32,81 @@ const SwapContainer: React.FC = () => { const [query, setQuery] = useState<{ [key in string]: string | null }>({}); const [initialized, setInitialized] = useState(false); const { connected: connectedWallet, isSwitchNetwork, switchNetwork } = useWallet(); - const { tokens, tokenPrices, balances, updateTokens, updateTokenPrices, updateBalances } = useTokenData(); - const [swapError, setSwapError] = useState(null); + const { tokens, tokenPrices, balances, updateTokens, updateTokenPrices, updateBalances, getTokenUSDPrice, getTokenPriceRate } = useTokenData(); const [tokenAAmount, setTokenAAmount] = useState(""); const [tokenBAmount, setTokenBAmount] = useState(""); const [submitted, setSubmitted] = useState(false); const [copied, setCopied] = useState(false); const [swapResult, setSwapResult] = useState(null); - const [swapRate] = useState(100); - const [slippage, setSlippage] = useState(1); - const [gasFeeAmount] = useState(amountEmptyNumberInit); - const [swapRouteInfos] = useState(tempSwapRouteInfos); + const { slippage: storedSlippage, changeSlippage: changeStoredSlippage } = useSlippage(); + const [slippage, setSlippage] = useState(storedSlippage); + const [gasFeeAmount] = useState({ + amount: 0.000001, + currency: "GNOT" + }); const [openedConfirmModal, setOpenedConfirModal] = useState(false); const [isLoading, setIsLoading] = useState(false); const { openModal } = useConnectWalletModal(); - const { swap, getExpectedSwap } = useSwap({ + const { estimatedRoutes, tokenAmountLimit, swap, estimateSwapRoute } = useSwap({ tokenA, tokenB, direction: type, slippage }); - usePreventScroll(openedConfirmModal || submitted); + const swapRouteInfos: SwapRouteInfo[] = useMemo(() => { + if (!tokenA || !tokenB) { + return []; + } + return estimatedRoutes.map(route => ({ + version: "V1", + from: tokenA, + to: tokenB, + pools: route.pools, + weight: route.quote, + gasFee: { + amount: 1, + currency: "ugnot" + }, + gasFeeUSD: 1 + })); + }, [estimatedRoutes, tokenA, tokenB]); + const checkBalance = useCallback((token: TokenModel, amount: string) => { - const tokenBalance = balances[token.priceId] || 0; + const tokenBalance = balances[token.path] || 0; return BigNumber(tokenBalance).isGreaterThan(amount); }, [balances]); const tokenABalance = useMemo(() => { - if (tokenA && !Number.isNaN(balances[tokenA.priceId])) { - return BigNumber(balances[tokenA.priceId] || 0).toFormat(); + if (tokenA && !Number.isNaN(balances[tokenA.path])) { + return BigNumber(balances[tokenA.path] || 0).toFormat(); } return "-"; }, [balances, tokenA]); const tokenBBalance = useMemo(() => { - if (tokenB && !Number.isNaN(balances[tokenB.priceId])) { - return BigNumber(balances[tokenB.priceId] || 0).toFormat(); + if (tokenB && !Number.isNaN(balances[tokenB.path])) { + return BigNumber(balances[tokenB.path] || 0).toFormat(); } return "-"; }, [balances, tokenB]); const tokenAUSD = useMemo(() => { - if (!tokenA || !tokenPrices[tokenA.priceId]) { + if (!tokenA || !tokenPrices[tokenA.path]) { return Number.NaN; } - return BigNumber(tokenAAmount).multipliedBy(tokenPrices[tokenA.priceId].usd).toNumber(); + return BigNumber(tokenAAmount).multipliedBy(tokenPrices[tokenA.path].usd).toNumber(); }, [tokenA, tokenAAmount, tokenPrices]); const tokenBUSD = useMemo(() => { - if (!tokenB || !tokenPrices[tokenB.priceId]) { + if (!tokenB || !tokenPrices[tokenB.path]) { return Number.NaN; } - return BigNumber(tokenBAmount).multipliedBy(tokenPrices[tokenB.priceId].usd).toNumber(); + return BigNumber(tokenBAmount).multipliedBy(tokenPrices[tokenB.path].usd).toNumber(); }, [tokenB, tokenBAmount, tokenPrices]); const swapButtonText = useMemo(() => { @@ -111,7 +128,7 @@ const SwapContainer: React.FC = () => { ) { return "Amount Too Low"; } - + if (type === "EXACT_IN") { if ( Number(tokenAAmount) > Number(parseFloat(tokenABalance.replace(/,/g, ""))) @@ -126,19 +143,8 @@ const SwapContainer: React.FC = () => { } } return "Swap"; - }, [ - connectedWallet, - swapError, - tokenA, - tokenB, - isSwitchNetwork, - tokenAAmount, - tokenBAmount, - type, - tokenBBalance, - tokenABalance, - ]); - + }, [connectedWallet, tokenA, tokenB, isSwitchNetwork, tokenAAmount, tokenBAmount, type, tokenBBalance, tokenABalance]); + const openConfirmModal = useCallback(() => { setOpenedConfirModal(true); }, []); @@ -153,7 +159,7 @@ const SwapContainer: React.FC = () => { setOpenedConfirModal(false); updateBalances(); if (swapResult?.success) { - setNotice(null, {timeout: 2000}); + setNotice(null, { timeout: 2000 }); } }, [updateBalances, swapResult]); @@ -189,11 +195,14 @@ const SwapContainer: React.FC = () => { })); setTokenBAmount(value); setQuery({ ...query, direction: "EXACT_OUT" }); - }, [query, setTokenAAmount]); + }, [query]); const changeSlippage = useCallback((value: string) => { setSlippage(BigNumber(value || 0).toNumber()); - }, [setSlippage]); + if (!Number.isNaN(value)) { + changeStoredSlippage(Number(value)); + } + }, []); const swapTokenInfo: SwapTokenInfo = useMemo(() => { return { @@ -216,23 +225,37 @@ const SwapContainer: React.FC = () => { if (!tokenA || !tokenB) { return null; } - const swapRateUSD = BigNumber(swapRate).multipliedBy(1).toNumber(); + const targetTokenA = type === "EXACT_IN" ? tokenA : tokenB; + const targetTokenB = type === "EXACT_IN" ? tokenB : tokenA; + const inputAmount = type === "EXACT_IN" ? tokenAAmount : tokenBAmount; + const outputAmount = type === "EXACT_IN" ? tokenBAmount : tokenAAmount; + + const tokenAUSDPrice = getTokenUSDPrice(tokenA.path, 1); + const tokenPairPriceRate = getTokenPriceRate(targetTokenA.path, targetTokenB.path); + const swapRate = tokenPairPriceRate ? tokenPairPriceRate : 0; + const swapRateUSD = tokenAUSDPrice ? Number((Number(tokenAAmount) * tokenAUSDPrice).toFixed(4)) : 0; + const priceImpactNum = BigNumber(swapRate * Number(inputAmount) - Number(outputAmount)) + .multipliedBy(100) + .dividedBy(swapRate * Number(inputAmount)) + .abs(); + const priceImpact = priceImpactNum.isGreaterThan(100) ? 100 : Number(priceImpactNum.toFixed(2)); const gasFeeUSD = BigNumber(gasFeeAmount.amount).multipliedBy(1).toNumber(); + return { tokenA, tokenB, swapDirection: type, swapRate, swapRateUSD, - priceImpact: 0.1, + priceImpact, guaranteedAmount: { - amount: BigNumber(tokenBAmount).toNumber(), - currency: type === "EXACT_IN" ? tokenB.symbol : tokenA.symbol, + amount: tokenAmountLimit, + currency: targetTokenB.symbol, }, gasFee: gasFeeAmount, gasFeeUSD, }; - }, [gasFeeAmount, type, swapRate, tokenA, tokenB, tokenBAmount]); + }, [tokenA, tokenB, type, tokenAAmount, tokenBAmount, tokenAUSD, gasFeeAmount, tokenAmountLimit]); const isAvailSwap = useMemo(() => { if (isLoading) return false; @@ -261,20 +284,8 @@ const SwapContainer: React.FC = () => { } } return true; - }, [ - connectedWallet, - swapError, - tokenA, - tokenB, - isSwitchNetwork, - tokenAAmount, - tokenBAmount, - type, - tokenBBalance, - tokenABalance, - isLoading, - ]); - + }, [connectedWallet, tokenA, tokenB, isSwitchNetwork, tokenAAmount, tokenBAmount, type, tokenBBalance, tokenABalance, isLoading]); + const changeTokenA = useCallback((token: TokenModel) => { let changedSwapDirection = type; if (tokenB?.symbol === token.symbol) { @@ -309,7 +320,7 @@ const SwapContainer: React.FC = () => { const preTokenA = tokenA ? { ...tokenA } : null; const preTokenB = tokenB ? { ...tokenB } : null; const changedSwapDirection = type === "EXACT_IN" ? "EXACT_OUT" : "EXACT_IN"; - + if (!!Number(tokenAAmount || 0) && !!Number(tokenBAmount || 0)) { setIsLoading(true); } @@ -354,10 +365,11 @@ const SwapContainer: React.FC = () => { return; } setSubmitted(true); - swap(tokenAAmount, tokenBAmount).then(result => { + const swapAmount = type === "EXACT_IN" ? tokenAAmount : tokenBAmount; + swap(estimatedRoutes, swapAmount).then(result => { setSwapResult({ success: result !== null, - hash: (result as SwapResponse)?.tx_hash || "", + hash: (result as unknown as SwapResponse)?.tx_hash || "", }); }); } @@ -373,35 +385,32 @@ const SwapContainer: React.FC = () => { } const isExactIn = type === "EXACT_IN"; const changedAmount = isExactIn ? tokenAAmount : tokenBAmount; - + if (Number.isNaN(changedAmount) || BigNumber(changedAmount).isLessThanOrEqualTo(0)) { return; } - + const timeout = setTimeout(() => { - getExpectedSwap(changedAmount).then(result => { + estimateSwapRoute(changedAmount).then(result => { const isError = result === null; - const expectedAmount = isError ? "" : result; - let swapError = null; + const expectedAmount = isError ? "" : result.amount; if (isError) { - swapError = new SwapError("INSUFFICIENT_BALANCE"); - } - if (!checkBalance(tokenA, tokenAAmount) || - !checkBalance(tokenB, tokenBAmount)) { - swapError = new SwapError("INSUFFICIENT_BALANCE"); - } - - if (isExactIn) { - setTokenBAmount(expectedAmount); } else { - setTokenAAmount(expectedAmount); + if (!checkBalance(tokenA, tokenAAmount) || + !checkBalance(tokenB, tokenBAmount)) { + } + + if (isExactIn) { + setTokenBAmount(expectedAmount); + } else { + setTokenAAmount(expectedAmount); + } } - setSwapError(swapError); setIsLoading(() => false); }); }, 2000); return () => clearTimeout(timeout); - }, [checkBalance, getExpectedSwap, type, tokenA, tokenAAmount, tokenB, tokenBAmount, isLoading]); + }, [type, tokenA, tokenAAmount, tokenB, tokenBAmount]); useEffect(() => { const queryValues = []; diff --git a/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx b/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx index 3a27715c2..0f6531e60 100644 --- a/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx +++ b/packages/web/src/containers/token-swap-container/TokenSwapContainer.tsx @@ -113,8 +113,8 @@ const TokenSwapContainer: React.FC = () => { const [swapRouteInfos] = useState(tempSwapRouteInfos); const [slippage, setSlippage] = useState(1); - const { openModal } = useConnectWalletModal(); - + const { openModal } = useConnectWalletModal(); + const { tokenPrices, balances, @@ -127,7 +127,7 @@ const TokenSwapContainer: React.FC = () => { switchNetwork, } = useWallet(); - const { getExpectedSwap } = useSwap({ + const { estimateSwapRoute } = useSwap({ tokenA, tokenB, direction: type, @@ -246,9 +246,9 @@ const TokenSwapContainer: React.FC = () => { return; } const timeout = setTimeout(() => { - getExpectedSwap(changedAmount).then((result) => { + estimateSwapRoute(changedAmount).then((result) => { const isError = result === null; - const expectedAmount = isError ? "" : result; + const expectedAmount = isError ? "" : result.amount; let swapError = null; if (isError) { swapError = new SwapError("INSUFFICIENT_BALANCE"); @@ -272,7 +272,7 @@ const TokenSwapContainer: React.FC = () => { return () => clearTimeout(timeout); }, [ checkBalance, - getExpectedSwap, + estimateSwapRoute, type, tokenA, tokenAAmount, diff --git a/packages/web/src/hooks/common/use-background.tsx b/packages/web/src/hooks/common/use-background.tsx index c0e704bc5..97b3af21f 100644 --- a/packages/web/src/hooks/common/use-background.tsx +++ b/packages/web/src/hooks/common/use-background.tsx @@ -1,12 +1,15 @@ import { useEffect } from "react"; import { useWallet } from "@hooks/wallet/use-wallet"; import { useAtom } from "jotai"; -import { WalletState } from "@states/index"; +import { TokenState, WalletState } from "@states/index"; +import { useTokenData } from "@hooks/token/use-token-data"; export const useBackground = () => { const [walletClient] = useAtom(WalletState.client); - const { initSession, connectAccount, updateWalletEvents } = useWallet(); + const { account, initSession, connectAccount, updateWalletEvents } = useWallet(); + const [, setBalances] = useAtom(TokenState.balances); + const { updateBalances } = useTokenData(); useEffect(() => { initSession(); @@ -19,4 +22,11 @@ export const useBackground = () => { } }, [walletClient]); + useEffect(() => { + setBalances({}); + if (account?.address && account?.chainId) { + updateBalances(); + } + }, [account?.address, account?.chainId]); + }; diff --git a/packages/web/src/hooks/common/use-slippage.ts b/packages/web/src/hooks/common/use-slippage.ts index 31061c20c..551df559a 100644 --- a/packages/web/src/hooks/common/use-slippage.ts +++ b/packages/web/src/hooks/common/use-slippage.ts @@ -9,11 +9,11 @@ export const useSlippage = () => { const changeSlippage = useCallback( (slippage: number) => { - if (!isNumber(slippage)) { + if (!isNumber(slippage) || Number.isNaN(slippage)) { setSlippage(0.01); return; } - + const changedSlippage = Math.min(100, Math.max(0.01, slippage)); setSlippage(changedSlippage); }, diff --git a/packages/web/src/hooks/pool/use-pool-data.tsx b/packages/web/src/hooks/pool/use-pool-data.tsx index d99e6bbd3..b09e97d4f 100644 --- a/packages/web/src/hooks/pool/use-pool-data.tsx +++ b/packages/web/src/hooks/pool/use-pool-data.tsx @@ -6,7 +6,7 @@ import { PoolCardInfo } from "@models/pool/info/pool-card-info"; import { PoolMapper } from "@models/pool/mapper/pool-mapper"; import { PoolState } from "@states/index"; import { useAtom } from "jotai"; -import { useMemo, useEffect } from "react"; +import { useMemo } from "react"; export const usePoolData = () => { const { poolRepository } = useGnoswapContext(); @@ -15,13 +15,6 @@ export const usePoolData = () => { const [isFetchedPositions, setIsFetchedPositions] = useAtom(PoolState.isFetchedPositions); const [loading, setLoading] = useAtom(PoolState.isLoading); - useEffect(() => { - const timeout = setTimeout(() => { - setLoading(false); - }, 2000); - return () => clearTimeout(timeout); - }, []); - const poolListInfos = useMemo(() => { return pools?.map(PoolMapper.toListInfo); }, [pools]); @@ -55,6 +48,7 @@ export const usePoolData = () => { async function updatePools() { const response = await poolRepository.getPools(); setPools(response.pools); + setLoading(false); setIsFetchedPools(true); } diff --git a/packages/web/src/hooks/swap/use-swap.tsx b/packages/web/src/hooks/swap/use-swap.tsx index b553cbbab..6def72a6b 100644 --- a/packages/web/src/hooks/swap/use-swap.tsx +++ b/packages/web/src/hooks/swap/use-swap.tsx @@ -1,10 +1,11 @@ import { SwapDirectionType } from "@common/values"; -import { SwapFeeTierInfoMap } from "@constants/option.constant"; +import { EstimatedRoute } from "@gnoswap-labs/swap-router"; import { useGnoswapContext } from "@hooks/common/use-gnoswap-context"; +import { useSlippage } from "@hooks/common/use-slippage"; import { useWallet } from "@hooks/wallet/use-wallet"; import { TokenModel } from "@models/token/token-model"; import BigNumber from "bignumber.js"; -import { useCallback, useMemo } from "react"; +import { useCallback, useMemo, useState } from "react"; interface UseSwapProps { tokenA: TokenModel | null; @@ -19,107 +20,75 @@ export const useSwap = ({ direction, }: UseSwapProps) => { const { account } = useWallet(); - const { swapRepository } = useGnoswapContext(); + const { poolRepository, swapRouterRepository } = useGnoswapContext(); + const [estimatedRoutes, setEstimatedRoutes] = useState([]); + const [estimatedAmount, setEstimatedAmount] = useState(null); + const { slippage } = useSlippage(); const selectedTokenPair = tokenA !== null && tokenB !== null; - /** - * TODO: Once a contract can handle GRC20 tokens dynamically, it will need to be reconsidered. - */ - const zeroForOne = useMemo(() => { - return tokenA?.symbol.toLowerCase() === "foo"; - }, [tokenA?.symbol]); + const tokenAmountLimit = useMemo(() => { + if (estimatedAmount && !Number.isNaN(slippage)) { + const slippageAmountNumber = BigNumber(estimatedAmount).multipliedBy(slippage * 0.01); + const tokenAmountLimit = direction === "EXACT_IN" ? + BigNumber(estimatedAmount).minus(slippageAmountNumber).toNumber() : + BigNumber(estimatedAmount).plus(slippageAmountNumber).toNumber(); - const amountDirection = useMemo(() => { - return direction === "EXACT_IN" ? 1 : -1; - }, [direction]); - - const tempAmountDirection = useMemo(() => { - if (!zeroForOne) { - return -1 * amountDirection; - } - return amountDirection; - }, [amountDirection, zeroForOne]); - - /** - * TODO: Once a contract can handle GRC20 tokens dynamically, it will need to be reconsidered. - */ - const getAmountResult = useCallback((inputAmount: number, tokenAAmount: number, tokenBAmount: number) => { - const isExactIn = inputAmount >= 0; - if (isExactIn === zeroForOne) { - return BigNumber(tokenBAmount).abs().toString(); - } - return BigNumber(tokenAAmount).abs().toString(); - }, [zeroForOne]); - - const findSwapPool = useCallback(async (amount: string) => { - if (!selectedTokenPair) { - return null; + return tokenAmountLimit > 0 ? Math.round(tokenAmountLimit) : 0; } - const amountSpecified = BigNumber(amount).multipliedBy(tempAmountDirection).toNumber(); + return 0; + }, [direction, estimatedAmount, slippage]); - return await swapRepository.findSwapPool({ - tokenA: zeroForOne ? tokenA : tokenB, - tokenB: zeroForOne ? tokenB : tokenA, - zeroForOne, - amountSpecified, - }).catch(() => null); - }, [selectedTokenPair, tempAmountDirection, swapRepository, zeroForOne, tokenA, tokenB]); - - const getExpectedSwap = useCallback(async (amount: string) => { + const estimateSwapRoute = async (amount: string) => { if (!selectedTokenPair) { return null; } - const swapPool = await findSwapPool(amount); - if (!swapPool) { + if (Number.isNaN(amount)) { return null; } - const fee = SwapFeeTierInfoMap[swapPool.feeTier].fee; - - const amountSpecified = BigNumber(amount || 0).multipliedBy(tempAmountDirection).toNumber(); + const pools = await poolRepository.getRPCPools(); + swapRouterRepository.updatePools(pools); - return swapRepository.getExpectedSwapResult({ - tokenA: zeroForOne ? tokenA : tokenB, - tokenB: zeroForOne ? tokenB : tokenA, - fee, - receiver: "", - zeroForOne, - amountSpecified, - }).then((data) => - getAmountResult(amountSpecified, data.tokenAAmount, data.tokenBAmount)) - .catch(() => null); - }, [selectedTokenPair, findSwapPool, tempAmountDirection, swapRepository, zeroForOne, tokenA, tokenB, getAmountResult]); + return swapRouterRepository.estimateSwapRoute({ + inputToken: tokenA, + outputToken: tokenB, + exactType: direction, + tokenAmount: Number(amount) + }).then(response => { + console.log("response", response); + setEstimatedRoutes(response.estimatedRoutes); + setEstimatedAmount(response.amount); + return response; + }).catch(() => { + setEstimatedRoutes([]); + setEstimatedAmount(null); + return null; + }); + }; - const swap = useCallback(async (tokenAAmount: string, tokenBAmount: string) => { + const swap = useCallback(async (estimatedRoutes: EstimatedRoute[], tokenAmount: string) => { if (!account) { return false; } if (!selectedTokenPair) { return false; } - const amountSpecified = direction === "EXACT_IN" ? - BigNumber(tokenAAmount).multipliedBy(amountDirection).toNumber() : - BigNumber(tokenBAmount).multipliedBy(amountDirection).toNumber(); - - const swapPool = await findSwapPool(`${amountSpecified}`); - if (!swapPool) { - return false; - } - const fee = SwapFeeTierInfoMap[swapPool.feeTier].fee; - const response = await swapRepository.swap({ - tokenA: zeroForOne ? tokenA : tokenB, - tokenB: zeroForOne ? tokenB : tokenA, - fee, - receiver: account.address, - zeroForOne, - amountSpecified, + const response = await swapRouterRepository.swapRoute({ + inputToken: tokenA, + outputToken: tokenB, + estimatedRoutes, + exactType: direction, + tokenAmount: Math.floor(Number(tokenAmount)), + tokenAmountLimit }) .catch(() => false); return response; - }, [account, amountDirection, direction, findSwapPool, selectedTokenPair, swapRepository, tokenA, tokenB, zeroForOne]); + }, [account, direction, selectedTokenPair, swapRouterRepository, tokenA, tokenAmountLimit, tokenB]); return { + tokenAmountLimit, + estimatedRoutes, swap, - getExpectedSwap + estimateSwapRoute }; }; \ No newline at end of file diff --git a/packages/web/src/hooks/token/use-token-data.tsx b/packages/web/src/hooks/token/use-token-data.tsx index b1f4ef907..bd5c27d89 100644 --- a/packages/web/src/hooks/token/use-token-data.tsx +++ b/packages/web/src/hooks/token/use-token-data.tsx @@ -7,7 +7,7 @@ import { TokenState } from "@states/index"; import { evaluateExpressionToNumber } from "@utils/rpc-utils"; import BigNumber from "bignumber.js"; import { useAtom } from "jotai"; -import { useEffect, useMemo } from "react"; +import { useCallback, useMemo } from "react"; export const useTokenData = () => { const { account } = useWallet(); @@ -17,19 +17,6 @@ export const useTokenData = () => { const [balances, setBalances] = useAtom(TokenState.balances); const [loading, setLoading] = useAtom(TokenState.isLoading); - useEffect(() => { - const timeout = setTimeout(() => { - setLoading(false); - }, 2000); - return () => clearTimeout(timeout); - },[]); - - useEffect(() => { - if (rpcProvider && account) { - updateBalances(); - } - }, [rpcProvider, account]); - const trendingTokens: CardListTokenInfo[] = useMemo(() => { const sortedTokens = tokens.sort((t1, t2) => { if (tokenPrices[t1.priceId] && tokenPrices[t2.priceId]) { @@ -73,8 +60,26 @@ export const useTokenData = () => { })); }, [tokenPrices, tokens]); + const getTokenUSDPrice = useCallback((tokenAId: string, amount: bigint | string | number) => { + const tokenUSDPrice = tokenPrices[tokenAId].usd; + if (!tokenUSDPrice || Number.isNaN(amount)) { + return null; + } + return BigNumber(amount.toString()).multipliedBy(tokenUSDPrice).toNumber(); + }, [tokenPrices]); + + const getTokenPriceRate = useCallback((tokenAId: string, tokenBId: string) => { + const tokenAUSDPrice = tokenPrices[tokenAId].usd; + const tokenBUSDPrice = tokenPrices[tokenBId].usd; + if (!tokenAUSDPrice || !tokenBUSDPrice) { + return null; + } + return BigNumber(tokenBUSDPrice).dividedBy(tokenAUSDPrice).toNumber(); + }, [tokenPrices]); + async function updateTokens() { const response = await tokenRepository.getTokens(); + setLoading(false); setTokens(response?.tokens || []); } @@ -116,6 +121,8 @@ export const useTokenData = () => { balances, trendingTokens, recentlyAddedTokens, + getTokenUSDPrice, + getTokenPriceRate, updateTokens, updateTokenPrices, updateBalances, diff --git a/packages/web/src/hooks/token/use-token-image.tsx b/packages/web/src/hooks/token/use-token-image.tsx new file mode 100644 index 000000000..5fa9bd43f --- /dev/null +++ b/packages/web/src/hooks/token/use-token-image.tsx @@ -0,0 +1,15 @@ +import { TokenState } from "@states/index"; +import { useAtom } from "jotai"; +import { useCallback } from "react"; + +export const useTokenImage = () => { + const [tokens] = useAtom(TokenState.tokens); + + const getTokenImage = useCallback((tokenId: string): string | null => { + return tokens.find(token => token.path === tokenId)?.logoURI || null; + }, [tokens]); + + return { + getTokenImage + }; +}; \ No newline at end of file diff --git a/packages/web/src/models/pool/mapper/pool-rpc-mapper.ts b/packages/web/src/models/pool/mapper/pool-rpc-mapper.ts index c9b0025d4..cbe37ecfd 100644 --- a/packages/web/src/models/pool/mapper/pool-rpc-mapper.ts +++ b/packages/web/src/models/pool/mapper/pool-rpc-mapper.ts @@ -1,7 +1,4 @@ -import { - PoolListRPCResponse, - PoolRPCResponse, -} from "@repositories/pool/response/pool-rpc-response"; +import { PoolRPCResponse } from "@repositories/pool/response/pool-rpc-response"; import { rawBySqrtX96 } from "@utils/swap-utils"; import { PoolRPCModel } from "../pool-rpc-model"; @@ -45,11 +42,11 @@ export class PoolRPCMapper { }; } - public static fromList(response: PoolListRPCResponse | null): PoolRPCModel[] { - if (!response || !response?.response?.data) { + public static fromList(response: PoolRPCResponse[] | null): PoolRPCModel[] { + if (!response) { throw new Error("mapper error"); } - return response.response.data.map(PoolRPCMapper.from); + return response.map(PoolRPCMapper.from); } } diff --git a/packages/web/src/models/swap/swap-route-info.ts b/packages/web/src/models/swap/swap-route-info.ts index 2dcc064a2..bc389f091 100644 --- a/packages/web/src/models/swap/swap-route-info.ts +++ b/packages/web/src/models/swap/swap-route-info.ts @@ -1,12 +1,12 @@ -import { PoolModel } from "@models/pool/pool-model"; import { TokenModel } from "@models/token/token-model"; import { AmountModel } from "@models/common/amount-model"; +import { Pool } from "@gnoswap-labs/swap-router"; export interface SwapRouteInfo { version: string; from: TokenModel; to: TokenModel; - pools: PoolModel[]; + pools: Pool[]; weight: number; gasFee: AmountModel; gasFeeUSD: number; diff --git a/packages/web/src/models/token/token-model.ts b/packages/web/src/models/token/token-model.ts index f171bb3c7..7c466f013 100644 --- a/packages/web/src/models/token/token-model.ts +++ b/packages/web/src/models/token/token-model.ts @@ -21,3 +21,11 @@ export interface TokenModel { isGasToken?: boolean; } + +export interface NativeTokenModel extends TokenModel { + originName: string; + + originSymbol: string; + + originDenom: string; +} diff --git a/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx b/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx index 720c938db..53bd09050 100644 --- a/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx +++ b/packages/web/src/providers/gnoswap-service-provider/GnoswapServiceProvider.tsx @@ -14,6 +14,8 @@ import { CommonState, WalletState } from "@states/index"; import { GnoProvider, GnoWSProvider } from "@gnolang/gno-js-client"; import { SwapRepositoryImpl } from "@repositories/swap/swap-repository-impl"; import ChainNetworkInfos from "@resources/chains.json"; +import { SwapRouterRepository } from "@repositories/swap/swap-router-repository"; +import { SwapRouterRepositoryImpl } from "@repositories/swap/swap-router-repository-impl"; interface GnoswapContextProps { rpcProvider: GnoProvider | null; @@ -22,6 +24,7 @@ interface GnoswapContextProps { poolRepository: PoolRepository; stakingRepository: StakingRepository; swapRepository: SwapRepository; + swapRouterRepository: SwapRouterRepository; tokenRepository: TokenRepository; } @@ -80,6 +83,10 @@ const GnoswapServiceProvider: React.FC = ({ return new SwapRepositoryImpl(walletClient, rpcProvider, localStorageClient); }, [localStorageClient, rpcProvider, walletClient]); + const swapRouterRepository = useMemo(() => { + return new SwapRouterRepositoryImpl(rpcProvider, walletClient); + }, [rpcProvider, walletClient]); + const tokenRepository = useMemo(() => { return new TokenRepositoryImpl(networkClient, localStorageClient); }, [localStorageClient, networkClient]); @@ -94,6 +101,7 @@ const GnoswapServiceProvider: React.FC = ({ stakingRepository, swapRepository, tokenRepository, + swapRouterRepository, }} > {children} diff --git a/packages/web/src/repositories/pool/pool-repository-impl.ts b/packages/web/src/repositories/pool/pool-repository-impl.ts index 0bcbb3508..051f9a0e8 100644 --- a/packages/web/src/repositories/pool/pool-repository-impl.ts +++ b/packages/web/src/repositories/pool/pool-repository-impl.ts @@ -22,7 +22,7 @@ import { PoolRPCModel } from "@models/pool/pool-rpc-model"; import { PoolRPCMapper } from "@models/pool/mapper/pool-rpc-mapper"; import { PoolError } from "@common/errors/pool"; import { PoolMapper } from "@models/pool/mapper/pool-mapper"; -import { PoolListRPCResponse, PoolRPCResponse } from "./response/pool-rpc-response"; +import { PoolRPCResponse } from "./response/pool-rpc-response"; const POOL_PATH = process.env.NEXT_PUBLIC_PACKAGE_POOL_PATH || ""; const POSITION_PATH = process.env.NEXT_PUBLIC_PACKAGE_POSITION_PATH || ""; @@ -51,11 +51,11 @@ export class PoolRepositoryImpl implements PoolRepository { const result: PoolRPCModel[] = await this.rpcProvider.evaluateExpression( poolPackagePath, param, - ).then(evaluateExpressionToObject) + ).then(evaluateExpressionToObject) .then(PoolRPCMapper.fromList) .catch(e => { console.error(e); - return [] as PoolRPCModel[]; + return []; }); return result; }; @@ -174,11 +174,8 @@ export class PoolRepositoryImpl implements PoolRepository { startPrice: string, caller: string, ) { - // Todo: Change to Path - // const tokenAPath = tokenA.path; - // const tokenBPath = tokenB.path; - const tokenAPath = tokenA.symbol.toLowerCase(); - const tokenBPath = tokenB.symbol.toLowerCase(); + const tokenAPath = tokenA.path; + const tokenBPath = tokenB.path; const fee = `${SwapFeeTierInfoMap[feeTier].fee}`; return { caller, @@ -214,11 +211,8 @@ export class PoolRepositoryImpl implements PoolRepository { slippage: number, caller: string, ) { - // Todo: Change to Path - // const tokenAPath = tokenA.path; - // const tokenBPath = tokenB.path; - const tokenAPath = tokenA.symbol.toLowerCase(); - const tokenBPath = tokenB.symbol.toLowerCase(); + const tokenAPath = tokenA.path; + const tokenBPath = tokenB.path; const fee = `${SwapFeeTierInfoMap[feeTier].fee}`; const deadline = "7282571140"; return { diff --git a/packages/web/src/repositories/swap/request/estimate-swap-route-request.ts b/packages/web/src/repositories/swap/request/estimate-swap-route-request.ts index 8689fcf0a..afe2f1e3e 100644 --- a/packages/web/src/repositories/swap/request/estimate-swap-route-request.ts +++ b/packages/web/src/repositories/swap/request/estimate-swap-route-request.ts @@ -1,4 +1,3 @@ -import { PoolRPCModel } from "@models/pool/pool-rpc-model"; import { TokenModel } from "@models/token/token-model"; export interface EstimateSwapRouteRequest { @@ -9,6 +8,4 @@ export interface EstimateSwapRouteRequest { tokenAmount: number; exactType: "EXACT_IN" | "EXACT_OUT"; - - pools: PoolRPCModel[]; } diff --git a/packages/web/src/repositories/swap/response/estimate-swap-route-response.ts b/packages/web/src/repositories/swap/response/estimate-swap-route-response.ts index 9ed9cb41b..c3b032665 100644 --- a/packages/web/src/repositories/swap/response/estimate-swap-route-response.ts +++ b/packages/web/src/repositories/swap/response/estimate-swap-route-response.ts @@ -1,3 +1,6 @@ +import { EstimatedRoute } from "@gnoswap-labs/swap-router"; + export interface EstimateSwapRouteResponse { - amount: number; + estimatedRoutes: EstimatedRoute[]; + amount: string; } diff --git a/packages/web/src/repositories/swap/swap-repository-mock.ts b/packages/web/src/repositories/swap/swap-repository-mock.ts index ee0e630d6..daa944996 100644 --- a/packages/web/src/repositories/swap/swap-repository-mock.ts +++ b/packages/web/src/repositories/swap/swap-repository-mock.ts @@ -10,7 +10,6 @@ import { } from "."; import { SwapRequest } from "./request"; import { WalletClient } from "@common/clients/wallet-client"; -import { SwapPoolResponse } from "./response/swap-pool-response"; export class SwapRepositoryMock implements SwapRepository { private localStorageClient: StorageClient; @@ -20,14 +19,6 @@ export class SwapRepositoryMock implements SwapRepository { this.localStorageClient = new MockStorageClient("LOCAL"); this.walletClient = walletClient; } - public findSwapPool = async (): Promise => { - return { - feeTier: "FEE_100", - poolPath: "", - sqrtPriceX96: "", - tickSpacing: 20, - }; - }; public getSwapRate = async (): Promise => { return { diff --git a/packages/web/src/repositories/swap/swap-repository.ts b/packages/web/src/repositories/swap/swap-repository.ts index 9f8312e48..a93236060 100644 --- a/packages/web/src/repositories/swap/swap-repository.ts +++ b/packages/web/src/repositories/swap/swap-repository.ts @@ -1,5 +1,4 @@ import { SwapRequest } from "./request"; -import { FindBestPoolReqeust } from "./request/find-best-pool-request"; import { SwapInfoRequest } from "./request/swap-info-request"; import { SwapExpectedResultResponse, @@ -7,11 +6,8 @@ import { SwapRateResponse, SwapResponse, } from "./response"; -import { SwapPoolResponse } from "./response/swap-pool-response"; export interface SwapRepository { - findSwapPool: (request: FindBestPoolReqeust) => Promise; - getSwapRate: (request: SwapInfoRequest) => Promise; getSwapFee: () => Promise; diff --git a/packages/web/src/repositories/swap/swap-router-repository-impl.ts b/packages/web/src/repositories/swap/swap-router-repository-impl.ts index 0424df9f6..8800bd957 100644 --- a/packages/web/src/repositories/swap/swap-router-repository-impl.ts +++ b/packages/web/src/repositories/swap/swap-router-repository-impl.ts @@ -5,11 +5,12 @@ import { makeRoutesQuery } from "@utils/swap-route-utils"; import { GnoProvider } from "@gnolang/gno-js-client"; import { CommonError } from "@common/errors"; import { SwapError } from "@common/errors/swap"; -import { evaluateExpressionToObject, makeABCIParams } from "@utils/rpc-utils"; +import { evaluateExpressionToNumber, makeABCIParams } from "@utils/rpc-utils"; import { EstimateSwapRouteRequest } from "./request/estimate-swap-route-request"; import { SwapRouteRequest } from "./request/swap-route-request"; import { EstimateSwapRouteResponse } from "./response/estimate-swap-route-response"; import { SwapRouter } from "@gnoswap-labs/swap-router"; +import { PoolRPCModel } from "@models/pool/pool-rpc-model"; const ROUTER_PACKAGE_PATH = process.env.NEXT_PUBLIC_PACKAGE_ROUTER_PATH; const ROUTER_ADDRESS = process.env.NEXT_PUBLIC_PACKAGE_ROUTER_ADDRESS || ""; @@ -17,23 +18,28 @@ const ROUTER_ADDRESS = process.env.NEXT_PUBLIC_PACKAGE_ROUTER_ADDRESS || ""; export class SwapRouterRepositoryImpl implements SwapRouterRepository { private rpcProvider: GnoProvider | null; private walletClient: WalletClient | null; + private pools: PoolRPCModel[]; - constructor(rpcProvider: GnoProvider, walletClient: WalletClient) { + constructor( + rpcProvider: GnoProvider | null, + walletClient: WalletClient | null, + ) { this.rpcProvider = rpcProvider; this.walletClient = walletClient; + this.pools = []; + } + + public updatePools(pools: PoolRPCModel[]) { + this.pools = pools; } - public estimateSwapRoute = async(request: EstimateSwapRouteRequest):Promise => { + public estimateSwapRoute = async ( + request: EstimateSwapRouteRequest, + ): Promise => { if (!ROUTER_PACKAGE_PATH || !this.rpcProvider) { throw new CommonError("FAILED_INITIALIZE_GNO_PROVIDER"); } - const { - inputToken, - outputToken, - exactType, - tokenAmount, - pools, - } = request; + const { inputToken, outputToken, exactType, tokenAmount } = request; if (Number.isNaN(tokenAmount)) { throw new SwapError("INVALID_PARAMS"); @@ -41,9 +47,14 @@ export class SwapRouterRepositoryImpl implements SwapRouterRepository { const inputTokenPath = inputToken.path; const outputTokenPath = outputToken.path; - const swapRouter = new SwapRouter(pools); - const estimatedRoutes = swapRouter.estimateSwapRoute(inputTokenPath, outputTokenPath, BigInt(tokenAmount), exactType); - + const swapRouter = new SwapRouter(this.pools); + const estimatedRoutes = swapRouter.estimateSwapRoute( + inputTokenPath, + outputTokenPath, + BigInt(tokenAmount), + exactType, + ); + const routesQuery = makeRoutesQuery(estimatedRoutes, inputToken.path); const quotes = estimatedRoutes.map(route => route.quote).join(","); const param = makeABCIParams("DrySwapRoute", [ @@ -57,19 +68,18 @@ export class SwapRouterRepositoryImpl implements SwapRouterRepository { const result = await this.rpcProvider .evaluateExpression(ROUTER_PACKAGE_PATH, param) - .then(evaluateExpressionToObject<{ - response: { - data: EstimateSwapRouteResponse; - } - }>); + .then(evaluateExpressionToNumber); - if (result === null) { - throw new SwapError("NOT_FOUND_SWAP_POOL"); - } - return result.response.data; + if (result === null) { + throw new SwapError("NOT_FOUND_SWAP_POOL"); + } + return { + amount: result.toString(), + estimatedRoutes, + }; }; - public swapRoute = async(request: SwapRouteRequest):Promise => { + public swapRoute = async (request: SwapRouteRequest): Promise => { if (this.walletClient === null) { throw new CommonError("FAILED_INITIALIZE_WALLET"); } @@ -84,14 +94,22 @@ export class SwapRouterRepositoryImpl implements SwapRouterRepository { exactType, tokenAmount, estimatedRoutes, - tokenAmountLimit + tokenAmountLimit, } = request; const routesQuery = makeRoutesQuery(estimatedRoutes, inputToken.path); const quotes = estimatedRoutes.map(route => route.quote).join(","); const response = await this.walletClient.sendTransaction({ messages: [ - SwapRouterRepositoryImpl.makeApproveTokenMessage(inputToken, "", address), - SwapRouterRepositoryImpl.makeApproveTokenMessage(outputToken, "", address), + SwapRouterRepositoryImpl.makeApproveTokenMessage( + inputToken, + "", + address, + ), + SwapRouterRepositoryImpl.makeApproveTokenMessage( + outputToken, + "", + address, + ), { caller: address, send: "", @@ -118,7 +136,6 @@ export class SwapRouterRepositoryImpl implements SwapRouterRepository { return response.type; }; - private static makeApproveTokenMessage( token: TokenModel, amount: string, diff --git a/packages/web/src/repositories/swap/swap-router-repository-mock.ts b/packages/web/src/repositories/swap/swap-router-repository-mock.ts index cc3d055e9..750e4ce5a 100644 --- a/packages/web/src/repositories/swap/swap-router-repository-mock.ts +++ b/packages/web/src/repositories/swap/swap-router-repository-mock.ts @@ -2,14 +2,22 @@ import { SwapRouterRepository } from "./swap-router-repository"; import { EstimateSwapRouteRequest } from "./request/estimate-swap-route-request"; import { SwapRouteRequest } from "./request/swap-route-request"; import { EstimateSwapRouteResponse } from "./response/estimate-swap-route-response"; +import { PoolRPCModel } from "@models/pool/pool-rpc-model"; export class SwapRouterRepositoryMock implements SwapRouterRepository { + public pools: PoolRPCModel[] = []; + + public updatePools(pools: PoolRPCModel[]) { + this.pools = pools; + } + public estimateSwapRoute = async ( request: EstimateSwapRouteRequest, ): Promise => { console.log(request); return { - amount: 213, + estimatedRoutes: [], + amount: "0", }; }; diff --git a/packages/web/src/repositories/swap/swap-router-repository.ts b/packages/web/src/repositories/swap/swap-router-repository.ts index d0b1579cf..e5e5717de 100644 --- a/packages/web/src/repositories/swap/swap-router-repository.ts +++ b/packages/web/src/repositories/swap/swap-router-repository.ts @@ -1,8 +1,11 @@ +import { PoolRPCModel } from "@models/pool/pool-rpc-model"; import { EstimateSwapRouteRequest } from "./request/estimate-swap-route-request"; import { SwapRouteRequest } from "./request/swap-route-request"; import { EstimateSwapRouteResponse } from "./response/estimate-swap-route-response"; export interface SwapRouterRepository { + updatePools: (pools: PoolRPCModel[]) => void; + estimateSwapRoute: ( request: EstimateSwapRouteRequest, ) => Promise; diff --git a/packages/web/src/repositories/token/token-repository-impl.ts b/packages/web/src/repositories/token/token-repository-impl.ts index 0867418c7..6a61b2efa 100644 --- a/packages/web/src/repositories/token/token-repository-impl.ts +++ b/packages/web/src/repositories/token/token-repository-impl.ts @@ -26,6 +26,9 @@ export class TokenRepositoryImpl implements TokenRepository { const response = await this.networkClient.get({ url: "/tokens", }); + if (response.data.tokens === null) { + return { tokens: [] }; + } return response.data; }; diff --git a/packages/web/src/states/token.ts b/packages/web/src/states/token.ts index d7f201306..ce713bfff 100644 --- a/packages/web/src/states/token.ts +++ b/packages/web/src/states/token.ts @@ -35,4 +35,4 @@ export const swap = atom({ type: "EXACT_IN", }); -export const isLoading = atom(true); \ No newline at end of file +export const isLoading = atom(true); diff --git a/packages/web/src/utils/common.ts b/packages/web/src/utils/common.ts index ad096fefb..9cf82db44 100644 --- a/packages/web/src/utils/common.ts +++ b/packages/web/src/utils/common.ts @@ -1,19 +1,29 @@ -export class Queue { - private _arr: T[]; +export function wait( + runner: () => Promise, + waitTime = 1000, + finishTime = 10000, +) { + return new Promise(resolve => { + let finishedResult = false; + let result: T | null = null; + let currentTime = 0; - constructor() { - this._arr = []; - } + runner().then(response => { + result = response; + finishedResult = true; + }); - public get arr() { - return this._arr; - } + const interval = setInterval(() => { + if (finishedResult && currentTime >= waitTime) { + clearInterval(interval); + resolve(result); + } - enqueue(item: T) { - this._arr.push(item); - } - - dequeue() { - return this._arr.shift(); - } + if (currentTime >= finishTime) { + clearInterval(interval); + resolve(null); + } + currentTime += 500; + }, 500); + }); }