Skip to content

Commit

Permalink
feat: Add debounce and abort signal to position manager, make calls p…
Browse files Browse the repository at this point in the history
…er chain instead of per gauge (#10906)

<!--
Before opening a pull request, please read the [contributing
guidelines](https://github.com/pancakeswap/pancake-frontend/blob/develop/CONTRIBUTING.md)
first
-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the `fetchPositionManager` and
`getPositionManagerName` functions to support cancellation through
`AbortSignal`. It also updates the `useQuery` hooks in various
components to utilize these changes, improving error handling and
performance.

### Detailed summary
- Updated `fetchPositionManager` to accept an optional `signal`
parameter for cancellation.
- Improved error handling in `fetchPositionManager` and
`getPositionManagerName`.
- Modified `useQuery` in `usePositionManager` and
`usePositionManagerName` to use the new `signal` parameter.
- Enhanced `useFilteredGauges` to handle asynchronous fetching with
cancellation.
- Added `anySignal` utility function to manage multiple abort signals.
- Updated the `getPositionManagerName` function to handle an optional
`vaults` parameter and signal.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your
question}`

<!-- end pr-codex -->
  • Loading branch information
memoyil authored Nov 4, 2024
1 parent 0dcaf4f commit d69cdda
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 90 deletions.
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
}

0 comments on commit d69cdda

Please sign in to comment.