Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add debounce and abort signal to position manager, make calls per chain instead of per gauge #10906

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 70 additions & 26 deletions apps/web/src/views/GaugesVoting/hooks/useGaugesFilter.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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'

Expand Down Expand Up @@ -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
Expand All @@ -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<Record<ChainId, PCSDuoTokenVaultConfig[]>> = 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
Expand All @@ -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<SortOptions>()
const debouncedQuery = useDebounce(searchText, 200)
const filterGauges = useFilteredGauges({ filter, fullGauges, searchText, sort, setSort })

return {
filterGauges,

searchText,
searchText: debouncedQuery,
setSearchText,

filter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 10 additions & 3 deletions apps/web/src/views/GaugesVoting/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> => {
export const getPositionManagerName = async (
gauge: Gauge,
vaults?: PCSDuoTokenVaultConfig[],
signal?: AbortSignal,
): Promise<string> => {
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 ?? ''

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
103 changes: 58 additions & 45 deletions packages/position-managers/src/utils/fetchPositionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,70 @@ import { VaultConfig } from '../types'

const positionManagerCache: Record<string, VaultConfig[]> = {}

export const fetchPositionManager = async (chainId: ChainId): Promise<VaultConfig[]> => {
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<VaultConfig[]> => {
const cacheKey = `${chainId}-all}`

// Return cached data if it exists
if (positionManagerCache[cacheKey]) {
return positionManagerCache[cacheKey]
}

try {
const params = { chainId }
const queryString = Object.entries(params)
.map(([key, value]) => `${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
}
Loading