From 3daac0cfcfe68a98808a3be864b06e4b117209b0 Mon Sep 17 00:00:00 2001 From: trungbach Date: Sun, 12 Jan 2025 19:48:35 +0700 Subject: [PATCH 1/2] Refactor token verification logic and enhance pool liquidity handling --- src/helper/index.tsx | 20 +++++++- src/initCommon.ts | 4 +- src/layouts/App.tsx | 15 +++--- src/libs/contractSingleton.ts | 1 + .../Pool-V3/components/PoolList/index.tsx | 18 ++----- .../hooks/useGetPoolLiquidityVolume.ts | 25 +++++----- src/pages/Pool-V3/hooks/useGetPoolList.ts | 48 +++++++++---------- 7 files changed, 71 insertions(+), 60 deletions(-) diff --git a/src/helper/index.tsx b/src/helper/index.tsx index a6b897174..458ef51dc 100644 --- a/src/helper/index.tsx +++ b/src/helper/index.tsx @@ -1,7 +1,7 @@ import { fromBech32, toBech32 } from '@cosmjs/encoding'; import { Bech32Config } from '@keplr-wallet/types'; import { getSnap } from '@leapwallet/cosmos-snap-provider'; -import { CosmosChainId, CustomChainInfo, NetworkChainId } from '@oraichain/common'; +import { CosmosChainId, CustomChainInfo, fetchRetry, NetworkChainId } from '@oraichain/common'; import { BigDecimal, BSC_SCAN, @@ -35,6 +35,7 @@ import { serializeError } from 'serialize-error'; import { store } from 'store/configure'; import { bitcoinChainId, leapSnapId } from './constants'; import { numberWithCommas } from './format'; +import { onChainTokenToTokenItem } from 'reducer/onchainTokens'; export interface Tokens { denom?: string; @@ -748,3 +749,20 @@ export const retry = async (fn, retries = 3, delay = 1000) => { return retry(fn, retries - 1, delay); } }; + +export const inspectTokenFromOraiCommonApi = async (denoms: string[]): Promise => { + try { + const BASE_URL = 'https://oraicommon.oraidex.io/api/v1/tokens/list'; + const TOKEN_LIST = denoms.map(encodeURIComponent).join(','); + const URL = `${BASE_URL}/${TOKEN_LIST}`; + const response = await fetchRetry(URL); + if (response.status !== 200) throw new Error('Failed to fetch token list: ' + response.statusText); + + const inspectedTokens = await response.json(); + const tokenItemTypes = inspectedTokens.map((info) => onChainTokenToTokenItem(info)); + return tokenItemTypes; + } catch (error) { + console.log('Error inspectTokenFromOraiCommonApi: ', error); + return []; + } +}; diff --git a/src/initCommon.ts b/src/initCommon.ts index f94f2d76a..276328955 100644 --- a/src/initCommon.ts +++ b/src/initCommon.ts @@ -23,9 +23,7 @@ export const initializeOraidexCommon = async ( const oraichainTokens = oraidexCommonOg.oraichainTokens; const otherChainTokens = oraidexCommonOg.otherChainTokens; - const allVerifiedOraichainTokens = allOraichainTokens.filter( - (token) => !addedTokens.find((addedToken) => addedToken.denom === token.denom) - ); + const allVerifiedOraichainTokens = allOraichainTokens.filter((token) => token.isVerified); if (arraysAreDifferent(oraichainTokens, allVerifiedOraichainTokens)) { dispatch(updateAllOraichainTokens([...oraichainTokens, ...addedTokens])); } diff --git a/src/layouts/App.tsx b/src/layouts/App.tsx index 4df9c3ddd..534e7a8c1 100644 --- a/src/layouts/App.tsx +++ b/src/layouts/App.tsx @@ -3,15 +3,18 @@ import { WEBSOCKET_RECONNECT_ATTEMPTS, WEBSOCKET_RECONNECT_INTERVAL } from '@oraichain/oraidex-common'; +import { useWallet } from '@solana/wallet-adapter-react'; import { useTonConnectUI } from '@tonconnect/ui-react'; import { isMobile } from '@walletconnect/browser-utils'; -import { TToastType, displayToast } from 'components/Toasts/Toast'; -import { useWallet } from '@solana/wallet-adapter-react'; +import { displayToast, TToastType } from 'components/Toasts/Toast'; import { ThemeProvider } from 'context/theme-context'; import { TonNetwork } from 'context/ton-provider'; import { getListAddressCosmos, getWalletByNetworkFromStorage, interfaceRequestTron, retry } from 'helper'; import useConfigReducer from 'hooks/useConfigReducer'; +import useLoadTokens from 'hooks/useLoadTokens'; +import { useTronEventListener } from 'hooks/useTronLink'; import useWalletReducer from 'hooks/useWalletReducer'; +import { initializeOraidexCommon, network } from 'initCommon'; import SingletonOraiswapV3 from 'libs/contractSingleton'; import { getCosmWasmClient } from 'libs/cosmjs'; import Keplr from 'libs/keplr'; @@ -19,18 +22,15 @@ import Metamask from 'libs/metamask'; import { buildUnsubscribeMessage, buildWebsocketSendMessage, processWsResponseMsg } from 'libs/utils'; import { useLoadWalletsTon } from 'pages/Balance/hooks/useLoadWalletsTon'; import { useEffect, useState } from 'react'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { useDispatch, useSelector } from 'react-redux'; import useWebSocket from 'react-use-websocket'; import { setAddressBookList } from 'reducer/addressBook'; +import routes from 'routes'; import { persistor, RootState } from 'store/configure'; import { ADDRESS_BOOK_KEY_BACKUP, PERSIST_VER } from 'store/constants'; import './index.scss'; -import { initializeOraidexCommon, network } from 'initCommon'; -import useLoadTokens from 'hooks/useLoadTokens'; -import { useTronEventListener } from 'hooks/useTronLink'; import Menu from './Menu'; - -import routes from 'routes'; import { NoticeBanner } from './NoticeBanner'; import Sidebar from './Sidebar'; @@ -389,6 +389,7 @@ const App = () => {
{/* */} + {/* {(!bannerTime || Date.now() > bannerTime + 86_400_000) && } */}
diff --git a/src/libs/contractSingleton.ts b/src/libs/contractSingleton.ts index f3ba94b86..16eee1363 100644 --- a/src/libs/contractSingleton.ts +++ b/src/libs/contractSingleton.ts @@ -764,6 +764,7 @@ export function simulateIncentiveAprPosition( const allOraichainTokens = storage.token.allOraichainTokens || []; const tokenX = allOraichainTokens.find((token) => extractAddress(token) === poolKey.token_x); const tokenY = allOraichainTokens.find((token) => extractAddress(token) === poolKey.token_y); + if (!tokenX || !tokenY) return { min: 0, max: 0 }; const positionLiquidityUsdX = ((prices[tokenX?.coinGeckoId] ?? 0) * Number(res.x)) / 10 ** tokenX.decimals; const positionLiquidityUsdY = ((prices[tokenY?.coinGeckoId] ?? 0) * Number(res.y)) / 10 ** tokenY.decimals; diff --git a/src/pages/Pool-V3/components/PoolList/index.tsx b/src/pages/Pool-V3/components/PoolList/index.tsx index ea5031855..ef04ce9e6 100644 --- a/src/pages/Pool-V3/components/PoolList/index.tsx +++ b/src/pages/Pool-V3/components/PoolList/index.tsx @@ -45,7 +45,7 @@ export enum PoolColumnHeader { const PoolList = ({ search, filterType }: { search: string; filterType: POOL_TYPE }) => { const theme = useTheme(); const { data: price } = useCoinGeckoPrices(); - const [, setLiquidityPools] = useConfigReducer('liquidityPools'); + const [poolLiquiditiesFromCache, setLiquidityPools] = useConfigReducer('liquidityPools'); const [volumnePools, setVolumnePools] = useConfigReducer('volumnePools'); const [aprInfo, setAprInfo] = useConfigReducer('aprPools'); const [openTooltip, setOpenTooltip] = useState(false); @@ -137,7 +137,6 @@ const PoolList = ({ search, filterType }: { search: string; filterType: POOL_TYP useEffect(() => { if (Object.values(poolLiquidities).length > 0) { - const totalLiqudity = Object.values(poolLiquidities).reduce((acc, cur) => acc + cur, 0); setLiquidityPools(poolLiquidities); } }, [poolLiquidities, dataPool]); @@ -153,7 +152,9 @@ const PoolList = ({ search, filterType }: { search: string; filterType: POOL_TYP const { type, totalLiquidity: liquidityV2, volume24Hour: volumeV2, liquidityAddr, poolKey } = item || {}; const showLiquidity = - type === POOL_TYPE.V3 ? poolLiquidities?.[item?.poolKey] : toDisplay(Math.trunc(liquidityV2 || 0).toString()); + type === POOL_TYPE.V3 + ? poolLiquiditiesFromCache?.[item?.poolKey] + : toDisplay(Math.trunc(liquidityV2 || 0).toString()); const showVolume = type === POOL_TYPE.V3 ? volumeV3 : toDisplay(volumeV2 || 0); let showApr = aprInfo?.[poolKey] || aprInfo?.[liquidityAddr] || {}; @@ -412,16 +413,7 @@ const PoolList = ({ search, filterType }: { search: string; filterType: POOL_TYP
-
- {filteredPool?.length > 0 - ? renderList() - : !loading && ( -
- {theme === 'light' ? : } - {!dataPool.length ? 'No Pools!' : !filteredPool.length && 'No Matched Pools!'} -
- )} -
+
{filteredPool?.length > 0 && renderList()}
{totalPages > 1 && ( diff --git a/src/pages/Pool-V3/hooks/useGetPoolLiquidityVolume.ts b/src/pages/Pool-V3/hooks/useGetPoolLiquidityVolume.ts index b3fa780a5..021f7cff2 100644 --- a/src/pages/Pool-V3/hooks/useGetPoolLiquidityVolume.ts +++ b/src/pages/Pool-V3/hooks/useGetPoolLiquidityVolume.ts @@ -16,7 +16,8 @@ export const useGetPoolLiquidityVolume = (prices: CoinGeckoPrices) => { () => getPoolsLiqudityAndVolumeAmount(), { refetchOnWindowFocus: false, - placeholderData: [] + placeholderData: [], + cacheTime: 5 * 60 * 1000 } ); @@ -24,7 +25,7 @@ export const useGetPoolLiquidityVolume = (prices: CoinGeckoPrices) => { ['pool-v3-liquidty-volume-hourly', prices], getPoolsVolumeByTokenLatest24h, { - refetchOnWindowFocus: true, + refetchOnWindowFocus: false, placeholderData: [], cacheTime: 5 * 60 * 1000 } @@ -32,13 +33,13 @@ export const useGetPoolLiquidityVolume = (prices: CoinGeckoPrices) => { useEffect(() => { if (data.length === 0 || Object.keys(prices).length === 0 || dataHours.length === 0) return; + const newPoolLiquidities: Record = {}; + const newPoolVolumes: Record = {}; + data.forEach((item) => { - setPoolLiquidities((prevState) => ({ - ...prevState, - [item.id]: - (item.totalValueLockedTokenX / 10 ** item.tokenX.decimals) * (prices[item.tokenX.coingeckoId] ?? 0) + - (item.totalValueLockedTokenY / 10 ** item.tokenY.decimals) * (prices[item.tokenY.coingeckoId] ?? 0) - })); + newPoolLiquidities[item.id] = + (item.totalValueLockedTokenX / 10 ** item.tokenX.decimals) * (prices[item.tokenX.coingeckoId] ?? 0) + + (item.totalValueLockedTokenY / 10 ** item.tokenY.decimals) * (prices[item.tokenY.coingeckoId] ?? 0); let poolVolumeInUsd = 0; const poolHourData = dataHours.find((dataHour) => dataHour.id === item.id); @@ -54,11 +55,11 @@ export const useGetPoolLiquidityVolume = (prices: CoinGeckoPrices) => { (Number(volume24hByToken.volumeTokenY) / 10 ** item.tokenY.decimals) * (prices[item.tokenY.coingeckoId] ?? 0); } - setPoolVolume((prevState) => ({ - ...prevState, - [item.id]: poolVolumeInUsd - })); + newPoolVolumes[item.id] = poolVolumeInUsd; }); + + setPoolLiquidities(newPoolLiquidities); + setPoolVolume(newPoolVolumes); }, [data, prices, dataHours]); return { diff --git a/src/pages/Pool-V3/hooks/useGetPoolList.ts b/src/pages/Pool-V3/hooks/useGetPoolList.ts index d7794bce9..ec5151fb5 100644 --- a/src/pages/Pool-V3/hooks/useGetPoolList.ts +++ b/src/pages/Pool-V3/hooks/useGetPoolList.ts @@ -2,32 +2,35 @@ import { parseAssetInfo } from '@oraichain/oraidex-common'; import { PoolWithPoolKey } from '@oraichain/oraidex-contracts-sdk/build/OraiswapV3.types'; import { useQuery } from '@tanstack/react-query'; import { CoinGeckoPrices } from 'hooks/useCoingecko'; -import { getTokenInspectorInstance } from 'initTokenInspector'; import SingletonOraiswapV3 from 'libs/contractSingleton'; import { getPools } from 'pages/Pools/hooks'; import { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { onChainTokenToTokenItem } from 'reducer/onchainTokens'; import { addToOraichainTokens } from 'reducer/token'; -import { RootState } from 'store/configure'; +import { RootState, store } from 'store/configure'; import { PoolInfoResponse } from 'types/pool'; import { calcPrice } from '../components/PriceRangePlot/utils'; import { extractAddress, formatPoolData } from '../helpers/format'; +import { inspectTokenFromOraiCommonApi } from 'helper'; export const useGetPoolList = (coingeckoPrices: CoinGeckoPrices) => { - const [prices, setPrices] = useState>(coingeckoPrices); - const [dataPool, setDataPool] = useState([...Array(0)]); const dispatch = useDispatch(); const allOraichainTokens = useSelector((state: RootState) => state.token.allOraichainTokens || []); - + const [prices, setPrices] = useState>(coingeckoPrices); + const [dataPool, setDataPool] = useState([...Array(0)]); const { data: poolList, refetch: refetchPoolList, isLoading: isLoadingGetPoolList - } = useQuery<(PoolWithPoolKey | PoolInfoResponse)[]>(['pool-v3-pools', coingeckoPrices], () => getPoolList(), { - refetchOnWindowFocus: false - // cacheTime: 5 * 60 * 1000, - }); + } = useQuery<(PoolWithPoolKey | PoolInfoResponse)[]>( + ['pool-v3-pools', Object.keys(coingeckoPrices).length], + getPoolList, + { + refetchOnWindowFocus: false, + cacheTime: 5 * 60 * 1000, + enabled: Object.keys(coingeckoPrices).length > 0 + } + ); useEffect(() => { if (!poolList || poolList?.length === 0 || Object.keys(coingeckoPrices).length === 0) return; @@ -43,7 +46,7 @@ export const useGetPoolList = (coingeckoPrices: CoinGeckoPrices) => { const tokenY = allOraichainTokens.find((token) => extractAddress(token) === pool.pool_key.token_y); if (!tokenX || !tokenY) continue; - if (tokenX && !prices[tokenX.coinGeckoId]) { + if (!prices[tokenX.coinGeckoId]) { if (prices[tokenY.coinGeckoId]) { // calculate price of X in Y from current sqrt price const price = calcPrice(pool.pool.current_tick_index, true, tokenX.decimals, tokenY.decimals); @@ -51,7 +54,7 @@ export const useGetPoolList = (coingeckoPrices: CoinGeckoPrices) => { } } - if (tokenY && !prices[tokenY.coinGeckoId]) { + if (!prices[tokenY.coinGeckoId]) { if (prices[tokenX.coinGeckoId]) { // calculate price of Y in X from current sqrt price const price = calcPrice(pool.pool.current_tick_index, false, tokenX.decimals, tokenY.decimals); @@ -64,7 +67,7 @@ export const useGetPoolList = (coingeckoPrices: CoinGeckoPrices) => { }, [poolList]); useEffect(() => { - if (!poolList || poolList.length === 0 || Object.keys(coingeckoPrices).length === 0) return; + if (!poolList || poolList.length === 0) return; (async function formatListPools() { const tokenAddresses = new Set(); @@ -85,19 +88,16 @@ export const useGetPoolList = (coingeckoPrices: CoinGeckoPrices) => { } }); + const HMSTR_DENOM = 'factory/orai17hyr3eg92fv34fdnkend48scu32hn26gqxw3hnwkfy904lk9r09qqzty42/HMSTR'; + if (tokenAddresses.has(HMSTR_DENOM)) tokenAddresses.delete(HMSTR_DENOM); if (tokenAddresses.size > 0) { - if ( - !( - tokenAddresses.size === 1 && - // deprecate HMSTR token - tokenAddresses.has('factory/orai17hyr3eg92fv34fdnkend48scu32hn26gqxw3hnwkfy904lk9r09qqzty42/HMSTR') - ) - ) { - const tokenInspector = await getTokenInspectorInstance(); - const extendedInfos = await tokenInspector.inspectMultipleTokens([...tokenAddresses]); - const convertToTokensType = extendedInfos.map((info) => onChainTokenToTokenItem(info)); - dispatch(addToOraichainTokens(convertToTokensType)); + const tokenAddressesArray = [...tokenAddresses]; + const tokenChunks = []; + for (let i = 0; i < tokenAddressesArray.length; i += 30) { + const chunk = tokenAddressesArray.slice(i, i + 30); + tokenChunks.push(inspectTokenFromOraiCommonApi(chunk)); } + dispatch(addToOraichainTokens(tokenChunks.flat())); } const listPools = (poolList || []).map((p) => formatPoolData(p)); From 5a92786653f0c8667c881b6a52fde67cec571e79 Mon Sep 17 00:00:00 2001 From: trungbach Date: Sun, 12 Jan 2025 19:51:23 +0700 Subject: [PATCH 2/2] Remove unused ReactQueryDevtools import from App component --- src/layouts/App.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/layouts/App.tsx b/src/layouts/App.tsx index 534e7a8c1..3abba4e3f 100644 --- a/src/layouts/App.tsx +++ b/src/layouts/App.tsx @@ -22,7 +22,6 @@ import Metamask from 'libs/metamask'; import { buildUnsubscribeMessage, buildWebsocketSendMessage, processWsResponseMsg } from 'libs/utils'; import { useLoadWalletsTon } from 'pages/Balance/hooks/useLoadWalletsTon'; import { useEffect, useState } from 'react'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { useDispatch, useSelector } from 'react-redux'; import useWebSocket from 'react-use-websocket'; import { setAddressBookList } from 'reducer/addressBook'; @@ -389,7 +388,6 @@ const App = () => {
{/* */} - {/* {(!bannerTime || Date.now() > bannerTime + 86_400_000) && } */}