diff --git a/lib/cron/cache-pools.ts b/lib/cron/cache-pools.ts index 2ae85e0e3a..888c8e9c8b 100644 --- a/lib/cron/cache-pools.ts +++ b/lib/cron/cache-pools.ts @@ -6,6 +6,7 @@ import { V2SubgraphProvider, V3SubgraphPool, V3SubgraphProvider, + V4SubgraphPool, } from '@uniswap/smart-order-router' import { EventBridgeEvent, ScheduledHandler } from 'aws-lambda' import { S3 } from 'aws-sdk' @@ -23,7 +24,7 @@ import { AWSMetricsLogger } from '../handlers/router-entities/aws-metrics-logger import { metricScope } from 'aws-embedded-metrics' import * as zlib from 'zlib' import dotenv from 'dotenv' -import { v4HooksPoolsFiltering } from '../../test/utils/v4HooksPoolsFiltering' +import { v4HooksPoolsFiltering } from '../util/v4HooksPoolsFiltering' // Needed for local stack dev, not needed for staging or prod // But it still doesn't work on the local cdk stack update, @@ -223,7 +224,7 @@ const handler: ScheduledHandler = metricScope((metrics) => async (event: EventBr } if (protocol === Protocol.V4) { - pools = v4HooksPoolsFiltering(chainId, pools) + pools = v4HooksPoolsFiltering(chainId, pools as Array) } metric.putMetric(`${metricPrefix}.getPools.latency`, Date.now() - beforeGetPool) diff --git a/lib/util/v4HooksPoolsFiltering.ts b/lib/util/v4HooksPoolsFiltering.ts new file mode 100644 index 0000000000..85f095e1e2 --- /dev/null +++ b/lib/util/v4HooksPoolsFiltering.ts @@ -0,0 +1,62 @@ +import { V4SubgraphPool } from '@uniswap/smart-order-router' +import { Hook } from '@uniswap/v4-sdk' +import { HOOKS_ADDRESSES_ALLOWLIST } from './hooksAddressesAllowlist' +import { ChainId } from '@uniswap/sdk-core' +import { PriorityQueue } from '@datastructures-js/priority-queue' + +type V4PoolGroupingKey = string + +function convertV4PoolToGroupingKey(pool: V4SubgraphPool): V4PoolGroupingKey { + return pool.token0.id.concat(pool.token1.id).concat(pool.feeTier) +} + +function isHooksPoolRoutable(pool: V4SubgraphPool): boolean { + return ( + !Hook.hasSwapPermissions(pool.hooks) && + // If the fee tier is smaller than or equal to 100%, it means the pool is not dynamic fee pool. + // Swap fee in total can be 100% (https://github.com/Uniswap/v4-core/blob/b619b6718e31aa5b4fa0286520c455ceb950276d/src/libraries/SwapMath.sol#L12) + // Dynamic fee is at 0x800000 or 838.8608% fee tier. + // Since pool manager doesn;t check the fee at 100% max during pool initialization (https://github.com/Uniswap/v4-core/blob/main/src/PoolManager.sol#L128) + // it's more defensive programming to ensure the fee tier is less than or equal to 100% + Number(pool.feeTier) <= 1000000 + ) +} + +// it has to be a min heap in order to preserve the top eth tvl v4 pools +const V4SubgraphPoolComparator = (a: V4SubgraphPool, b: V4SubgraphPool) => { + return a.tvlETH > b.tvlETH ? 1 : -1 +} + +export function v4HooksPoolsFiltering(chainId: ChainId, pools: Array): Array { + const v4PoolsByTokenPairsAndFees: Record> = {} + + pools.forEach((pool: V4SubgraphPool) => { + if (isHooksPoolRoutable(pool)) { + const v4Pools = + v4PoolsByTokenPairsAndFees[convertV4PoolToGroupingKey(pool)] ?? + new PriorityQueue(V4SubgraphPoolComparator) + + v4Pools.push(pool) + + if (v4Pools.size() > 10) { + v4Pools.dequeue() + } + + v4PoolsByTokenPairsAndFees[pool.token0.id.concat(pool.token1.id).concat(pool.feeTier)] = v4Pools + } + }) + + const topTvlPools: Array = [] + Object.values(v4PoolsByTokenPairsAndFees).forEach((pq: PriorityQueue) => { + topTvlPools.push(...pq.toArray()) + }) + + const allowlistedHooksPools = pools.filter((pool: V4SubgraphPool) => { + return ( + HOOKS_ADDRESSES_ALLOWLIST[chainId].includes(pool.hooks.toLowerCase()) && + !topTvlPools.find((topPool: V4SubgraphPool) => topPool.id.toLowerCase() === pool.id.toLowerCase()) + ) + }) + + return topTvlPools.concat(allowlistedHooksPools) +} diff --git a/package-lock.json b/package-lock.json index 9fc7911573..afd35082ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "GPL", "dependencies": { + "@datastructures-js/priority-queue": "^6.3.1", "@hapi/joi": "^17.1.1", "@middy/core": "^2.4.1", "@middy/http-error-handler": "^2.4.1", @@ -835,9 +836,17 @@ } }, "node_modules/@datastructures-js/heap": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.1.2.tgz", - "integrity": "sha512-Dl6MPPVXxzWsSQxIaV0sOpAx/B8r7RYUO5/GWe7GhG9v9P4QfZ1cgPSq+SoF0QJFhu9G9TmtPfRLHPWzL73GpQ==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.3.3.tgz", + "integrity": "sha512-UcUu/DLh/aM4W3C8zZfwxxm6/6FIZUlm3mcAXuNOCa6Aj4iizNvNXQyb8DjZQH2jKSQbMRyNlngP6TPimuGjpQ==" + }, + "node_modules/@datastructures-js/priority-queue": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@datastructures-js/priority-queue/-/priority-queue-6.3.1.tgz", + "integrity": "sha512-eoxkWql/j0VJ0UFMFTpnyJz4KbEEVQ6aZ/JuJUgenu0Im4tYKylAycNGsYCHGXiVNEd7OKGVwfx1Ac3oYkuu7A==", + "dependencies": { + "@datastructures-js/heap": "^4.3.3" + } }, "node_modules/@ensdomains/ens": { "version": "0.4.5", @@ -25260,9 +25269,17 @@ } }, "@datastructures-js/heap": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.1.2.tgz", - "integrity": "sha512-Dl6MPPVXxzWsSQxIaV0sOpAx/B8r7RYUO5/GWe7GhG9v9P4QfZ1cgPSq+SoF0QJFhu9G9TmtPfRLHPWzL73GpQ==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@datastructures-js/heap/-/heap-4.3.3.tgz", + "integrity": "sha512-UcUu/DLh/aM4W3C8zZfwxxm6/6FIZUlm3mcAXuNOCa6Aj4iizNvNXQyb8DjZQH2jKSQbMRyNlngP6TPimuGjpQ==" + }, + "@datastructures-js/priority-queue": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@datastructures-js/priority-queue/-/priority-queue-6.3.1.tgz", + "integrity": "sha512-eoxkWql/j0VJ0UFMFTpnyJz4KbEEVQ6aZ/JuJUgenu0Im4tYKylAycNGsYCHGXiVNEd7OKGVwfx1Ac3oYkuu7A==", + "requires": { + "@datastructures-js/heap": "^4.3.3" + } }, "@ensdomains/ens": { "version": "0.4.5", diff --git a/package.json b/package.json index 040916bf9c..f1854a6816 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "typescript": "^4.2.3" }, "dependencies": { + "@datastructures-js/priority-queue": "^6.3.1", "@hapi/joi": "^17.1.1", "@middy/core": "^2.4.1", "@middy/http-error-handler": "^2.4.1", @@ -80,13 +81,13 @@ "@types/async-retry": "^1.4.2", "@types/chai-subset": "^1.3.3", "@types/qs": "^6.9.7", + "@types/semver": "^7.5.8", "@types/sinon": "^10.0.6", "@types/stats-lite": "^2.2.0", "@uniswap/default-token-list": "^11.13.0", "@uniswap/permit2-sdk": "^1.3.0", "@uniswap/router-sdk": "^1.14.0", "@uniswap/sdk-core": "^5.9.0", - "@types/semver": "^7.5.8", "@uniswap/smart-order-router": "4.8.0", "@uniswap/token-lists": "^1.0.0-beta.33", "@uniswap/universal-router-sdk": "^4.6.1", diff --git a/test/jest/unit/handlers/util/v4HooksPoolsFiltering.test.ts b/test/jest/unit/handlers/util/v4HooksPoolsFiltering.test.ts new file mode 100644 index 0000000000..cc8b16b9fc --- /dev/null +++ b/test/jest/unit/handlers/util/v4HooksPoolsFiltering.test.ts @@ -0,0 +1,597 @@ +import { describe, expect } from '@jest/globals' +import { v4HooksPoolsFiltering } from '../../../../../lib/util/v4HooksPoolsFiltering' +import { V4SubgraphPool } from '@uniswap/smart-order-router' + +describe('v4HooksPoolsFiltering', () => { + it('before swap hooks pool is filtered out', () => { + const beforeSwapHookAddress = '0x0000000000000000000000000000000000000080' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeSwapHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[0]]) + }) + + it('after swap hooks pool is filtered out', () => { + const afterSwapHookAddress = '0x0000000000000000000000000000000000000040' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterSwapHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[0]]) + }) + + it('dynamic fee hooks pool is filtered out', () => { + const dynamicFee = `8388608` + + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: dynamicFee, + tickSpacing: '1', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[0]]) + }) + + it('before initialize hooks pool is not filtered out', () => { + const beforeInitializeHookAddress = '0x0000000000000000000000000000000000002000' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeInitializeHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after initialize hooks pool is not filtered out', () => { + const afterInitializeHookAddress = '0x0000000000000000000000000000000000001000' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterInitializeHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before add liquidity hooks pool is not filtered out', () => { + const beforeAddLiquidityHookAddress = '0x0000000000000000000000000000000000000800' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeAddLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after add liquidity hooks pool is not filtered out', () => { + const afterAddLiquidityHookAddress = '0x0000000000000000000000000000000000000400' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterAddLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before remove liquidity hooks pool is not filtered out', () => { + const beforeRemoveLiquidityHookAddress = '0x0000000000000000000000000000000000000200' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeRemoveLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after remove liquidity hooks pool is not filtered out', () => { + const afterRemoveLiquidityHookAddress = '0x0000000000000000000000000000000000000100' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterRemoveLiquidityHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before donate hooks pool is not filtered out', () => { + const beforeDonateHookAddress = '0x0000000000000000000000000000000000000020' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeDonateHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after donate hooks pool is not filtered out', () => { + const afterDonateHookAddress = '0x0000000000000000000000000000000000000010' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterDonateHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('before swap returns delta hooks pool is not filtered out', () => { + const beforeSwapReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000008' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: beforeSwapReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after swap returns delta hooks pool is not filtered out', () => { + const afterSwapReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000004' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterSwapReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after add liquidity returns delta hooks pool is not filtered out', () => { + const afterAddLiquidityReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000002' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterAddLiquidityReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('after remove liquidity returns delta hooks pool is not filtered out', () => { + const afterRemoveLiquidityReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000001' + const v4Pools: Array = [ + { + id: '0', + feeTier: '0', + tickSpacing: '0', + hooks: '0x0000000000000000000000000000000000000020', + liquidity: '0', + token0: { + id: '0', + }, + token1: { + id: '0', + }, + tvlETH: 0, + tvlUSD: 0, + }, + { + id: '1', + feeTier: '1', + tickSpacing: '1', + hooks: afterRemoveLiquidityReturnsDeltaHookAddress, + liquidity: '1', + token0: { + id: '1', + }, + token1: { + id: '1', + }, + tvlETH: 1, + tvlUSD: 1, + }, + ] + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual([v4Pools[1], v4Pools[0]]) + }) + + it('11 hooks pool to retain 10 pools', () => { + const afterRemoveLiquidityReturnsDeltaHookAddress = '0x0000000000000000000000000000000000000001' + const v4Pools: Array = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'].map((i) => ({ + id: i, + feeTier: '500', + tickSpacing: i, + hooks: afterRemoveLiquidityReturnsDeltaHookAddress, + liquidity: i, + token0: { + id: 'USDC', + }, + token1: { + id: 'ETH', + }, + tvlETH: Number(i), + tvlUSD: Number(i), + })) + + expect(v4HooksPoolsFiltering(1, v4Pools)).toEqual(v4Pools.slice(1, 11)) + }) +}) diff --git a/test/utils/v4HooksPoolsFiltering.ts b/test/utils/v4HooksPoolsFiltering.ts deleted file mode 100644 index 5791522c4d..0000000000 --- a/test/utils/v4HooksPoolsFiltering.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { V2SubgraphPool, V3SubgraphPool, V4SubgraphPool } from '@uniswap/smart-order-router' -import { Hook } from '@uniswap/v4-sdk' -import { HOOKS_ADDRESSES_ALLOWLIST } from '../../lib/util/hooksAddressesAllowlist' -import { ChainId } from '@uniswap/sdk-core' - -export function routableHooks(pool: V4SubgraphPool): boolean { - return ( - !Hook.hasSwapPermissions(pool.hooks) && - // If the fee tier is smaller than or equal to 100%, it means the pool is not dynamic fee pool. - // Swap fee in total can be 100% (https://github.com/Uniswap/v4-core/blob/b619b6718e31aa5b4fa0286520c455ceb950276d/src/libraries/SwapMath.sol#L12) - // Dynamic fee is at 0x800000 or 838.8608% fee tier. - // Since pool manager doesn;t check the fee at 100% max during pool initialization (https://github.com/Uniswap/v4-core/blob/main/src/PoolManager.sol#L128) - // it's more defensive programming to ensure the fee tier is less than or equal to 100% - Number(pool.feeTier) <= 1000000 - ) -} - -export function v4HooksPoolsFiltering( - chainId: ChainId, - pools: Array -): Array { - const v4PoolsByTokenPairsAndFees: Record> = {} - - ;(pools as Array).forEach((pool: V4SubgraphPool) => { - if (routableHooks(pool)) { - const v4Pools = v4PoolsByTokenPairsAndFees[pool.token0.id.concat(pool.token1.id).concat(pool.feeTier)] || [] - - v4Pools.push(pool) - v4Pools.sort((a, b) => (a.tvlETH > b.tvlETH ? -1 : 1)) - - if (v4Pools.length > 10) { - v4Pools.pop() - } - - v4PoolsByTokenPairsAndFees[pool.token0.id.concat(pool.token1.id).concat(pool.feeTier)] = v4Pools - } - }) - - const topTvlPools = Object.values(v4PoolsByTokenPairsAndFees).reduce((acc, pools) => { - return acc.concat(pools) - }) - - const allowlistedHooksPools = (pools as Array).filter((pool: V4SubgraphPool) => { - const shouldFilterOut = !HOOKS_ADDRESSES_ALLOWLIST[chainId].includes(pool.hooks.toLowerCase()) - - return !shouldFilterOut - }) - - return topTvlPools.concat(allowlistedHooksPools) -}