diff --git a/apps/web/src/views/GaugesVoting/hooks/useGaugesFilter.ts b/apps/web/src/views/GaugesVoting/hooks/useGaugesFilter.ts index 71bd894a9dabb..8dbebd4b73461 100644 --- a/apps/web/src/views/GaugesVoting/hooks/useGaugesFilter.ts +++ b/apps/web/src/views/GaugesVoting/hooks/useGaugesFilter.ts @@ -1,4 +1,4 @@ -import { chainNames } from '@pancakeswap/chains' +import { ChainId, chainNames } from '@pancakeswap/chains' import { GAUGES_SUPPORTED_CHAIN_IDS, GAUGE_TYPE_NAMES, Gauge, GaugeType } from '@pancakeswap/gauges' import { FeeAmount } from '@pancakeswap/v3-sdk' import { @@ -10,6 +10,9 @@ import { useQueryStates, } from 'nuqs' import { useCallback, useEffect, useState } from 'react' +import { useDebounce } from '@pancakeswap/hooks' +import { fetchPositionManager, PCSDuoTokenVaultConfig } from '@pancakeswap/position-managers' +import fromPairs from 'lodash/fromPairs' import { Filter, FilterValue, Gauges, OptionsType, SortOptions } from '../components/GaugesFilter' import { getPositionManagerName } from '../utils' @@ -135,7 +138,7 @@ const useFilteredGauges = ({ filter, fullGauges, searchText, sort, setSort }) => }, [fullGauges, setSort, sort]) useEffect(() => { - const fetchFilteredGauges = async () => { + const fetchFilteredGauges = async (signal) => { if (!fullGauges || !fullGauges.length) { setFilteredGauges([]) return @@ -160,34 +163,74 @@ const useFilteredGauges = ({ filter, fullGauges, searchText, sort, setSort }) => // Asynchronous search based on searchText if (searchText?.length > 0) { - const updatedResults = await Promise.all( - results.map(async (gauge) => { - const positionManagerName = await getPositionManagerName(gauge) - const isMatch = [ - // search by pairName or tokenName - gauge.pairName.toLowerCase(), - // search by gauges type, e.g. "v2", "v3", "position manager" - GAUGE_TYPE_NAMES[gauge.type].toLowerCase(), - // search by chain name - chainNames[gauge.chainId], - // search by chain id - String(gauge.chainId), - // search by boost multiplier, e.g. "1.5x" - `${Number(gauge.boostMultiplier) / 100}x`, - // search by alm strategy name - positionManagerName.toLowerCase(), - ].some((text) => text?.includes(searchText.toLowerCase())) - return isMatch ? gauge : null - }), - ) - results = updatedResults.filter(Boolean) // Remove nulls + try { + const positionManagerPairs: Partial> = fromPairs( + await Promise.all( + results + .reduce((acc, gauge) => { + if (!acc.includes(gauge.chainId)) { + acc.push(gauge.chainId) + } + return acc + }, []) + .map(async (chainId) => { + const positionManagerName = await fetchPositionManager(chainId, signal) + return [chainId, positionManagerName] + }), + ), + ) + const updatedResults = await Promise.all( + results.map(async (gauge) => { + try { + const positionManagerName = await getPositionManagerName( + gauge, + positionManagerPairs?.[gauge.chainId] ?? undefined, + signal, + ) + const isMatch = [ + // search by pairName or tokenName + gauge.pairName.toLowerCase(), + // search by gauges type, e.g. "v2", "v3", "position manager" + GAUGE_TYPE_NAMES[gauge.type].toLowerCase(), + // search by chain name + chainNames[gauge.chainId], + // search by chain id + String(gauge.chainId), + // search by boost multiplier, e.g. "1.5x" + `${Number(gauge.boostMultiplier) / 100}x`, + // search by alm strategy name + positionManagerName.toLowerCase(), + ].some((text) => text?.includes(searchText.toLowerCase())) + return isMatch ? gauge : null + } catch (error) { + if (error instanceof Error) { + if (error.name !== 'AbortError') { + console.error('Error fetching position manager name:', error) + } else { + throw error + } + } + return null + } + }), + ) + results = updatedResults.filter(Boolean) // Remove nulls + } catch (error) { + return + } } - const sorter = getSorter(sort) setFilteredGauges(results.sort(sorter)) } - fetchFilteredGauges() + const controller = new AbortController() + const { signal } = controller + + fetchFilteredGauges(signal) + + return () => { + controller.abort() + } }, [filter, fullGauges, searchText, sort]) return filteredGauges @@ -196,12 +239,13 @@ const useFilteredGauges = ({ filter, fullGauges, searchText, sort, setSort }) => export const useGaugesQueryFilter = (fullGauges: Gauge[] | undefined) => { const { filter, setFilter, searchText, setSearchText } = useGaugesFilterQueryState() const [sort, setSort] = useState() + const debouncedQuery = useDebounce(searchText, 200) const filterGauges = useFilteredGauges({ filter, fullGauges, searchText, sort, setSort }) return { filterGauges, - searchText, + searchText: debouncedQuery, setSearchText, filter, diff --git a/apps/web/src/views/GaugesVoting/hooks/usePositionManagerName.ts b/apps/web/src/views/GaugesVoting/hooks/usePositionManagerName.ts index eaaa49e886ea4..7687aac9080a4 100644 --- a/apps/web/src/views/GaugesVoting/hooks/usePositionManagerName.ts +++ b/apps/web/src/views/GaugesVoting/hooks/usePositionManagerName.ts @@ -5,14 +5,7 @@ import { getPositionManagerName } from 'views/GaugesVoting/utils' export const usePositionManagerName = (data: Gauge) => { const { data: managerName } = useQuery({ queryKey: ['position-manager-name'], - queryFn: async () => { - try { - const result = await getPositionManagerName(data) - return result - } catch { - return '' - } - }, + queryFn: async ({ signal }) => getPositionManagerName(data, undefined, signal), enabled: Boolean(data), refetchOnWindowFocus: false, refetchOnReconnect: false, diff --git a/apps/web/src/views/GaugesVoting/utils.ts b/apps/web/src/views/GaugesVoting/utils.ts index 5e8071236d3c8..586e48fc088f6 100644 --- a/apps/web/src/views/GaugesVoting/utils.ts +++ b/apps/web/src/views/GaugesVoting/utils.ts @@ -6,11 +6,18 @@ export const getGaugeHash = (gaugeAddress: Address = zeroAddress, chainId: numbe return keccak256(encodePacked(['address', 'uint256'], [gaugeAddress, BigInt(chainId || 0)])) } -export const getPositionManagerName = async (gauge: Gauge): Promise => { +export const getPositionManagerName = async ( + gauge: Gauge, + vaults?: PCSDuoTokenVaultConfig[], + signal?: AbortSignal, +): Promise => { if (gauge.type !== GaugeType.ALM) return '' - const vaults: PCSDuoTokenVaultConfig[] = await fetchPositionManager(gauge.chainId) - const matchedVault = vaults.find((v) => v.vaultAddress === gauge.address) + let _vaults = vaults + if (!vaults) { + _vaults = await fetchPositionManager(gauge.chainId, signal) + } + const matchedVault = _vaults?.find((v) => v.vaultAddress === gauge.address) if (!matchedVault) return gauge.managerName ?? '' diff --git a/apps/web/src/views/PositionManagers/hooks/usePositionManager.ts b/apps/web/src/views/PositionManagers/hooks/usePositionManager.ts index b3f39fe34f9af..8d6ca4e34e68b 100644 --- a/apps/web/src/views/PositionManagers/hooks/usePositionManager.ts +++ b/apps/web/src/views/PositionManagers/hooks/usePositionManager.ts @@ -5,14 +5,7 @@ import { useQuery } from '@tanstack/react-query' export const usePositionManager = (chainId: ChainId): VaultConfig[] => { const { data } = useQuery({ queryKey: ['vault-config-by-chain', chainId], - queryFn: async () => { - try { - const result = await fetchPositionManager(chainId) - return result - } catch { - return [] - } - }, + queryFn: async ({ signal }) => fetchPositionManager(chainId, signal), enabled: Boolean(chainId), refetchOnWindowFocus: false, refetchOnReconnect: false, diff --git a/packages/position-managers/src/utils/fetchPositionManager.ts b/packages/position-managers/src/utils/fetchPositionManager.ts index 3460f2886222d..d81192f010697 100644 --- a/packages/position-managers/src/utils/fetchPositionManager.ts +++ b/packages/position-managers/src/utils/fetchPositionManager.ts @@ -5,7 +5,24 @@ import { VaultConfig } from '../types' const positionManagerCache: Record = {} -export const fetchPositionManager = async (chainId: ChainId): Promise => { +function anySignal(signals: AbortSignal[]): AbortSignal { + const controller = new AbortController() + + for (const signal of signals) { + if (signal.aborted) { + controller.abort() + return signal + } + + signal.addEventListener('abort', () => controller.abort(signal.reason), { + signal: controller.signal, + }) + } + + return controller.signal +} + +export const fetchPositionManager = async (chainId: ChainId, signal?: AbortSignal): Promise => { const cacheKey = `${chainId}-all}` // Return cached data if it exists @@ -13,49 +30,45 @@ export const fetchPositionManager = async (chainId: ChainId): Promise `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join('&') + const params = { chainId } + const queryString = Object.entries(params) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join('&') - const response = await fetch(`${POSITION_MANAGER_API_V2}?${queryString}`, { - signal: AbortSignal.timeout(3000), - }) - const result = await response.json() - const newData: VaultConfig[] = result.map((p: any) => ({ - ...p, - currencyA: new ERC20Token( - p.currencyA.chainId, - p.currencyA.address, - p.currencyA.decimals, - p.currencyA.symbol, - p.currencyA.name, - p.currencyA.projectLink, - ), - currencyB: new ERC20Token( - p.currencyB.chainId, - p.currencyB.address, - p.currencyB.decimals, - p.currencyB.symbol, - p.currencyB.name, - p.currencyB.projectLink, - ), - earningToken: new ERC20Token( - p.earningToken.chainId, - p.earningToken.address, - p.earningToken.decimals, - p.earningToken.symbol, - p.earningToken.name, - p.earningToken.projectLink, - ), - })) - - // Cache the result before returning it - positionManagerCache[cacheKey] = newData - - return newData - } catch (error) { - return [] - } + const response = await fetch(`${POSITION_MANAGER_API_V2}?${queryString}`, { + signal: anySignal([AbortSignal.timeout(3000), ...(signal ? [signal] : [])]), + }) + const result = await response.json() + const newData: VaultConfig[] = result.map((p: any) => ({ + ...p, + currencyA: new ERC20Token( + p.currencyA.chainId, + p.currencyA.address, + p.currencyA.decimals, + p.currencyA.symbol, + p.currencyA.name, + p.currencyA.projectLink, + ), + currencyB: new ERC20Token( + p.currencyB.chainId, + p.currencyB.address, + p.currencyB.decimals, + p.currencyB.symbol, + p.currencyB.name, + p.currencyB.projectLink, + ), + earningToken: new ERC20Token( + p.earningToken.chainId, + p.earningToken.address, + p.earningToken.decimals, + p.earningToken.symbol, + p.earningToken.name, + p.earningToken.projectLink, + ), + })) + + // Cache the result before returning it + positionManagerCache[cacheKey] = newData + + return newData }