From 919179110e9ab0033b9feab2d2425d72b3102a4f Mon Sep 17 00:00:00 2001 From: viet-nv Date: Tue, 7 May 2024 14:52:55 +0700 Subject: [PATCH] feat: support elastic legacy query from rpc instead of subgraph --- src/hooks/useContract.ts | 4 +- src/hooks/useElasticLegacy.ts | 255 ++++++++++++++++---------------- src/hooks/usePools.ts | 8 +- src/hooks/useProAmmPositions.ts | 31 +++- src/services/ksSetting.ts | 31 ++++ 5 files changed, 186 insertions(+), 143 deletions(-) diff --git a/src/hooks/useContract.ts b/src/hooks/useContract.ts index 8b6fa3c953..53ed00cc46 100644 --- a/src/hooks/useContract.ts +++ b/src/hooks/useContract.ts @@ -349,9 +349,9 @@ export function useProAmmNFTPositionManagerSigningContract(): Contract | null { return useSigningContract(networkInfo.elastic.nonfungiblePositionManager, NFTPositionManagerABI.abi) } -export function useProAmmNFTPositionManagerReadingContract(): Contract | null { +export function useProAmmNFTPositionManagerReadingContract(customContract?: string): Contract | null { const { networkInfo } = useActiveWeb3React() - return useReadingContract(networkInfo.elastic.nonfungiblePositionManager, NFTPositionManagerABI.abi) + return useReadingContract(customContract || networkInfo.elastic.nonfungiblePositionManager, NFTPositionManagerABI.abi) } export function useProAmmTickReader(): Contract | null { diff --git a/src/hooks/useElasticLegacy.ts b/src/hooks/useElasticLegacy.ts index 941cce23a2..d6d9436b00 100644 --- a/src/hooks/useElasticLegacy.ts +++ b/src/hooks/useElasticLegacy.ts @@ -1,11 +1,18 @@ import { TransactionResponse } from '@ethersproject/abstract-provider' -import { ChainId, CurrencyAmount, Percent, Token as TokenSDK } from '@kyberswap/ks-sdk-core' -import { NonfungiblePositionManager, Pool, Position as PositionSDK } from '@kyberswap/ks-sdk-elastic' +import { ChainId, Currency, CurrencyAmount, Percent, Token as TokenSDK } from '@kyberswap/ks-sdk-core' +import { + FeeAmount, + NonfungiblePositionManager, + Pool, + Position as PositionSDK, + computePoolAddress, +} from '@kyberswap/ks-sdk-elastic' import { captureException } from '@sentry/react' import { BigNumber } from 'ethers' import { Interface } from 'ethers/lib/utils' import JSBI from 'jsbi' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { useGetTokenByAddressesQuery } from 'services/ksSetting' import TickReaderABI from 'constants/abis/v2/ProAmmTickReader.json' import { didUserReject } from 'constants/connectors/utils' @@ -18,6 +25,8 @@ import { ErrorName } from 'utils/sentry' import { unwrappedToken } from 'utils/wrappedCurrency' import { useMulticallContract } from './useContract' +import { PoolState, usePools } from './usePools' +import { useProAmmPositions } from './useProAmmPositions' import useTransactionDeadline from './useTransactionDeadline' const tickReaderInterface = new Interface(TickReaderABI.abi) @@ -92,78 +101,6 @@ export const config: { }, } -const query = (user: string) => ` -{ - depositedPositions(subgraphError: allow, first: 1000, where: {user: "${user.toLowerCase()}"}) { - user - farm { - id - } - position { - id - owner - tickLower { - tickIdx - } - tickUpper { - tickIdx - } - liquidity - token0 { - id - symbol - decimals - name - } - token1 { - id - symbol - decimals - name - } - pool { - id - feeTier - sqrtPrice - liquidity - reinvestL - tick - } - } - } - positions(subgraphError: allow, first: 1000, where: {owner: "${user.toLowerCase()}"}) { - id - liquidity - owner - tickLower { - tickIdx - } - tickUpper { - tickIdx - } - token0 { - id - symbol - decimals - name - } - token1 { - id - symbol - decimals - name - } - pool { - id - feeTier - sqrtPrice - liquidity - reinvestL - tick - } - } -}` - interface Token { id: string symbol: string @@ -192,72 +129,130 @@ export interface Position { } } -export default function useElasticLegacy(interval = true) { +export default function useElasticLegacy(_interval = true) { const { chainId, account } = useActiveWeb3React() - const [loading, setLoading] = useState(false) - const [positions, setPositions] = useState([]) - const [farmPositions, setFarmPostions] = useState([]) - const previousChainIdRef = useRef(chainId) + const [loading, _setLoading] = useState(false) + // const [positions, setPositions] = useState([]) + // const [farmPositions, setFarmPostions] = useState([]) + // const previousChainIdRef = useRef(chainId) + + const { positions: positionsFromContract } = useProAmmPositions( + account, + config[chainId]?.positionManagerContract, + '0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a', + '0xc597aba1bb02db42ba24a8878837965718c032f8b46be94a6e46452a9f89ca01', + ) - useEffect(() => { - if (previousChainIdRef.current !== chainId || !account) { - setPositions([]) - setFarmPostions([]) - } - const getData = () => { - if (!account || !config[chainId]) { - setLoading(false) - return - } - fetch(config[chainId].subgraphUrl, { - method: 'POST', - body: JSON.stringify({ - query: query(account), - }), - }) - .then(res => res.json()) - .then( - (res: { - data: { - positions: Position[] - depositedPositions: { - user: string - farm: { id: string } - position: Position - }[] - } - }) => { - setPositions(res.data.positions) - const farmPositions = res.data.depositedPositions.filter( - item => item.farm.id === config[chainId].farmContract && item.user !== item.position.owner, - ) + const activePositions = useMemo( + () => positionsFromContract?.filter(item => item.liquidity.gt('0')) || [], + [positionsFromContract], + ) - setFarmPostions(farmPositions.map(item => item.position)) - }, - ) - .finally(() => setLoading(false)) - } + const tokenAddresses = useMemo(() => { + return [...new Set(activePositions.map(item => [item.token0, item.token1]).flat())] + }, [activePositions]) - setLoading(true) - getData() - const i = - interval && - setInterval(() => { - getData() - }, 15_000) + const { data: tokens } = useGetTokenByAddressesQuery( + { addresses: tokenAddresses, chainId }, + { + skip: !tokenAddresses.length, + }, + ) - return () => (i ? clearInterval(i) : undefined) - }, [chainId, account, interval]) + const poolIds = useMemo(() => { + const ids = [...new Set(activePositions.map(item => item.poolId))] - useEffect(() => { - previousChainIdRef.current = chainId - }, [chainId]) + const poolKeys = ids.map(id => { + const pos = activePositions.find(item => item.poolId === id) + const t0 = tokens?.find(t => t.wrapped.address === pos?.token0) + const t1 = tokens?.find(t => t.wrapped.address === pos?.token1) + + if (!pos || !t0 || !t1) return null + + return [t0.wrapped, t1.wrapped, pos.fee] + }) + + return poolKeys.filter(Boolean) as [Currency, Currency, FeeAmount][] + }, [activePositions, tokens]) + + const poolRes = usePools( + poolIds, + '0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a', + '0xc597aba1bb02db42ba24a8878837965718c032f8b46be94a6e46452a9f89ca01', + ) + + const pools = useMemo(() => { + return poolRes + .map(p => { + if (p[0] === PoolState.EXISTS) { + return p[1] + } + return null + }) + .filter(Boolean) + }, [poolRes]) + + const positions: Position[] = useMemo(() => { + return activePositions + .map(pos => { + const t0 = tokens?.find(t => t.wrapped.address === pos?.token0) + const t1 = tokens?.find(t => t.wrapped.address === pos?.token1) + + const pool = pools.find(p => { + if (!p) return false + const pId = computePoolAddress({ + factoryAddress: '0x5F1dddbf348aC2fbe22a163e30F99F9ECE3DD50a', + tokenA: p.token0.wrapped, + tokenB: p.token1.wrapped, + fee: p.fee, + initCodeHashManualOverride: '0xc597aba1bb02db42ba24a8878837965718c032f8b46be94a6e46452a9f89ca01', + }) + return pId === pos.poolId + }) + + if (!t0 || !t1 || !pool || !account) return null + + return { + id: pos.tokenId.toString(), + owner: account, + liquidity: pos.liquidity.toString(), + token0: { + id: t0.wrapped.address, + symbol: t0.symbol, + decimals: t0.wrapped.decimals.toString(), + name: t0.name, + }, + token1: { + id: t1.wrapped.address, + symbol: t1.symbol, + decimals: t1.wrapped.decimals.toString(), + name: t1.name, + }, + tickLower: { + tickIdx: pos.tickLower.toString(), + }, + tickUpper: { + tickIdx: pos.tickUpper.toString(), + }, + pool: { + id: pos.poolId, + feeTier: pos.fee.toString(), + sqrtPrice: pool.sqrtRatioX96.toString(), + + liquidity: pool.liquidity.toString(), + reinvestL: pool.reinvestLiquidity.toString(), + tick: pool.tickCurrent.toString(), + }, + } + }) + .filter(Boolean) as Position[] + }, [activePositions, account, pools, tokens]) return { loading, positions: positions.filter(item => item.liquidity !== '0'), allPositions: positions, - farmPositions, + farmPositions: [] as Position[], } } diff --git a/src/hooks/usePools.ts b/src/hooks/usePools.ts index ff674c993a..29dfe26bc0 100644 --- a/src/hooks/usePools.ts +++ b/src/hooks/usePools.ts @@ -18,6 +18,8 @@ const POOL_STATE_INTERFACE = new Interface(ProAmmPoolStateABI.abi) export function usePools( poolKeys: [Currency | undefined, Currency | undefined, FeeAmount | undefined][], + customFactory?: string, + customHash?: string, ): [PoolState, Pool | null][] { const { networkInfo } = useActiveWeb3React() @@ -39,16 +41,16 @@ export function usePools( if (!proAmmCoreFactoryAddress || !value || value[0].equals(value[1])) return undefined const param = { - factoryAddress: proAmmCoreFactoryAddress, + factoryAddress: customFactory || proAmmCoreFactoryAddress, tokenA: value[0], tokenB: value[1], fee: value[2], - initCodeHashManualOverride: networkInfo.elastic.initCodeHash, + initCodeHashManualOverride: customHash || networkInfo.elastic.initCodeHash, } return computePoolAddress(param) }) - }, [transformed, networkInfo]) + }, [transformed, networkInfo, customFactory, customHash]) const slot0s = useMultipleContractSingleData(poolAddresses, POOL_STATE_INTERFACE, 'getPoolState') const liquidities = useMultipleContractSingleData(poolAddresses, POOL_STATE_INTERFACE, 'getLiquidityState') diff --git a/src/hooks/useProAmmPositions.ts b/src/hooks/useProAmmPositions.ts index e01703fd1a..be2a33cea1 100644 --- a/src/hooks/useProAmmPositions.ts +++ b/src/hooks/useProAmmPositions.ts @@ -17,8 +17,13 @@ interface UseProAmmPositionsResults { positions: PositionDetails[] | undefined } -export function useProAmmPositionsFromTokenIds(tokenIds: BigNumber[] | undefined): UseProAmmPositionsResults { - const positionManager = useProAmmNFTPositionManagerReadingContract() +export function useProAmmPositionsFromTokenIds( + tokenIds: BigNumber[] | undefined, + customContract?: string, + customFactory?: string, + customInitCodeHash?: string, +): UseProAmmPositionsResults { + const positionManager = useProAmmNFTPositionManagerReadingContract(customContract) const { networkInfo } = useActiveWeb3React() const inputs = useMemo(() => (tokenIds ? tokenIds.map(tokenId => [tokenId]) : []), [tokenIds]) @@ -36,7 +41,7 @@ export function useProAmmPositionsFromTokenIds(tokenIds: BigNumber[] | undefined return { tokenId: tokenId, poolId: getCreate2Address( - networkInfo.elastic.coreFactory, + customFactory || networkInfo.elastic.coreFactory, keccak256( ['bytes'], [ @@ -46,7 +51,7 @@ export function useProAmmPositionsFromTokenIds(tokenIds: BigNumber[] | undefined ), ], ), - networkInfo.elastic.initCodeHash, + customInitCodeHash || networkInfo.elastic.initCodeHash, ), feeGrowthInsideLast: result.pos.feeGrowthInsideLast, nonce: result.pos.nonce, @@ -62,7 +67,7 @@ export function useProAmmPositionsFromTokenIds(tokenIds: BigNumber[] | undefined }) } return undefined - }, [loading, error, results, tokenIds, networkInfo]) + }, [loading, error, results, tokenIds, networkInfo, customInitCodeHash, customFactory]) return useMemo(() => { return { @@ -85,8 +90,13 @@ export function useProAmmPositionsFromTokenId(tokenId: BigNumber | undefined): U } } -export function useProAmmPositions(account: string | null | undefined): UseProAmmPositionsResults { - const positionManager = useProAmmNFTPositionManagerReadingContract() +export function useProAmmPositions( + account: string | null | undefined, + customContract?: string, + customFactory?: string, + customInitCodeHash?: string, +): UseProAmmPositionsResults { + const positionManager = useProAmmNFTPositionManagerReadingContract(customContract) const { loading: balanceLoading, result: balanceResult } = useSingleCallResult(positionManager, 'balanceOf', [ account ?? undefined, ]) @@ -118,7 +128,12 @@ export function useProAmmPositions(account: string | null | undefined): UseProAm return [] }, [account, tokenIdResults]) - const { positions, loading: positionsLoading } = useProAmmPositionsFromTokenIds(tokenIds) + const { positions, loading: positionsLoading } = useProAmmPositionsFromTokenIds( + tokenIds, + customContract, + customFactory, + customInitCodeHash, + ) return useMemo(() => { return { diff --git a/src/services/ksSetting.ts b/src/services/ksSetting.ts index 5afac7fecf..9a45b370a0 100644 --- a/src/services/ksSetting.ts +++ b/src/services/ksSetting.ts @@ -153,6 +153,36 @@ const ksSettingApi = createApi({ return { data } }, }), + + getTokenByAddresses: builder.query< + Array, + { addresses: string[]; chainId: ChainId } + >({ + queryFn: async ({ addresses, chainId }, _api, _extra, fetchWithBQ): Promise => { + const tokenListRes = await fetchWithBQ({ + url: '/tokens', + params: { chainIds: chainId, addresses: addresses.join(','), page: 1, pageSize: 100 }, + }) + const tokens = (tokenListRes.data as TokenListResponse)?.data.tokens + const foundedTokenAddress = tokens.map(item => item.address) + + const tokensNotFound = addresses.filter(item => !foundedTokenAddress.includes(item.toLowerCase())) + + if (tokensNotFound.length) { + const importTokenRes = await fetchWithBQ({ + url: '/tokens/import', + method: 'POST', + body: { tokens: tokensNotFound.map(item => ({ chainId: chainId.toString(), address: item })) }, + }) + const importedTokens = (importTokenRes.data as TokenImportResponse)?.data.tokens?.map(item => item.data) + return { + data: [...tokens, ...importedTokens].map(formatTokenInfo), + } + } + return { data: tokens.map(formatTokenInfo) } + }, + }), + importToken: builder.mutation>({ query: tokens => ({ url: `/tokens/import`, @@ -185,6 +215,7 @@ export const { useImportTokenMutation, useLazyGetTopTokensQuery, useGetChainsConfigurationQuery, + useGetTokenByAddressesQuery, } = ksSettingApi export default ksSettingApi