diff --git a/package.json b/package.json index 87300613d..bf594ae6f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@oraichain/orai-bitcoin": "2.0.0", "@oraichain/oraidex-common-ui": "1.0.11", "@oraichain/oraidex-contracts-sdk": "^1.0.26", - "@oraichain/oraidex-universal-swap": "1.0.91", + "@oraichain/oraidex-universal-swap": "1.0.93", "@reduxjs/toolkit": "^1.9.3", "@sentry/react": "^7.47.0", "@tanstack/react-query": "^4.32.6", diff --git a/src/assets/icons/obridge_full_dark.svg b/src/assets/icons/obridge_full_dark.svg index 2104e97ee..0fe2ab160 100644 --- a/src/assets/icons/obridge_full_dark.svg +++ b/src/assets/icons/obridge_full_dark.svg @@ -1,19 +1,35 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/obridge_full_light.svg b/src/assets/icons/obridge_full_light.svg index 3cac48890..f1b6ef341 100644 --- a/src/assets/icons/obridge_full_light.svg +++ b/src/assets/icons/obridge_full_light.svg @@ -1,19 +1,34 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/UniversalSwap/Swap/components/TokenAndChainSelectors/index.tsx b/src/pages/UniversalSwap/Swap/components/TokenAndChainSelectors/index.tsx new file mode 100644 index 000000000..7c4dd6a58 --- /dev/null +++ b/src/pages/UniversalSwap/Swap/components/TokenAndChainSelectors/index.tsx @@ -0,0 +1,88 @@ +import React, { useRef } from 'react'; +import SelectToken from '../SelectToken/SelectToken'; +import SelectChain from '../SelectChain/SelectChain'; +import useOnClickOutside from 'hooks/useOnClickOutside'; + +const TokenAndChainSelectors = ({ + setIsSelectTokenTo, + setIsSelectTokenFrom, + setIsSelectChainTo, + setIsSelectChainFrom, + amounts, + prices, + handleChangeToken, + filteredToTokens, + filteredFromTokens, + theme, + selectChainTo, + selectChainFrom, + isSelectTokenTo, + isSelectTokenFrom, + isSelectChainTo, + isSelectChainFrom, + setSelectChainTo, + setSelectChainFrom, + setTokenDenomFromChain, + originalFromToken, + unSupportSimulateToken +}) => { + const ref = useRef(null); + useOnClickOutside(ref, () => { + setIsSelectTokenFrom(false); + setIsSelectTokenTo(false); + setIsSelectChainFrom(false); + setIsSelectChainTo(false); + }); + + return ( +
+ handleChangeToken(token, 'to')} + items={filteredToTokens} + theme={theme} + selectChain={selectChainTo} + isSelectToken={isSelectTokenTo} + /> + handleChangeToken(token, 'from')} + isSelectToken={isSelectTokenFrom} + /> + { + setSelectChainTo(chain); + setTokenDenomFromChain(chain, 'to'); + }} + prices={prices} + isSelectToken={isSelectChainTo} + /> + { + setSelectChainFrom(chain); + setTokenDenomFromChain(chain, 'from'); + }} + isSelectToken={isSelectChainFrom} + /> +
+ ); +}; + +export default TokenAndChainSelectors; diff --git a/src/pages/UniversalSwap/Swap/hooks/useCalculateDataSwap.ts b/src/pages/UniversalSwap/Swap/hooks/useCalculateDataSwap.ts new file mode 100644 index 000000000..74331db7f --- /dev/null +++ b/src/pages/UniversalSwap/Swap/hooks/useCalculateDataSwap.ts @@ -0,0 +1,157 @@ +import { BigDecimal, calculateMinReceive, CW20_DECIMALS, toAmount } from '@oraichain/oraidex-common'; +import { OraiswapRouterQueryClient } from '@oraichain/oraidex-contracts-sdk'; +import { useQuery } from '@tanstack/react-query'; +import { network } from 'config/networks'; +import { useCoinGeckoPrices } from 'hooks/useCoingecko'; +import useConfigReducer from 'hooks/useConfigReducer'; +import useTokenFee, { useRelayerFeeToken } from 'hooks/useTokenFee'; +import { numberWithCommas } from 'pages/Pools/helpers'; +import { getAverageRatio, getRemoteDenom, isAllowAlphaSmartRouter } from 'pages/UniversalSwap/helpers'; +import { fetchTokenInfos } from 'rest/api'; +import { useSimulate } from './useSimulate'; +import { useSwapFee } from './useSwapFee'; + +export const SIMULATE_INIT_AMOUNT = 1; + +const useCalculateDataSwap = ({ originalFromToken, originalToToken, fromToken, toToken, userSlippage }) => { + const { data: prices } = useCoinGeckoPrices(); + const { fee, isDependOnNetwork } = useSwapFee({ + fromToken: originalFromToken, + toToken: originalToToken + }); + + const remoteTokenDenomFrom = getRemoteDenom(originalFromToken); + const remoteTokenDenomTo = getRemoteDenom(originalToToken); + const fromTokenFee = useTokenFee(remoteTokenDenomFrom, fromToken.chainId, toToken.chainId); + const toTokenFee = useTokenFee(remoteTokenDenomTo, fromToken.chainId, toToken.chainId); + + const [isAIRoute] = useConfigReducer('AIRoute'); + const useAlphaSmartRouter = isAllowAlphaSmartRouter(originalFromToken, originalToToken) && isAIRoute; + const routerClient = new OraiswapRouterQueryClient(window.client, network.router); + + const { relayerFee, relayerFeeInOraiToAmount: relayerFeeToken } = useRelayerFeeToken( + originalFromToken, + originalToToken + ); + + const { + data: [fromTokenInfoData, toTokenInfoData] + } = useQuery(['token-infos', fromToken, toToken], () => fetchTokenInfos([fromToken!, toToken!]), { initialData: [] }); + + const { simulateData, setSwapAmount, fromAmountToken, toAmountToken, debouncedFromAmount, isPreviousSimulate } = + useSimulate( + 'simulate-data', + fromTokenInfoData, + toTokenInfoData, + originalFromToken, + originalToToken, + routerClient, + null, + { + useAlphaSmartRoute: useAlphaSmartRouter + }, + isAIRoute + ); + + const { simulateData: averageSimulateData } = useSimulate( + 'average-simulate-data', + fromTokenInfoData, + toTokenInfoData, + originalFromToken, + originalToToken, + routerClient, + SIMULATE_INIT_AMOUNT, + { + useAlphaSmartRoute: useAlphaSmartRouter + }, + isAIRoute + ); + + const fromAmountTokenBalance = + fromTokenInfoData && + toAmount(fromAmountToken, originalFromToken?.decimals || fromTokenInfoData?.decimals || CW20_DECIMALS); + + const { averageRatio } = getAverageRatio(simulateData, averageSimulateData, fromAmountToken, originalFromToken); + + const usdPriceShowFrom = (prices?.[originalFromToken?.coinGeckoId] * fromAmountToken).toFixed(6); + const usdPriceShowTo = (prices?.[originalToToken?.coinGeckoId] * simulateData?.displayAmount).toFixed(6); + + const isAverageRatio = averageRatio && averageRatio.amount; + const isSimulateDataDisplay = simulateData && simulateData.displayAmount; + const minimumReceive = + isAverageRatio && fromAmountTokenBalance + ? calculateMinReceive( + // @ts-ignore + new BigDecimal(averageRatio.amount).div(SIMULATE_INIT_AMOUNT).toString(), + fromAmountTokenBalance.toString(), + userSlippage, + originalFromToken.decimals + ) + : '0'; + const isWarningSlippage = +minimumReceive > +simulateData?.amount; + const simulateDisplayAmount = simulateData && simulateData.displayAmount ? simulateData.displayAmount : 0; + const bridgeTokenFee = + simulateDisplayAmount && (fromTokenFee || toTokenFee) + ? new BigDecimal(simulateDisplayAmount) + .mul(fromTokenFee) + .add(new BigDecimal(simulateDisplayAmount).mul(toTokenFee).toString()) + .div(100) + .toNumber() + : 0; + + const minimumReceiveDisplay = isSimulateDataDisplay + ? new BigDecimal(simulateDisplayAmount) + .sub(new BigDecimal(simulateDisplayAmount).mul(userSlippage).div(100).toString()) + .sub(relayerFee) + .sub(bridgeTokenFee) + .toNumber() + : 0; + + const expectOutputDisplay = isSimulateDataDisplay + ? numberWithCommas(simulateData.displayAmount, undefined, { minimumFractionDigits: 6 }) + : 0; + const estSwapFee = new BigDecimal(simulateDisplayAmount || 0).mul(fee || 0).toNumber(); + const totalFeeEst = new BigDecimal(bridgeTokenFee).add(relayerFee).add(estSwapFee).toNumber() || 0; + + return { + fees: { + estSwapFee, + isDependOnNetwork, + totalFeeEst, + bridgeTokenFee, + relayerFeeToken, + relayerFee, + fromTokenFee, + toTokenFee + }, + + outputs: { + expectOutputDisplay, + minimumReceiveDisplay, + isWarningSlippage, + minimumReceive + }, + + tokenInfos: { + fromAmountTokenBalance, + usdPriceShowFrom, + usdPriceShowTo + }, + + averageSimulateDatas: { + averageRatio, + averageSimulateData + }, + + simulateDatas: { + simulateData, + setSwapAmount, + fromAmountToken, + toAmountToken, + debouncedFromAmount, + isPreviousSimulate + } + }; +}; + +export default useCalculateDataSwap; diff --git a/src/pages/UniversalSwap/Swap/hooks/useHandleEffectTokenChange.ts b/src/pages/UniversalSwap/Swap/hooks/useHandleEffectTokenChange.ts new file mode 100644 index 000000000..fecac6bd0 --- /dev/null +++ b/src/pages/UniversalSwap/Swap/hooks/useHandleEffectTokenChange.ts @@ -0,0 +1,144 @@ +import { checkValidateAddressWithNetwork } from '@oraichain/oraidex-common'; +import { isMobile } from '@walletconnect/browser-utils'; +import { tokenMap } from 'config/bridgeTokens'; +import { getAddressTransfer, networks } from 'helper'; +import useConfigReducer from 'hooks/useConfigReducer'; +import useWalletReducer from 'hooks/useWalletReducer'; +import { genCurrentChain, generateNewSymbolV2, getFromToToken } from 'pages/UniversalSwap/helpers'; +import { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { + selectCurrentToChain, + selectCurrentToken, + setCurrentFromToken, + setCurrentToChain, + setCurrentToken, + setCurrentToToken +} from 'reducer/tradingSlice'; +import useFilteredTokens from './useFilteredTokens'; + +const useHandleEffectTokenChange = ({ fromTokenDenomSwap, toTokenDenomSwap }) => { + const dispatch = useDispatch(); + const currentPair = useSelector(selectCurrentToken); + const currentToChain = useSelector(selectCurrentToChain); + const [searchTokenName] = useState(''); + const [walletByNetworks] = useWalletReducer('walletsByNetwork'); + + const [metamaskAddress] = useConfigReducer('metamaskAddress'); + const [tronAddress] = useConfigReducer('tronAddress'); + const [oraiAddress] = useConfigReducer('address'); + + const [addressTransfer, setAddressTransfer] = useState(''); + const [initAddressTransfer, setInitAddressTransfer] = useState(''); + + // get token on oraichain to simulate swap amount. + const originalFromToken = tokenMap[fromTokenDenomSwap]; + const originalToToken = tokenMap[toTokenDenomSwap]; + + const { fromToken, toToken } = getFromToToken( + originalFromToken, + originalToToken, + fromTokenDenomSwap, + toTokenDenomSwap + ); + + const { filteredToTokens, filteredFromTokens } = useFilteredTokens( + originalFromToken, + originalToToken, + searchTokenName, + fromTokenDenomSwap, + toTokenDenomSwap + ); + + useEffect(() => { + (async () => { + if (!isMobile()) { + if (!walletByNetworks.evm && !walletByNetworks.cosmos && !walletByNetworks.tron) { + return setAddressTransfer(''); + } + + if (originalToToken.cosmosBased && !walletByNetworks.cosmos) { + return setAddressTransfer(''); + } + + if (!originalToToken.cosmosBased && originalToToken.chainId === '0x2b6653dc' && !walletByNetworks.tron) { + return setAddressTransfer(''); + } + + if (!originalToToken.cosmosBased && !walletByNetworks.evm) { + return setAddressTransfer(''); + } + } + + if (originalToToken.chainId) { + const findNetwork = networks.find((net) => net.chainId === originalToToken.chainId); + const address = await getAddressTransfer(findNetwork, walletByNetworks); + + setAddressTransfer(address); + setInitAddressTransfer(address); + } + })(); + }, [ + originalToToken, + oraiAddress, + metamaskAddress, + tronAddress, + walletByNetworks.evm, + walletByNetworks.cosmos, + walletByNetworks.tron, + window?.ethereumDapp, + window?.tronWebDapp + ]); + + useEffect(() => { + const newTVPair = generateNewSymbolV2(fromToken, toToken, currentPair); + + if (newTVPair) dispatch(setCurrentToken(newTVPair)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fromToken, toToken]); + + useEffect(() => { + const newCurrentToChain = genCurrentChain({ toToken: originalToToken, currentToChain }); + + if (toToken && originalToToken) { + dispatch(setCurrentToChain(newCurrentToChain)); + dispatch(setCurrentToToken(originalToToken)); + } + }, [originalToToken, toToken]); + + useEffect(() => { + if (fromToken && originalFromToken) { + dispatch(setCurrentFromToken(originalFromToken)); + } + }, [originalFromToken, fromToken]); + + const isConnectedWallet = + walletByNetworks.cosmos || walletByNetworks.bitcoin || walletByNetworks.evm || walletByNetworks.tron; + + let validAddress = { + isValid: true + }; + + if (isConnectedWallet) validAddress = checkValidateAddressWithNetwork(addressTransfer, originalToToken?.chainId); + + return { + originalFromToken, + originalToToken, + filteredToTokens, + filteredFromTokens, + searchTokenName, + fromToken, + toToken, + + addressInfo: { + addressTransfer, + initAddressTransfer, + setAddressTransfer, + setInitAddressTransfer + }, + validAddress, + isConnectedWallet + }; +}; + +export default useHandleEffectTokenChange; diff --git a/src/pages/UniversalSwap/Swap/index.tsx b/src/pages/UniversalSwap/Swap/index.tsx index eb4a4c535..4f0a50f93 100644 --- a/src/pages/UniversalSwap/Swap/index.tsx +++ b/src/pages/UniversalSwap/Swap/index.tsx @@ -1,23 +1,16 @@ import { BigDecimal, - CW20_DECIMALS, CosmosChainId, DEFAULT_SLIPPAGE, GAS_ESTIMATION_SWAP_DEFAULT, NetworkChainId, TRON_DENOM, TokenItemType, - calculateMinReceive, - checkValidateAddressWithNetwork, getTokenOnOraichain, - network, toAmount, toDisplay } from '@oraichain/oraidex-common'; -import { OraiswapRouterQueryClient } from '@oraichain/oraidex-contracts-sdk'; import { UniversalSwapHandler, UniversalSwapHelper } from '@oraichain/oraidex-universal-swap'; -import { useQuery } from '@tanstack/react-query'; -import { isMobile } from '@walletconnect/browser-utils'; import { ReactComponent as BookIcon } from 'assets/icons/book_icon.svg'; import DownArrowIcon from 'assets/icons/down-arrow-v2.svg'; import { ReactComponent as FeeIcon } from 'assets/icons/fee.svg'; @@ -36,23 +29,16 @@ import Loader from 'components/Loader'; import LoadingBox from 'components/LoadingBox'; import PowerByOBridge from 'components/PowerByOBridge'; import { TToastType, displayToast } from 'components/Toasts/Toast'; -import { flattenTokens, tokenMap } from 'config/bridgeTokens'; +import { flattenTokens } from 'config/bridgeTokens'; import { chainIcons } from 'config/chainInfos'; import { ethers } from 'ethers'; -import { - getAddressTransfer, - getSpecialCoingecko, - getTransactionUrl, - handleCheckAddress, - handleErrorTransaction, - networks -} from 'helper'; +import { getSpecialCoingecko, getTransactionUrl, handleCheckAddress, handleErrorTransaction } from 'helper'; import { useCoinGeckoPrices } from 'hooks/useCoingecko'; import useConfigReducer from 'hooks/useConfigReducer'; import { useCopyClipboard } from 'hooks/useCopyClipboard'; import useLoadTokens from 'hooks/useLoadTokens'; import useOnClickOutside from 'hooks/useOnClickOutside'; -import useTokenFee, { useGetFeeConfig, useRelayerFeeToken } from 'hooks/useTokenFee'; +import { useGetFeeConfig } from 'hooks/useTokenFee'; import useWalletReducer from 'hooks/useWalletReducer'; import Metamask from 'libs/metamask'; import { getUsd, reduceString, toSubAmount } from 'libs/utils'; @@ -60,47 +46,33 @@ import mixpanel from 'mixpanel-browser'; import { calcMaxAmount } from 'pages/Balance/helpers'; import { numberWithCommas } from 'pages/Pools/helpers'; import { - genCurrentChain, - generateNewSymbolV2, getDisableSwap, - getFromToToken, getPathInfo, - getRemoteDenom, getTokenBalance, getTokenInfo, isAllowAlphaSmartRouter, refreshBalances } from 'pages/UniversalSwap/helpers'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { selectCurrentAddressBookStep, setCurrentAddressBookStep } from 'reducer/addressBook'; -import { - selectCurrentToChain, - selectCurrentToken, - setCurrentFromToken, - setCurrentToChain, - setCurrentToToken, - setCurrentToken -} from 'reducer/tradingSlice'; import { AddressManagementStep } from 'reducer/type'; -import { fetchTokenInfos } from 'rest/api'; import { RootState } from 'store/configure'; import { SlippageModal } from '../Modals'; +import { SmartRouteModal } from '../Modals/SmartRouteModal'; import { checkEvmAddress, getSwapType } from '../helpers'; +import AIRouteSwitch from './components/AIRouteSwitch/AIRouteSwitch'; import AddressBook from './components/AddressBook'; import InputCommon from './components/InputCommon'; import InputSwap from './components/InputSwap/InputSwap'; -import SelectChain from './components/SelectChain/SelectChain'; -import SelectToken from './components/SelectToken/SelectToken'; import SwapDetail from './components/SwapDetail'; +import TokenAndChainSelectors from './components/TokenAndChainSelectors'; import { TooltipSwapBridge } from './components/TooltipSwapBridge'; -import { useGetTransHistory, useSimulate } from './hooks'; +import { useGetTransHistory } from './hooks'; +import useCalculateDataSwap, { SIMULATE_INIT_AMOUNT } from './hooks/useCalculateDataSwap'; import { useFillToken } from './hooks/useFillToken'; -import useFilteredTokens from './hooks/useFilteredTokens'; -import { useSwapFee } from './hooks/useSwapFee'; +import useHandleEffectTokenChange from './hooks/useHandleEffectTokenChange'; import styles from './index.module.scss'; -import { SmartRouteModal } from '../Modals/SmartRouteModal'; -import AIRouteSwitch from './components/AIRouteSwitch/AIRouteSwitch'; const cx = cn.bind(styles); // TODO: hardcode decimal relayerFee @@ -111,75 +83,111 @@ const SwapComponent: React.FC<{ toTokenDenom: string; setSwapTokens: (denoms: [string, string]) => void; }> = ({ fromTokenDenom, toTokenDenom, setSwapTokens }) => { + // store value + const [isAIRoute] = useConfigReducer('AIRoute'); + const [metamaskAddress] = useConfigReducer('metamaskAddress'); + const [tronAddress] = useConfigReducer('tronAddress'); + const [oraiAddress] = useConfigReducer('address'); + const [walletByNetworks] = useWalletReducer('walletsByNetwork'); + const [theme] = useConfigReducer('theme'); + const isLightMode = theme === 'light'; + const currentAddressManagementStep = useSelector(selectCurrentAddressBookStep); + const amounts = useSelector((state: RootState) => state.token.amounts); + const dispatch = useDispatch(); + + const loadTokenAmounts = useLoadTokens(); + const { refetchTransHistory } = useGetTransHistory(); const { handleUpdateQueryURL } = useFillToken(setSwapTokens); + const { handleReadClipboard } = useCopyClipboard(); + + // info token state const [openDetail, setOpenDetail] = useState(false); const [openRoutes, setOpenRoutes] = useState(false); - const [fromTokenDenomSwap, setFromTokenDenom] = useState(fromTokenDenom); const [toTokenDenomSwap, setToTokenDenom] = useState(toTokenDenom); - // get token on oraichain to simulate swap amount. - const originalFromToken = tokenMap[fromTokenDenomSwap]; - const originalToToken = tokenMap[toTokenDenomSwap]; - - const { data: prices } = useCoinGeckoPrices(); - - const [selectChainFrom, setSelectChainFrom] = useState( - originalFromToken?.chainId || ('OraiChain' as NetworkChainId) - ); - const [selectChainTo, setSelectChainTo] = useState( - originalToToken?.chainId || ('OraiChain' as NetworkChainId) - ); - + // modal state const [isSelectChainFrom, setIsSelectChainFrom] = useState(false); const [isSelectChainTo, setIsSelectChainTo] = useState(false); - const [isSelectFrom, setIsSelectFrom] = useState(false); - const [isSelectTo, setIsSelectTo] = useState(false); + const [isSelectTokenFrom, setIsSelectTokenFrom] = useState(false); + const [isSelectTokenTo, setIsSelectTokenTo] = useState(false); const [openSetting, setOpenSetting] = useState(false); - const [openSmartRoute, setOpenSmartRoute] = useState(false); const [indSmartRoute, setIndSmartRoute] = useState([0, 0]); - const [userSlippage, setUserSlippage] = useState(DEFAULT_SLIPPAGE); + + // value state const [coe, setCoe] = useState(0); - const [searchTokenName] = useState(''); + + // loading state const [swapLoading, setSwapLoading] = useState(false); const [loadingRefresh, setLoadingRefresh] = useState(false); - const amounts = useSelector((state: RootState) => state.token.amounts); - const [metamaskAddress] = useConfigReducer('metamaskAddress'); - const [tronAddress] = useConfigReducer('tronAddress'); - const [oraiAddress] = useConfigReducer('address'); - const [theme] = useConfigReducer('theme'); - const isLightMode = theme === 'light'; - const loadTokenAmounts = useLoadTokens(); - const dispatch = useDispatch(); - const currentPair = useSelector(selectCurrentToken); - const currentToChain = useSelector(selectCurrentToChain); - const { refetchTransHistory } = useGetTransHistory(); - const [walletByNetworks] = useWalletReducer('walletsByNetwork'); - const [addressTransfer, setAddressTransfer] = useState(''); - const [initAddressTransfer, setInitAddressTransfer] = useState(''); - const currentAddressManagementStep = useSelector(selectCurrentAddressBookStep); - const { handleReadClipboard } = useCopyClipboard(); - - const { fromToken, toToken } = getFromToToken( + const { originalFromToken, originalToToken, - fromTokenDenomSwap, - toTokenDenomSwap + filteredToTokens, + filteredFromTokens, + fromToken, + toToken, + addressInfo, + validAddress + } = useHandleEffectTokenChange({ fromTokenDenomSwap, toTokenDenomSwap }); + const { addressTransfer, initAddressTransfer, setAddressTransfer } = addressInfo; + + const [selectChainFrom, setSelectChainFrom] = useState( + originalFromToken?.chainId || ('OraiChain' as NetworkChainId) + ); + const [selectChainTo, setSelectChainTo] = useState( + originalToToken?.chainId || ('OraiChain' as NetworkChainId) ); - const remoteTokenDenomFrom = getRemoteDenom(originalFromToken); - const remoteTokenDenomTo = getRemoteDenom(originalToToken); - const fromTokenFee = useTokenFee(remoteTokenDenomFrom, fromToken.chainId, toToken.chainId); - const toTokenFee = useTokenFee(remoteTokenDenomTo, fromToken.chainId, toToken.chainId); + // hooks + useGetFeeConfig(); + const { data: prices } = useCoinGeckoPrices(); + const { fees, outputs, tokenInfos, simulateDatas, averageSimulateDatas } = useCalculateDataSwap({ + originalFromToken, + originalToToken, + fromToken, + toToken, + userSlippage + }); + + const { + estSwapFee, + isDependOnNetwork, + totalFeeEst, + bridgeTokenFee, + relayerFeeToken, + relayerFee, + fromTokenFee, + toTokenFee + } = fees; + const { expectOutputDisplay, minimumReceiveDisplay, isWarningSlippage } = outputs; + const { fromAmountTokenBalance, usdPriceShowFrom, usdPriceShowTo } = tokenInfos; + const { averageRatio, averageSimulateData } = averageSimulateDatas; + const { simulateData, setSwapAmount, fromAmountToken, toAmountToken, debouncedFromAmount, isPreviousSimulate } = + simulateDatas; const subAmountFrom = toSubAmount(amounts, originalFromToken); const subAmountTo = toSubAmount(amounts, originalToToken); - const INIT_AMOUNT = 1; - useGetFeeConfig(); + const fromTokenBalance = getTokenBalance(originalFromToken, amounts, subAmountFrom); + const toTokenBalance = getTokenBalance(originalToToken, amounts, subAmountTo); + + const useAlphaSmartRouter = isAllowAlphaSmartRouter(originalFromToken, originalToToken) && isAIRoute; + + const settingRef = useRef(); + const smartRouteRef = useRef(); + + useOnClickOutside(settingRef, () => { + setOpenSetting(false); + }); + + useOnClickOutside(smartRouteRef, () => { + setOpenSmartRoute(false); + setIndSmartRoute([0, 0]); + }); const onChangeFromAmount = (amount: number | undefined) => { if (!amount) { @@ -194,65 +202,6 @@ const SwapComponent: React.FC<{ setSwapAmount([displayAmount, toAmountToken]); }; - const { - data: [fromTokenInfoData, toTokenInfoData] - } = useQuery(['token-infos', fromToken, toToken], () => fetchTokenInfos([fromToken!, toToken!]), { initialData: [] }); - - const fromTokenBalance = getTokenBalance(originalFromToken, amounts, subAmountFrom); - const toTokenBalance = getTokenBalance(originalToToken, amounts, subAmountTo); - - const [isAIRoute] = useConfigReducer('AIRoute'); - const useAlphaSmartRouter = isAllowAlphaSmartRouter(originalFromToken, originalToToken) && isAIRoute; - const routerClient = new OraiswapRouterQueryClient(window.client, network.router); - const { simulateData, setSwapAmount, fromAmountToken, toAmountToken, debouncedFromAmount, isPreviousSimulate } = - useSimulate( - 'simulate-data', - fromTokenInfoData, - toTokenInfoData, - originalFromToken, - originalToToken, - routerClient, - null, - { - useAlphaSmartRoute: useAlphaSmartRouter - }, - isAIRoute - ); - - const { simulateData: averageSimulateData } = useSimulate( - 'average-simulate-data', - fromTokenInfoData, - toTokenInfoData, - originalFromToken, - originalToToken, - routerClient, - INIT_AMOUNT, - { - useAlphaSmartRoute: useAlphaSmartRouter - }, - isAIRoute - ); - - let averageRatio = undefined; - if (simulateData && fromAmountToken) { - const displayAmount = new BigDecimal(simulateData.displayAmount).div(fromAmountToken).toNumber(); - averageRatio = { - amount: toAmount(displayAmount ? displayAmount : averageSimulateData?.displayAmount, originalFromToken.decimals), - displayAmount: displayAmount ? displayAmount : averageSimulateData?.displayAmount ?? 0 - }; - } - - const usdPriceShowFrom = (prices?.[originalFromToken?.coinGeckoId] * fromAmountToken).toFixed(6); - const usdPriceShowTo = (prices?.[originalToToken?.coinGeckoId] * simulateData?.displayAmount).toFixed(6); - - const { filteredToTokens, filteredFromTokens } = useFilteredTokens( - originalFromToken, - originalToToken, - searchTokenName, - fromTokenDenomSwap, - toTokenDenomSwap - ); - const setTokenDenomFromChain = (chainId: string, type: 'from' | 'to') => { if (chainId) { const isFrom = type === 'from'; @@ -278,79 +227,6 @@ const SwapComponent: React.FC<{ } }; - const { fee, isDependOnNetwork } = useSwapFee({ - fromToken: originalFromToken, - toToken: originalToToken - }); - - const { relayerFee, relayerFeeInOraiToAmount: relayerFeeToken } = useRelayerFeeToken( - originalFromToken, - originalToToken - ); - - useEffect(() => { - const newTVPair = generateNewSymbolV2(fromToken, toToken, currentPair); - - if (newTVPair) dispatch(setCurrentToken(newTVPair)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fromToken, toToken]); - - useEffect(() => { - const newCurrentToChain = genCurrentChain({ toToken: originalToToken, currentToChain }); - - if (toToken && originalToToken) { - dispatch(setCurrentToChain(newCurrentToChain)); - dispatch(setCurrentToToken(originalToToken)); - } - }, [originalToToken, toToken]); - - useEffect(() => { - if (fromToken && originalFromToken) { - dispatch(setCurrentFromToken(originalFromToken)); - } - }, [originalFromToken, fromToken]); - - const fromAmountTokenBalance = - fromTokenInfoData && - toAmount(fromAmountToken, originalFromToken?.decimals || fromTokenInfoData?.decimals || CW20_DECIMALS); - - const isAverageRatio = averageRatio && averageRatio.amount; - const isSimulateDataDisplay = simulateData && simulateData.displayAmount; - const minimumReceive = - isAverageRatio && fromAmountTokenBalance - ? calculateMinReceive( - // @ts-ignore - new BigDecimal(averageRatio.amount).div(INIT_AMOUNT).toString(), - fromAmountTokenBalance.toString(), - userSlippage, - originalFromToken.decimals - ) - : '0'; - const isWarningSlippage = +minimumReceive > +simulateData?.amount; - const simulateDisplayAmount = simulateData && simulateData.displayAmount ? simulateData.displayAmount : 0; - const bridgeTokenFee = - simulateDisplayAmount && (fromTokenFee || toTokenFee) - ? new BigDecimal(simulateDisplayAmount) - .mul(fromTokenFee) - .add(new BigDecimal(simulateDisplayAmount).mul(toTokenFee).toString()) - .div(100) - .toNumber() - : 0; - - const minimumReceiveDisplay = isSimulateDataDisplay - ? new BigDecimal(simulateDisplayAmount) - .sub(new BigDecimal(simulateDisplayAmount).mul(userSlippage).div(100).toString()) - .sub(relayerFee) - .sub(bridgeTokenFee) - .toNumber() - : 0; - - const expectOutputDisplay = isSimulateDataDisplay - ? numberWithCommas(simulateData.displayAmount, undefined, { minimumFractionDigits: 6 }) - : 0; - const estSwapFee = new BigDecimal(simulateDisplayAmount || 0).mul(fee || 0).toNumber(); - const totalFeeEst = new BigDecimal(bridgeTokenFee).add(relayerFee).add(estSwapFee).toNumber() || 0; - const handleSubmit = async () => { if (fromAmountToken <= 0) return displayToast(TToastType.TX_FAILED, { @@ -421,7 +297,7 @@ const SwapComponent: React.FC<{ amounts: amountsBalance, simulatePrice: // @ts-ignore - averageRatio?.amount && new BigDecimal(averageRatio.amount).div(INIT_AMOUNT).toString(), + averageRatio?.amount && new BigDecimal(averageRatio.amount).div(SIMULATE_INIT_AMOUNT).toString(), relayerFee: relayerFeeUniversal, alphaSmartRoutes }; @@ -501,66 +377,6 @@ const SwapComponent: React.FC<{ } }; - useEffect(() => { - (async () => { - if (!isMobile()) { - if (!walletByNetworks.evm && !walletByNetworks.cosmos && !walletByNetworks.tron) { - return setAddressTransfer(''); - } - - if (originalToToken.cosmosBased && !walletByNetworks.cosmos) { - return setAddressTransfer(''); - } - - if (!originalToToken.cosmosBased && originalToToken.chainId === '0x2b6653dc' && !walletByNetworks.tron) { - return setAddressTransfer(''); - } - - if (!originalToToken.cosmosBased && !walletByNetworks.evm) { - return setAddressTransfer(''); - } - } - - if (originalToToken.chainId) { - const findNetwork = networks.find((net) => net.chainId === originalToToken.chainId); - const address = await getAddressTransfer(findNetwork, walletByNetworks); - - setAddressTransfer(address); - setInitAddressTransfer(address); - } - })(); - }, [ - originalToToken, - oraiAddress, - metamaskAddress, - tronAddress, - walletByNetworks.evm, - walletByNetworks.cosmos, - walletByNetworks.tron, - window?.ethereumDapp, - window?.tronWebDapp - ]); - - const ref = useRef(null); - useOnClickOutside(ref, () => { - setIsSelectFrom(false); - setIsSelectTo(false); - setIsSelectChainFrom(false); - setIsSelectChainTo(false); - }); - - const settingRef = useRef(); - const smartRouteRef = useRef(); - - useOnClickOutside(settingRef, () => { - setOpenSetting(false); - }); - - useOnClickOutside(smartRouteRef, () => { - setOpenSmartRoute(false); - setIndSmartRoute([0, 0]); - }); - const onChangePercentAmount = (coeff) => { if (coeff === coe) { setCoe(0); @@ -582,7 +398,7 @@ const SwapComponent: React.FC<{ const handleChangeToken = (token: TokenItemType, type) => { const isFrom = type === 'from'; const setSelectChain = isFrom ? setSelectChainFrom : setSelectChainTo; - const setIsSelect = isFrom ? setIsSelectFrom : setIsSelectTo; + const setIsSelect = isFrom ? setIsSelectTokenFrom : setIsSelectTokenTo; if (token.denom === (isFrom ? toTokenDenomSwap : fromTokenDenomSwap)) { setFromTokenDenom(toTokenDenomSwap); @@ -633,15 +449,6 @@ const SwapComponent: React.FC<{ handleUpdateQueryURL([toTokenDenomSwap, fromTokenDenomSwap]); }; - const isConnectedWallet = - walletByNetworks.cosmos || walletByNetworks.bitcoin || walletByNetworks.evm || walletByNetworks.tron; - - let validAddress = { - isValid: true - }; - - if (isConnectedWallet) validAddress = checkValidateAddressWithNetwork(addressTransfer, originalToToken?.chainId); - const defaultRouterSwap = { amount: '0', displayAmount: 0, @@ -711,7 +518,7 @@ const SwapComponent: React.FC<{ theme={theme} onChangePercentAmount={onChangePercentAmount} setIsSelectChain={setIsSelectChainFrom} - setIsSelectToken={setIsSelectFrom} + setIsSelectToken={setIsSelectTokenFrom} selectChain={selectChainFrom} token={originalFromToken} amount={fromAmountToken} @@ -755,11 +562,11 @@ const SwapComponent: React.FC<{ {Number(impactWarning) > 5 && } {`1 ${originalFromToken.name} ≈ ${ averageRatio - ? numberWithCommas(averageRatio.displayAmount / INIT_AMOUNT, undefined, { + ? numberWithCommas(averageRatio.displayAmount / SIMULATE_INIT_AMOUNT, undefined, { maximumFractionDigits: 6 }) : averageSimulateData - ? numberWithCommas(averageSimulateData?.displayAmount / INIT_AMOUNT, undefined, { + ? numberWithCommas(averageSimulateData?.displayAmount / SIMULATE_INIT_AMOUNT, undefined, { maximumFractionDigits: 6 }) : '0' @@ -845,7 +652,7 @@ const SwapComponent: React.FC<{ disable={true} selectChain={selectChainTo} setIsSelectChain={setIsSelectChainTo} - setIsSelectToken={setIsSelectTo} + setIsSelectToken={setIsSelectTokenTo} token={originalToToken} amount={toAmountToken} tokenFee={toTokenFee} @@ -952,57 +759,29 @@ const SwapComponent: React.FC<{ -
- { - handleChangeToken(token, 'to'); - }} - items={filteredToTokens} - theme={theme} - selectChain={selectChainTo} - isSelectToken={isSelectTo} - /> - { - handleChangeToken(token, 'from'); - }} - isSelectToken={isSelectFrom} - /> - { - setSelectChainTo(chain); - setTokenDenomFromChain(chain, 'to'); - }} - prices={prices} - isSelectToken={isSelectChainTo} - /> - { - setSelectChainFrom(chain); - setTokenDenomFromChain(chain, 'from'); - }} - isSelectToken={isSelectChainFrom} - /> -
+ { @@ -1029,7 +808,7 @@ const SwapComponent: React.FC<{
{openSmartRoute && [routersSwapData?.routes[indSmartRoute[0]]?.paths[indSmartRoute[1]]].map((path) => { - if (!path) return; + if (!path) return null; const { NetworkFromIcon, NetworkToIcon, assetList, pathChainId } = getPathInfo( path, chainIcons, @@ -1041,7 +820,7 @@ const SwapComponent: React.FC<{ const tokenOutChainId = path.tokenOutChainId; const hasTypeConvert = actions.find((act) => act.type === 'Convert'); const width = hasTypeConvert ? actions.length - 1 : actions.length; - if (action.type === 'Convert') return; + if (action.type === 'Convert') return null; return (
{ return false; }; +export const getAverageRatio = (simulateData, averageSimulateData, fromAmountToken, originalFromToken) => { + let averageRatio = undefined; + if (simulateData && fromAmountToken) { + const displayAmount = new BigDecimal(simulateData.displayAmount).div(fromAmountToken).toNumber(); + averageRatio = { + amount: toAmount(displayAmount ? displayAmount : averageSimulateData?.displayAmount, originalFromToken.decimals), + displayAmount: displayAmount ? displayAmount : averageSimulateData?.displayAmount ?? 0 + }; + } + return { averageRatio }; +}; + export const findKeyByValue = (obj, value: string) => Object.keys(obj).find((key) => obj[key] === value); const findTokenInfo = (token: string, flattenTokens: TokenItemType[]): TokenItemType => diff --git a/yarn.lock b/yarn.lock index 51eecb489..e785d6bf8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3099,10 +3099,10 @@ react-use "^17.4.0" react-use-websocket "^4.5.0" -"@oraichain/oraidex-common@latest": - version "1.0.89" - resolved "https://registry.yarnpkg.com/@oraichain/oraidex-common/-/oraidex-common-1.0.89.tgz#f6b51dc00bfc6bb5c8d37f710d84666e343ecd92" - integrity sha512-kqloc/PdeI8PXmN4NBAEpQY8zU9DlHe5uwjrnD4dBS9ybmAr3VdmpCsuyAo4icEZ+OYGrg7PISsWH/NleU8exg== +"@oraichain/oraidex-common@^1.0.91": + version "1.0.92" + resolved "https://registry.yarnpkg.com/@oraichain/oraidex-common/-/oraidex-common-1.0.92.tgz#5d0d3958d81cac5b7404ef5a61aa4980371853ce" + integrity sha512-/Y5XplB6ddJysKCB6dZQifwCw1GwK7dyrsp8qM1ZWn/k4qDeyJRnANKQ1bRNzCfyvQXqRgi43NgqDDa4pHufag== dependencies: "@cosmjs/amino" "0.31.3" "@cosmjs/cosmwasm-stargate" "0.31.3" @@ -3140,12 +3140,12 @@ resolved "https://registry.yarnpkg.com/@oraichain/oraidex-contracts-sdk/-/oraidex-contracts-sdk-1.0.44.tgz#9ff41ec388dd92ba112c2eef545d11fd6e18c684" integrity sha512-fMY/QOzO/afPlAVkLGH1bcpJTs6V/URrJju2r4UoE9f5HUWC/6OC6+H5dWuIgq26frp8wjH23mNCQAlmHzji8g== -"@oraichain/oraidex-universal-swap@1.0.91": - version "1.0.91" - resolved "https://registry.yarnpkg.com/@oraichain/oraidex-universal-swap/-/oraidex-universal-swap-1.0.91.tgz#9819f4554d4e4319e743125259f985c81dc897d7" - integrity sha512-mMjvMjSgAhvBTce5w7uXktlZ0Enrag26Hz9n0GmeLc5xKweRYBknn3OyHbjKLleXFT8eAZYfe1bMgt52nGNDaA== +"@oraichain/oraidex-universal-swap@1.0.93": + version "1.0.93" + resolved "https://registry.yarnpkg.com/@oraichain/oraidex-universal-swap/-/oraidex-universal-swap-1.0.93.tgz#943f26f3971d7aa2d1a7d726c2361faf1476d9c0" + integrity sha512-BUWBi9uVceWRmJY/kvgbsggLThKMG8eCMKDDrP4EAmJ/IhKSRJd5MWYtny/OeFN6w0dHgBv78DPJuMALDE+xpA== dependencies: - "@oraichain/oraidex-common" latest + "@oraichain/oraidex-common" "^1.0.91" "@oraichain/oraidex-contracts-sdk" "^1.0.43" bech32 "1.1.4" ethers "^5.0.15"