diff --git a/apps/web/src/hooks/usePermit2Requires.ts b/apps/web/src/hooks/usePermit2Requires.ts index 96ee7690dcad9..768fcf30ab75d 100644 --- a/apps/web/src/hooks/usePermit2Requires.ts +++ b/apps/web/src/hooks/usePermit2Requires.ts @@ -1,7 +1,8 @@ import { CurrencyAmount, Token } from '@pancakeswap/swap-sdk-core' import { bscTestnetTokens, ethereumTokens, goerliTestnetTokens } from '@pancakeswap/tokens' import { useMemo } from 'react' -import { Address, isAddressEqual } from 'viem' +import { isAddressEqual } from 'utils' +import { Address } from 'viem' import useAccountActiveChain from './useAccountActiveChain' import useCurrentBlockTimestamp from './useCurrentBlockTimestamp' import { usePermit2Allowance } from './usePermit2Allowance' diff --git a/apps/web/src/state/farmsV4/state/accountPositions/fetcher.ts b/apps/web/src/state/farmsV4/state/accountPositions/fetcher.ts index 0e78c21796b64..99cdc8892e06e 100644 --- a/apps/web/src/state/farmsV4/state/accountPositions/fetcher.ts +++ b/apps/web/src/state/farmsV4/state/accountPositions/fetcher.ts @@ -12,6 +12,7 @@ import { AppState } from 'state' import { safeGetAddress } from 'utils' import { publicClient } from 'utils/viem' import { Address, erc20Abi, zeroAddress } from 'viem' +import { getBalanceNumber } from '@pancakeswap/utils/formatBalance' import { StablePoolInfo, V2PoolInfo } from '../type' import { StableLPDetail, V2LPDetail } from './type' @@ -136,10 +137,7 @@ export const getAccountV2LpDetails = async ( const validLpTokens = lpTokens.filter((token) => token.chainId === chainId) - const bCakeWrapperAddresses = validReserveTokens.map((tokens) => { - const lpAddress = getV2LiquidityToken(tokens).address - return getBCakeWrapperAddress(lpAddress, chainId) - }) + const bCakeWrapperAddresses = validLpTokens.map((token) => getBCakeWrapperAddress(token.address, chainId)) const balanceCalls = validLpTokens.map((token) => { return { @@ -152,15 +150,13 @@ export const getAccountV2LpDetails = async ( const farmingCalls = bCakeWrapperAddresses.reduce( (acc, address) => { if (!address || address === '0x') return acc - return [ - ...acc, - { - abi: v2BCakeWrapperABI, - address, - functionName: 'userInfo', - args: [account] as const, - } as const, - ] + acc.push({ + abi: v2BCakeWrapperABI, + address, + functionName: 'userInfo', + args: [account] as const, + }) + return acc }, [] as Array<{ abi: typeof v2BCakeWrapperABI @@ -203,16 +199,26 @@ export const getAccountV2LpDetails = async ( ]) const farming = bCakeWrapperAddresses.reduce((acc, address) => { - if (!address || address === '0x') return [...acc, undefined] - const { result } = _farming.shift() ?? { result: undefined } - return [...acc, result] + if (!address || address === '0x') { + acc.push(undefined) + } else { + const { result } = _farming.shift() ?? { result: undefined } + acc.push(result) + } + return acc }, [] as Array) - return balances .map((result, index) => { const { result: _balance = 0n, status } = result // LP not exist - if (status === 'failure') return undefined + if (status === 'failure') { + if (reserves[index].status === 'failure' && totalSupplies[index].status === 'failure') { + return undefined + } + throw new Error( + `Mismatch between balances, reserves, and supplies: Token: ${validLpTokens[index].address} Balance (${status}), Reserve (${reserves[index].status}), Supply (${totalSupplies[index].status})`, + ) + } const nativeBalance = CurrencyAmount.fromRawAmount(validLpTokens[index], _balance) const farmingInfo = farming[index] @@ -221,7 +227,7 @@ export const getAccountV2LpDetails = async ( let farmingBoostedAmount = CurrencyAmount.fromRawAmount(validLpTokens[index], '0') if (farmingInfo) { farmingBalance = CurrencyAmount.fromRawAmount(validLpTokens[index], farmingInfo[0].toString()) - farmingBoosterMultiplier = new BigNumber(Number(farmingInfo[2])).div(1000000000000).toNumber() + farmingBoosterMultiplier = getBalanceNumber(new BigNumber(Number(farmingInfo[2])), 12) farmingBoostedAmount = CurrencyAmount.fromRawAmount(validLpTokens[index], farmingInfo[3].toString()) } const tokens = validReserveTokens[index] @@ -256,7 +262,7 @@ export const getAccountV2LpDetails = async ( protocol: Protocol.V2, } }) - .filter((r) => typeof r !== 'undefined') as V2LPDetail[] + .filter(Boolean) as V2LPDetail[] } export const getStablePairDetails = async ( @@ -270,7 +276,8 @@ export const getStablePairDetails = async ( if (!account || !client || !validStablePairs.length) return [] const bCakeWrapperAddresses = validStablePairs.reduce((acc, pair) => { - return [...acc, getBCakeWrapperAddress(pair.lpAddress, chainId)] + acc.push(getBCakeWrapperAddress(pair.lpAddress, chainId)) + return acc }, [] as Array
) const balanceCalls = validStablePairs.map((pair) => { diff --git a/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts b/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts index 23edce12e6865..fe0123025bb0f 100644 --- a/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts +++ b/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountPositionDetailByPool.ts @@ -2,7 +2,8 @@ import { Protocol } from '@pancakeswap/farms' import { LegacyRouter } from '@pancakeswap/smart-router/legacy-router' import { useQuery, UseQueryResult } from '@tanstack/react-query' import { useCallback, useMemo } from 'react' -import { Address, isAddressEqual } from 'viem' +import { isAddressEqual } from 'utils' +import { Address } from 'viem' import { PoolInfo } from '../../type' import { getAccountV2LpDetails, getStablePairDetails } from '../fetcher' import { getAccountV3Positions } from '../fetcher/v3' diff --git a/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountV2LpDetails.ts b/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountV2LpDetails.ts index f6e3777c3604d..61ec4ccb7fe2c 100644 --- a/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountV2LpDetails.ts +++ b/apps/web/src/state/farmsV4/state/accountPositions/hooks/useAccountV2LpDetails.ts @@ -37,7 +37,6 @@ export const useAccountV2LpDetails = (chainIds: number[], account?: Address | nu refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, - refetchInterval: false, // Prevents re-fetching while the data is still fresh staleTime: SLOW_INTERVAL, } satisfies UseQueryOptions diff --git a/apps/web/src/state/farmsV4/state/extendPools/atom.ts b/apps/web/src/state/farmsV4/state/extendPools/atom.ts index 8784afafbd0dd..5bafd7afb153d 100644 --- a/apps/web/src/state/farmsV4/state/extendPools/atom.ts +++ b/apps/web/src/state/farmsV4/state/extendPools/atom.ts @@ -1,7 +1,8 @@ import { ChainId } from '@pancakeswap/chains' import { Protocol, supportedChainIdV4 } from '@pancakeswap/farms' import { atom } from 'jotai' -import { isAddressEqual, type Address } from 'viem' +import { isAddressEqual } from 'utils' +import { type Address } from 'viem' import { farmPoolsAtom } from '../farmPools/atom' import { ChainIdAddressKey, PoolInfo } from '../type' diff --git a/apps/web/src/state/farmsV4/state/farmPools/fetcher.ts b/apps/web/src/state/farmsV4/state/farmPools/fetcher.ts index 1c1e49b871369..f14ddd84d2b1a 100644 --- a/apps/web/src/state/farmsV4/state/farmPools/fetcher.ts +++ b/apps/web/src/state/farmsV4/state/farmPools/fetcher.ts @@ -16,7 +16,8 @@ import groupBy from 'lodash/groupBy' import { explorerApiClient } from 'state/info/api/client' import { v3Clients } from 'utils/graphql' import { publicClient } from 'utils/viem' -import { isAddressEqual, type Address } from 'viem' +import { isAddressEqual } from 'utils' +import { type Address } from 'viem' import { PoolInfo } from '../type' import { parseFarmPools } from '../utils' @@ -146,7 +147,12 @@ export const fetchExplorerFarmPools = async ( params: { query: { protocols: args.protocols ?? DEFAULT_PROTOCOLS, - chains: chains.reduce((acc, cur) => (cur ? [...acc, getChainNameInKebabCase(cur)] : acc), [] as any[]), + chains: chains.reduce((acc, cur) => { + if (cur) { + acc.push(getChainNameInKebabCase(cur)) + } + return acc + }, [] as any[]), }, }, }) @@ -184,6 +190,11 @@ export const fetchFarmPools = async ( try { remotePools = await fetchExplorerFarmPools(args, signal) } catch (error) { + if (error instanceof Error) { + if (error.name === 'AbortError') { + throw error + } + } console.error('Failed to fetch remote pools', error) } const localPools = UNIVERSAL_FARMS.filter((farm) => { diff --git a/apps/web/src/state/farmsV4/state/farmPools/hooks.ts b/apps/web/src/state/farmsV4/state/farmPools/hooks.ts index a6e60a76715a2..30b2ee4989352 100644 --- a/apps/web/src/state/farmsV4/state/farmPools/hooks.ts +++ b/apps/web/src/state/farmsV4/state/farmPools/hooks.ts @@ -9,7 +9,7 @@ import dayjs from 'dayjs' import { useAtom } from 'jotai' import groupBy from 'lodash/groupBy' import keyBy from 'lodash/keyBy' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useMemo } from 'react' import { publicClient } from 'utils/viem' import { zeroAddress } from 'viem' import { Address } from 'viem/accounts' @@ -22,18 +22,18 @@ type UnwrapPromise = T extends Promise ? U : T type ArrayItemType = T extends Array ? U : T export const useFarmPools = () => { - const [loaded, setLoaded] = useState(false) const [pools, setPools] = useAtom(farmPoolsAtom) - useEffect(() => { - if (!loaded) { - fetchFarmPools() - .then(setPools) - .finally(() => setLoaded(true)) - } - // only fetch once when mount - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + const { isLoading } = useQuery({ + queryKey: ['fetchFarmPools'], + queryFn: async ({ signal }) => { + const data = await fetchFarmPools(undefined, signal) + setPools(data) + }, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + }) const { data: poolsStatus, pending: isPoolStatusPending } = useMultiChainV3PoolsStatus(UNIVERSAL_FARMS) const { data: poolsTimeFrame, pending: isPoolsTimeFramePending } = useMultiChainPoolsTimeFrame(UNIVERSAL_FARMS) @@ -60,7 +60,7 @@ export const useFarmPools = () => { }) }, [pools, poolsStatus, isPoolStatusPending, poolsTimeFrame, isPoolsTimeFramePending]) - return { loaded, data: poolsWithStatus } + return { loaded: !isLoading, data: poolsWithStatus } } export const useV3PoolsLength = (chainIds: number[]) => { @@ -88,10 +88,8 @@ export const useV3PoolsLength = (chainIds: number[]) => { (results: UseQueryResult[]) => { return { data: results.reduce((acc, result, idx) => { - return { - ...acc, - [chainIds[idx]]: Number(result.data ?? 0), - } + Object.assign(acc, { [chainIds[idx]]: Number(result.data ?? 0) }) + return acc }, {} as { [key: `${number}`]: number }), pending: results.some((result) => result.isPending), } @@ -130,10 +128,7 @@ export const useV2PoolsLength = (chainIds: number[]) => { (results: UseQueryResult[]) => { return { data: results.reduce((acc, result, idx) => { - return { - ...acc, - [chainIds[idx]]: Number(result.data ?? 0), - } + return Object.assign(acc, { [chainIds[idx]]: Number(result.data ?? 0) }) }, {} as { [key: `${number}`]: number }), pending: results.some((result) => result.isPending), } @@ -172,10 +167,9 @@ export const useMultiChainV3PoolsStatus = (pools: UniversalFarmConfig[]) => { (results: UseQueryResult>, Error>[]) => { return { data: results.reduce((acc, result, idx) => { - return { - ...acc, + return Object.assign(acc, { [poolsEntries[idx][0]]: keyBy(result.data ?? [], ([, lpAddress]) => lpAddress), - } + }) }, {} as IPoolsStatusType), pending: results.some((result) => result.isPending), } @@ -259,12 +253,11 @@ export const useMultiChainPoolsTimeFrame = (pools: UniversalFarmConfig[]) => { return { data: results.reduce((acc, result, idx) => { let dataIdx = 0 - return { - ...acc, + return Object.assign(acc, { [poolsEntries[idx][0]]: keyBy(result.data ?? [], () => { return poolsEntries[idx][1][dataIdx++].lpAddress }), - } + }) }, {} as IPoolsTimeFrameType), pending: results.some((result) => result.isPending), } diff --git a/apps/web/src/state/farmsV4/state/poolApr/atom.ts b/apps/web/src/state/farmsV4/state/poolApr/atom.ts index 5b12e57aa1071..ab76b674f068d 100644 --- a/apps/web/src/state/farmsV4/state/poolApr/atom.ts +++ b/apps/web/src/state/farmsV4/state/poolApr/atom.ts @@ -21,10 +21,9 @@ export type LpApr = AprValue export const lpAprAtom = atom((get) => { const pools = get(poolsAtom) return pools.reduce((acc, pool) => { - return { - ...acc, - [`${pool.chainId}:${pool.lpAddress}`]: pool.lpApr ?? '0', - } + // eslint-disable-next-line no-param-reassign + acc[`${pool.chainId}:${pool.lpAddress}`] = pool.lpApr ?? '0' + return acc }, {} as LpApr) }) @@ -43,7 +42,6 @@ export type CakeApr = Record< > export const cakeAprAtom = atom({}) -export const cakeAprGetterAtom = atom export const cakeAprSetterAtom = atom(null, (get, set, newApr: CakeApr) => { const cakeApr = get(cakeAprAtom) set(cakeAprAtom, { ...cakeApr, ...newApr }) @@ -63,15 +61,14 @@ export const poolAprAtom = atom((get) => { const cakeAprs = get(cakeAprAtom) const merklAprs = get(merklAprAtom) - return Object.keys(lpAprs).reduce((acc, key) => { - return { - ...acc, - [key]: { - lpApr: lpAprs[key], - cakeApr: cakeAprs[key], - merklApr: merklAprs[key], - }, + return Object.entries(lpAprs).reduce((acc, [key, lpApr]) => { + // eslint-disable-next-line no-param-reassign + acc[key] = { + lpApr, + cakeApr: cakeAprs[key], + merklApr: merklAprs[key], } + return acc }, {} as PoolApr) }) @@ -79,5 +76,5 @@ export const emptyCakeAprPoolsAtom = atom((get) => { const pools = get(poolsAtom) const aprs = get(cakeAprAtom) - return pools.filter((pool) => !aprs[`${pool.chainId}:${pool.lpAddress}`]) + return pools.filter((pool) => !(`${pool.chainId}:${pool.lpAddress}` in aprs)) }) diff --git a/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts b/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts index ac66c902ca759..3687d38b4d01b 100644 --- a/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts +++ b/apps/web/src/state/farmsV4/state/poolApr/fetcher.ts @@ -7,8 +7,6 @@ import BigNumber from 'bignumber.js' import { SECONDS_PER_YEAR } from 'config' import { v2BCakeWrapperABI } from 'config/abi/v2BCakeWrapper' import dayjs from 'dayjs' -import { getCakePriceFromOracle } from 'hooks/useCakePrice' -import assign from 'lodash/assign' import groupBy from 'lodash/groupBy' import set from 'lodash/set' import { chainIdToExplorerInfoChainName, explorerApiClient } from 'state/info/api/client' @@ -20,13 +18,13 @@ import { erc20Abi } from 'viem' import { PoolInfo, StablePoolInfo, V2PoolInfo, V3PoolInfo } from '../type' import { CakeApr, MerklApr } from './atom' -export const getCakeApr = (pool: PoolInfo): Promise => { +export const getCakeApr = (pool: PoolInfo, cakePrice: BigNumber): Promise => { switch (pool.protocol) { case 'v3': - return v3PoolCakeAprBatcher.fetch(pool) + return v3PoolCakeAprBatcher.fetch({ pool, cakePrice }) case 'v2': case 'stable': - return v2PoolCakeAprBatcher.fetch(pool) + return v2PoolCakeAprBatcher.fetch({ pool, cakePrice }) default: return Promise.resolve({ [`${pool.chainId}:${pool.lpAddress}`]: { @@ -67,7 +65,6 @@ const masterChefV3CacheMap = new Map< { totalAllocPoint: bigint latestPeriodCakePerSecond: bigint - poolInfo: readonly [bigint, `0x${string}`, `0x${string}`, `0x${string}`, number, bigint, bigint] } >() @@ -82,17 +79,19 @@ export const getV3PoolCakeApr = async (pool: V3PoolInfo, cakePrice: BigNumber): } } - const hasCache = masterChefV3CacheMap.has(pool.chainId) - const [totalAllocPoint, latestPeriodCakePerSecond, poolInfo] = await Promise.all([ - hasCache ? masterChefV3CacheMap.get(pool.chainId)!.totalAllocPoint : masterChefV3.read.totalAllocPoint(), - hasCache - ? masterChefV3CacheMap.get(pool.chainId)!.latestPeriodCakePerSecond - : masterChefV3.read.latestPeriodCakePerSecond(), - hasCache ? masterChefV3CacheMap.get(pool.chainId)!.poolInfo : masterChefV3.read.poolInfo([BigInt(pool.pid)]), + masterChefV3CacheMap.get(pool.chainId)?.totalAllocPoint ?? masterChefV3.read.totalAllocPoint(), + masterChefV3CacheMap.get(pool.chainId)?.latestPeriodCakePerSecond ?? masterChefV3.read.latestPeriodCakePerSecond(), + masterChefV3.read.poolInfo([BigInt(pool.pid)]), ]) - if (!hasCache) masterChefV3CacheMap.set(pool.chainId, { totalAllocPoint, latestPeriodCakePerSecond, poolInfo }) + if (!masterChefV3CacheMap.has(pool.chainId)) { + masterChefV3CacheMap.set(pool.chainId, { + ...(masterChefV3CacheMap.get(pool.chainId) ?? {}), + totalAllocPoint, + latestPeriodCakePerSecond, + }) + } const cakePerYear = new BigNumber(SECONDS_PER_YEAR) .times(latestPeriodCakePerSecond.toString()) @@ -197,10 +196,10 @@ export const getV2PoolCakeApr = async ( } } -export const getMerklApr = async (chainId: number) => { +export const getMerklApr = async (chainId: number, signal?: AbortSignal) => { try { // @todo @ChefJerry merkl api cannot accept multiple chainIds, we need to batch fetch - const resp = await fetch(`https://api.angle.money/v2/merkl?chainIds=${chainId}&AMMs=pancakeswapv3`) + const resp = await fetch(`https://api.angle.money/v2/merkl?chainIds=${chainId}&AMMs=pancakeswapv3`, { signal }) if (resp.ok) { const result = await resp.json() if (!result[chainId] || !result[chainId].pools) return {} @@ -209,22 +208,26 @@ export const getMerklApr = async (chainId: number) => { if (!result[chainId].pools[poolId].aprs || !Object.keys(result[chainId].pools[poolId].aprs).length) return acc const apr = result[chainId].pools[poolId].aprs?.['Average APR (rewards / pool TVL)'] ?? '0' - return { - ...acc, - [key]: apr / 100, - } + // eslint-disable-next-line no-param-reassign + acc[key] = apr / 100 + return acc }, {} as MerklApr) } throw resp } catch (error) { + if (error instanceof Error) { + if (error.name === 'AbortError') { + throw error + } + } console.error('Failed to fetch merkl apr', error) return {} } } -export const getAllNetworkMerklApr = async () => { - const aprs = await Promise.all(supportedChainIdV4.map((chainId) => getMerklApr(chainId))) - return aprs.reduce((acc, apr) => assign(acc, apr), {}) +export const getAllNetworkMerklApr = async (signal?: AbortSignal) => { + const aprs = await Promise.all(supportedChainIdV4.map((chainId) => getMerklApr(chainId, signal))) + return aprs.reduce((acc, apr) => Object.assign(acc, apr), {}) } const getV3PoolsCakeAprByChainId = async (pools: V3PoolInfo[], chainId: number, cakePrice: BigNumber) => { @@ -237,13 +240,19 @@ const getV3PoolsCakeAprByChainId = async (pools: V3PoolInfo[], chainId: number, return pool.pid && pool.chainId === chainId }) - const masterChefV3Cache = masterChefV3CacheMap.get(chainId) + if (!validPools?.length) return {} + const [totalAllocPoint, latestPeriodCakePerSecond] = await Promise.all([ - masterChefV3Cache ? masterChefV3Cache.totalAllocPoint : masterChefV3.read.totalAllocPoint(), - masterChefV3Cache ? masterChefV3Cache.latestPeriodCakePerSecond : masterChefV3.read.latestPeriodCakePerSecond(), - getCakePriceFromOracle(), + masterChefV3CacheMap.get(chainId)?.totalAllocPoint ?? masterChefV3.read.totalAllocPoint(), + masterChefV3CacheMap.get(chainId)?.latestPeriodCakePerSecond ?? masterChefV3.read.latestPeriodCakePerSecond(), ]) + masterChefV3CacheMap.set(chainId, { + ...(masterChefV3CacheMap.get(chainId) ?? {}), + totalAllocPoint, + latestPeriodCakePerSecond, + }) + const poolInfoCalls = validPools.map( (pool) => ({ @@ -294,21 +303,23 @@ const getV3PoolsCakeAprByChainId = async (pools: V3PoolInfo[], chainId: number, }, {} as CakeApr) } -const getV3PoolsCakeApr = async (pools: V3PoolInfo[]): Promise => { - const cakePrice = await getCakePrice() +const getV3PoolsCakeApr = async (queries: { pool: V3PoolInfo; cakePrice: BigNumber }[]): Promise => { + const pools = queries.map((query) => query.pool) + const cakePrice = queries[0]?.cakePrice const poolsByChainId = groupBy(pools, 'chainId') const aprs = await Promise.all( Object.keys(poolsByChainId).map((chainId) => - getV3PoolsCakeAprByChainId(poolsByChainId[Number(chainId)], Number(chainId), cakePrice), + getV3PoolsCakeAprByChainId(poolsByChainId[chainId], Number(chainId), cakePrice), ), ) - return aprs.reduce((acc, apr) => assign(acc, apr), {}) + return aprs.reduce((acc, apr) => Object.assign(acc, apr), {}) } -const v3PoolCakeAprBatcher = create({ +const v3PoolCakeAprBatcher = create({ fetcher: getV3PoolsCakeApr, resolver: (items, query) => { - const key = `${query.chainId}:${query.lpAddress}` + const { pool } = query + const key = `${pool.chainId}:${pool.lpAddress}` return { [key]: items[key] } }, scheduler: windowedFiniteBatchScheduler({ @@ -366,18 +377,6 @@ const calcV2PoolApr = ({ } } -const cakePriceCache = { - value: new BigNumber(0), - timestamp: 0, -} -const getCakePrice = async () => { - const now = Date.now() - // cache for 10 minutes - if (now - cakePriceCache.timestamp < 1000 * 60 * 10) { - return cakePriceCache.value - } - return new BigNumber(await getCakePriceFromOracle()) -} const getV2PoolsCakeAprByChainId = async ( pools: Array, chainId: number, @@ -386,6 +385,8 @@ const getV2PoolsCakeAprByChainId = async ( const client = publicClient({ chainId }) const validPools = pools.filter((p) => p.chainId === chainId && p.bCakeWrapperAddress) + if (!validPools?.length) return {} + const rewardPerSecondCalls = validPools.map((pool) => { return { address: pool.bCakeWrapperAddress!, @@ -496,20 +497,24 @@ const getV2PoolsCakeAprByChainId = async ( return acc }, {} as CakeApr) } -const getV2PoolsCakeApr = async (pools: Array): Promise => { - const cakePrice = await getCakePrice() +const getV2PoolsCakeApr = async ( + queries: { pool: V2PoolInfo | StablePoolInfo; cakePrice: BigNumber }[], +): Promise => { + const pools = queries.map((query) => query.pool) + const cakePrice = queries[0]?.cakePrice const poolsByChainId = groupBy(pools, 'chainId') const aprs = await Promise.all( Object.keys(poolsByChainId).map((chainId) => - getV2PoolsCakeAprByChainId(poolsByChainId[Number(chainId)], Number(chainId), cakePrice), + getV2PoolsCakeAprByChainId(poolsByChainId[chainId], Number(chainId), cakePrice), ), ) - return aprs.reduce((acc, apr) => assign(acc, apr), {}) + return aprs.reduce((acc, apr) => Object.assign(acc, apr), {}) } -const v2PoolCakeAprBatcher = create({ +const v2PoolCakeAprBatcher = create({ fetcher: getV2PoolsCakeApr, resolver: (items, query) => { - const key = `${query.chainId}:${query.lpAddress}` + const { pool } = query + const key = `${pool.chainId}:${pool.lpAddress}` return { [key]: items[key] } }, scheduler: windowedFiniteBatchScheduler({ diff --git a/apps/web/src/state/farmsV4/state/poolApr/hooks.ts b/apps/web/src/state/farmsV4/state/poolApr/hooks.ts index da5a5b12bb0fe..bdd805ab7a2af 100644 --- a/apps/web/src/state/farmsV4/state/poolApr/hooks.ts +++ b/apps/web/src/state/farmsV4/state/poolApr/hooks.ts @@ -2,11 +2,20 @@ import { useQuery } from '@tanstack/react-query' import { SLOW_INTERVAL } from 'config/constants' import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { useCallback } from 'react' -import { extendPoolsAtom } from '../extendPools/atom' +import sha256 from 'crypto-js/sha256' +import memoize from 'lodash/memoize' +import { extendPoolsAtom } from 'state/farmsV4/state/extendPools/atom' +import { useCakePrice } from 'hooks/useCakePrice' +import { BIG_ZERO } from '@pancakeswap/utils/bigNumber' import { ChainIdAddressKey, PoolInfo } from '../type' import { CakeApr, cakeAprSetterAtom, emptyCakeAprPoolsAtom, merklAprAtom, poolAprAtom } from './atom' import { getAllNetworkMerklApr, getCakeApr, getLpApr } from './fetcher' +const generatePoolKey = memoize((pools) => { + const poolData = pools.map((pool) => `${pool.chainId}:${pool.lpAddress}`).join(',') + return sha256(poolData).toString() +}) + export const usePoolApr = ( key: ChainIdAddressKey | null, pool: PoolInfo, @@ -19,6 +28,7 @@ export const usePoolApr = ( const updateCakeApr = useSetAtom(cakeAprSetterAtom) const poolApr = useAtomValue(poolAprAtom)[key ?? ''] const [merklAprs, updateMerklApr] = useAtom(merklAprAtom) + const cakePrice = useCakePrice() const getMerklApr = useCallback(() => { if (Object.values(merklAprs).length === 0) { return getAllNetworkMerklApr().then((aprs) => { @@ -31,7 +41,7 @@ export const usePoolApr = ( const updateCallback = useCallback(async () => { try { const [cakeApr, lpApr, merklApr] = await Promise.all([ - getCakeApr(pool).then((apr) => { + getCakeApr(pool, cakePrice).then((apr) => { updateCakeApr(apr) return apr }), @@ -45,13 +55,16 @@ export const usePoolApr = ( updatePools([{ ...pool, lpApr: '0' }]) return '0' }), - , getMerklApr(), ]) return { lpApr: `${lpApr}`, cakeApr, merklApr, + } as { + lpApr: `${number}` + cakeApr: CakeApr[keyof CakeApr] + merklApr: `${number}` } } catch (error) { console.warn('error usePoolApr', error) @@ -59,9 +72,13 @@ export const usePoolApr = ( lpApr: '0', cakeApr: { value: '0' }, merklApr: '0', + } as { + lpApr: `${number}` + cakeApr: CakeApr[keyof CakeApr] + merklApr: `${number}` } } - }, [getMerklApr, pool, updateCakeApr, updatePools]) + }, [getMerklApr, pool, updateCakeApr, updatePools, cakePrice]) useQuery({ queryKey: ['apr', key], @@ -69,10 +86,10 @@ export const usePoolApr = ( // calcV3PoolApr depend on pool's TvlUsd // so if there are local pool without tvlUsd, don't to fetch queryFn // issue: PAN-3698 - enabled: typeof pool?.tvlUsd !== 'undefined' && !poolApr?.lpApr && !!key, - refetchInterval: 0, + enabled: typeof pool?.tvlUsd !== 'undefined' && !poolApr?.lpApr && !!key && cakePrice && cakePrice.gt(BIG_ZERO), refetchOnMount: false, refetchOnWindowFocus: false, + refetchOnReconnect: false, }) return { @@ -90,35 +107,23 @@ export const usePoolAprUpdater = () => { const pools = useAtomValue(emptyCakeAprPoolsAtom) const updateCakeApr = useSetAtom(cakeAprSetterAtom) const updateMerklApr = useSetAtom(merklAprAtom) - const fetchMerklApr = useCallback(async () => { - const merklAprs = await getAllNetworkMerklApr() - updateMerklApr(merklAprs) - }, [updateMerklApr]) - const fetchCakeApr = useCallback( - async (newPools: PoolInfo[]) => { - if (newPools && newPools.length) { - newPools.forEach((pool) => { - getCakeApr(pool).then((apr) => { - updateCakeApr(apr) - }) - }) - } - }, - [updateCakeApr], - ) + const cakePrice = useCakePrice() useQuery({ - queryKey: ['apr', 'merkl', fetchMerklApr], - queryFn: fetchMerklApr, + queryKey: ['apr', 'merkl', 'fetchMerklApr'], + queryFn: ({ signal }) => getAllNetworkMerklApr(signal).then(updateMerklApr), refetchInterval: SLOW_INTERVAL, refetchOnMount: false, refetchOnWindowFocus: false, }) useQuery({ - queryKey: ['apr', 'cake', fetchCakeApr], - queryFn: () => fetchCakeApr(pools), - enabled: pools && pools.length > 0, + queryKey: ['apr', 'cake', 'fetchCakeApr', generatePoolKey(pools)], + queryFn: () => + Promise.all(pools.map((pool) => getCakeApr(pool, cakePrice))).then((aprList) => { + updateCakeApr(aprList.reduce((acc, apr) => Object.assign(acc, apr), {})) + }), + enabled: pools?.length > 0 && cakePrice && cakePrice.gt(BIG_ZERO), refetchInterval: SLOW_INTERVAL, refetchOnMount: false, refetchOnWindowFocus: false, diff --git a/apps/web/src/state/farmsV4/state/utils.ts b/apps/web/src/state/farmsV4/state/utils.ts index 2f077f05aed2a..071b633b3d6a6 100644 --- a/apps/web/src/state/farmsV4/state/utils.ts +++ b/apps/web/src/state/farmsV4/state/utils.ts @@ -5,7 +5,7 @@ import { getTokenByAddress } from '@pancakeswap/tokens' import BN from 'bignumber.js' import { paths } from 'state/info/api/schema' import { safeGetAddress } from 'utils' -import { Address, isAddressEqual } from 'viem' +import { Address } from 'viem' import { PoolInfo } from './type' export const parseFarmPools = ( @@ -21,7 +21,7 @@ export const parseFarmPools = ( let feeTier = Number(pool.feeTier ?? 2500) if (pool.protocol === 'stable') { const stableConfig = LegacyRouter.stableSwapPairsByChainId[pool.chainId]?.find((pair) => { - return isAddressEqual(pair.stableSwapAddress, pool.id as Address) + return safeGetAddress(pair.stableSwapAddress) === safeGetAddress(pool.id as Address) }) if (stableConfig) { stableSwapAddress = safeGetAddress(stableConfig.stableSwapAddress) @@ -30,7 +30,7 @@ export const parseFarmPools = ( } } const localFarm = UNIVERSAL_FARMS.find( - (farm) => isAddressEqual(farm.lpAddress, lpAddress) && farm.chainId === pool.chainId, + (farm) => safeGetAddress(farm.lpAddress) === safeGetAddress(lpAddress) && farm.chainId === pool.chainId, ) let pid: number | undefined if (localFarm) { diff --git a/apps/web/src/utils/checksumAddress.ts b/apps/web/src/utils/checksumAddress.ts new file mode 100644 index 0000000000000..eb706e83a697f --- /dev/null +++ b/apps/web/src/utils/checksumAddress.ts @@ -0,0 +1,31 @@ +import { Address, InvalidAddressError, keccak256, stringToBytes } from 'viem' + +const addressRegex = /^0x[a-fA-F0-9]{40}$/ + +export function checksumAddress(address_: Address): Address { + if (!isAddress(address_)) throw new InvalidAddressError({ address: address_ }) + + const hexAddress = address_.substring(2).toLowerCase() + const hash = keccak256(stringToBytes(hexAddress), 'bytes') + + const address = hexAddress.split('') + for (let i = 0; i < 40; i += 2) { + // eslint-disable-next-line no-bitwise + if (hash[i >> 1] >> 4 >= 8 && address[i]) { + address[i] = address[i].toUpperCase() + } + // eslint-disable-next-line no-bitwise + if ((hash[i >> 1] & 0x0f) >= 8 && address[i + 1]) { + address[i + 1] = address[i + 1].toUpperCase() + } + } + + const result = `0x${address.join('')}` as const + return result +} + +function isAddress(address: string): address is Address { + if (!addressRegex.test(address)) return false + if (address.toLowerCase() === address) return true + return true +} diff --git a/apps/web/src/utils/index.ts b/apps/web/src/utils/index.ts index 023dd53a653d9..e7904e834f1fd 100644 --- a/apps/web/src/utils/index.ts +++ b/apps/web/src/utils/index.ts @@ -1,10 +1,20 @@ import { ChainId } from '@pancakeswap/chains' import { Currency } from '@pancakeswap/sdk' import { TokenAddressMap } from '@pancakeswap/token-lists' -import memoize from 'lodash/memoize' -import { Address, getAddress } from 'viem' +import { Address } from 'viem' import { bsc } from 'wagmi/chains' +import memoize from 'lodash/memoize' import { chains } from './wagmi' +import { checksumAddress } from './checksumAddress' + +export const isAddressEqual = (a?: any, b?: any) => { + if (!a || !b) return false + const a_ = safeGetAddress(a) + if (!a_) return false + const b_ = safeGetAddress(b) + if (!b_) return false + return a_ === b_ +} // returns the checksummed address if the address is valid, otherwise returns undefined export const safeGetAddress = memoize((value: any): Address | undefined => { @@ -13,7 +23,7 @@ export const safeGetAddress = memoize((value: any): Address | undefined => { if (typeof value === 'string' && !value.startsWith('0x')) { value_ = `0x${value}` } - return getAddress(value_) + return checksumAddress(value_) } catch { return undefined } diff --git a/apps/web/src/views/CakeStaking/hooks/useIsMigratedToVeCake.ts b/apps/web/src/views/CakeStaking/hooks/useIsMigratedToVeCake.ts index 428c818fc4c0f..095af34a5801d 100644 --- a/apps/web/src/views/CakeStaking/hooks/useIsMigratedToVeCake.ts +++ b/apps/web/src/views/CakeStaking/hooks/useIsMigratedToVeCake.ts @@ -1,5 +1,6 @@ import { ChainId } from '@pancakeswap/chains' -import { isAddressEqual, zeroAddress } from 'viem' +import { isAddressEqual } from 'utils' +import { zeroAddress } from 'viem' import { useVeCakeUserInfo } from './useVeCakeUserInfo' export const useIsMigratedToVeCake = (targetChainId?: ChainId) => { diff --git a/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalance.ts b/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalance.ts index 9bf74ba6c0468..35dcb7828e783 100644 --- a/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalance.ts +++ b/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalance.ts @@ -4,7 +4,8 @@ import BigNumber from 'bignumber.js' import { useActiveChainId } from 'hooks/useActiveChainId' import { useMemo } from 'react' import { getVeCakeAddress } from 'utils/addressHelpers' -import { Address, erc20Abi, isAddressEqual, zeroAddress } from 'viem' +import { isAddressEqual } from 'utils' +import { Address, erc20Abi, zeroAddress } from 'viem' import { CakePoolType } from '../types' import { useVeCakeUserInfo } from './useVeCakeUserInfo' diff --git a/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalanceOfAtTime.ts b/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalanceOfAtTime.ts index 558254d4c7256..a50c5f1a1da35 100644 --- a/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalanceOfAtTime.ts +++ b/apps/web/src/views/CakeStaking/hooks/useProxyVeCakeBalanceOfAtTime.ts @@ -5,7 +5,8 @@ import { veCakeABI } from 'config/abi/veCake' import { useActiveChainId } from 'hooks/useActiveChainId' import { useMemo } from 'react' import { getVeCakeAddress } from 'utils/addressHelpers' -import { Address, isAddressEqual, zeroAddress } from 'viem' +import { isAddressEqual } from 'utils' +import { Address, zeroAddress } from 'viem' import { CakePoolType } from '../types' import { useVeCakeUserInfo } from './useVeCakeUserInfo' diff --git a/apps/web/src/views/Farms/components/FarmCard/CardHeading.tsx b/apps/web/src/views/Farms/components/FarmCard/CardHeading.tsx index 35b1e80bae370..b361aba708a19 100644 --- a/apps/web/src/views/Farms/components/FarmCard/CardHeading.tsx +++ b/apps/web/src/views/Farms/components/FarmCard/CardHeading.tsx @@ -19,7 +19,8 @@ import { SwellTooltip } from 'components/SwellTooltip/SwellTooltip' import { TokenPairImage } from 'components/TokenImage' import { useHasSwellReward } from 'hooks/useHasSwellReward' import { styled } from 'styled-components' -import { Address, isAddressEqual } from 'viem' +import { isAddressEqual } from 'utils' +import { Address } from 'viem' import { bsc } from 'viem/chains' import { useHasCustomFarmLpTooltips } from 'views/Farms/hooks/useHasCustomFarmLpTooltips' import { useChainId } from 'wagmi' diff --git a/apps/web/src/views/Farms/components/FarmCard/V3/FarmV3Card.tsx b/apps/web/src/views/Farms/components/FarmCard/V3/FarmV3Card.tsx index 9c06716dc4f04..c3e1b6de2783a 100644 --- a/apps/web/src/views/Farms/components/FarmCard/V3/FarmV3Card.tsx +++ b/apps/web/src/views/Farms/components/FarmCard/V3/FarmV3Card.tsx @@ -10,10 +10,9 @@ import { useCallback, useMemo, useState } from 'react' import { type V3Farm } from 'state/farms/types' import { multiChainPaths } from 'state/info/constant' import { styled } from 'styled-components' -import { getBlockExploreLink } from 'utils' +import { getBlockExploreLink, isAddressEqual } from 'utils' import { getMerklLink, useMerklUserLink } from 'utils/getMerklLink' import { unwrappedToken } from 'utils/wrappedCurrency' -import { isAddressEqual } from 'viem' import { AddLiquidityV3Modal } from 'views/AddLiquidityV3/Modal' import { useFarmV3Multiplier } from 'views/Farms/hooks/v3/useFarmV3Multiplier' import { StatusView } from '../../YieldBooster/components/bCakeV3/StatusView' diff --git a/apps/web/src/views/Farms/components/FarmTable/Actions/ActionPanel.tsx b/apps/web/src/views/Farms/components/FarmTable/Actions/ActionPanel.tsx index c7afaeddd8357..019169bebe95d 100644 --- a/apps/web/src/views/Farms/components/FarmTable/Actions/ActionPanel.tsx +++ b/apps/web/src/views/Farms/components/FarmTable/Actions/ActionPanel.tsx @@ -24,10 +24,9 @@ import { FC, useContext, useMemo } from 'react' import { type V3Farm } from 'state/farms/types' import { ChainLinkSupportChains, multiChainPaths } from 'state/info/constant' import { css, keyframes, styled } from 'styled-components' -import { getBlockExploreLink } from 'utils' +import { getBlockExploreLink, isAddressEqual } from 'utils' import { useMerklUserLink } from 'utils/getMerklLink' import { unwrappedToken } from 'utils/wrappedCurrency' -import { isAddressEqual } from 'viem' import { AddLiquidityV3Modal } from 'views/AddLiquidityV3/Modal' import { SELECTOR_TYPE } from 'views/AddLiquidityV3/types' import { V2Farm } from 'views/Farms/FarmsV3' diff --git a/apps/web/src/views/Farms/components/FarmTable/Farm.tsx b/apps/web/src/views/Farms/components/FarmTable/Farm.tsx index 4125171332036..5b224c19e6acc 100644 --- a/apps/web/src/views/Farms/components/FarmTable/Farm.tsx +++ b/apps/web/src/views/Farms/components/FarmTable/Farm.tsx @@ -8,7 +8,8 @@ import { TokenPairImage } from 'components/TokenImage' import { USDPlusWarningTooltip } from 'components/USDPlusWarningTooltip' import { useHasSwellReward } from 'hooks/useHasSwellReward' import { useMemo } from 'react' -import { Address, isAddressEqual } from 'viem' +import { isAddressEqual } from 'utils' +import { Address } from 'viem' import { bsc } from 'viem/chains' import { useHasCustomFarmLpTooltips } from 'views/Farms/hooks/useHasCustomFarmLpTooltips' diff --git a/apps/web/src/views/Farms/hooks/useLmPoolLiquidity.ts b/apps/web/src/views/Farms/hooks/useLmPoolLiquidity.ts index 5c7719e60a249..a119e7dbfdb5b 100644 --- a/apps/web/src/views/Farms/hooks/useLmPoolLiquidity.ts +++ b/apps/web/src/views/Farms/hooks/useLmPoolLiquidity.ts @@ -1,8 +1,8 @@ import { pancakeV3PoolABI } from '@pancakeswap/v3-sdk' import { useQuery } from '@tanstack/react-query' -import { safeGetAddress } from 'utils' +import { safeGetAddress, isAddressEqual } from 'utils' import { publicClient } from 'utils/wagmi' -import { Address, isAddressEqual, parseAbiItem, zeroAddress } from 'viem' +import { Address, parseAbiItem, zeroAddress } from 'viem' const lmPoolAbi = [parseAbiItem('function lmLiquidity() view returns (uint128)')] const fetchLmPoolLiquidity = async (lpAddress: Address, chainId: number): Promise => { diff --git a/apps/web/src/views/GaugesVoting/hooks/useEpochVotePower.ts b/apps/web/src/views/GaugesVoting/hooks/useEpochVotePower.ts index 1395676508461..92d6c2752d21d 100644 --- a/apps/web/src/views/GaugesVoting/hooks/useEpochVotePower.ts +++ b/apps/web/src/views/GaugesVoting/hooks/useEpochVotePower.ts @@ -1,7 +1,8 @@ import { useQuery } from '@tanstack/react-query' import useAccountActiveChain from 'hooks/useAccountActiveChain' import { useVeCakeContract } from 'hooks/useContract' -import { Address, isAddressEqual, zeroAddress } from 'viem' +import { isAddressEqual } from 'utils' +import { Address, zeroAddress } from 'viem' import { useVeCakeUserInfo } from 'views/CakeStaking/hooks/useVeCakeUserInfo' import { useNextEpochStart } from './useEpochTime' diff --git a/apps/web/src/views/GaugesVoting/hooks/useUserVote.ts b/apps/web/src/views/GaugesVoting/hooks/useUserVote.ts index f79867e88bb32..244e64a3ad16e 100644 --- a/apps/web/src/views/GaugesVoting/hooks/useUserVote.ts +++ b/apps/web/src/views/GaugesVoting/hooks/useUserVote.ts @@ -6,7 +6,8 @@ import useAccountActiveChain from 'hooks/useAccountActiveChain' import { useGaugesVotingContract } from 'hooks/useContract' import { useEffect, useMemo } from 'react' import { publicClient as getPublicClient } from 'utils/viem' -import { Address, Hex, isAddressEqual, zeroAddress } from 'viem' +import { isAddressEqual } from 'utils' +import { Address, Hex, zeroAddress } from 'viem' import { useCurrentBlockTimestamp } from 'views/CakeStaking/hooks/useCurrentBlockTimestamp' import { useVeCakeUserInfo } from 'views/CakeStaking/hooks/useVeCakeUserInfo' import { CakePoolType } from 'views/CakeStaking/types' diff --git a/apps/web/src/views/GaugesVoting/hooks/useUserVoteGauges.ts b/apps/web/src/views/GaugesVoting/hooks/useUserVoteGauges.ts index e09a266620aa8..aef9b8fb585c9 100644 --- a/apps/web/src/views/GaugesVoting/hooks/useUserVoteGauges.ts +++ b/apps/web/src/views/GaugesVoting/hooks/useUserVoteGauges.ts @@ -3,7 +3,8 @@ import useAccountActiveChain from 'hooks/useAccountActiveChain' import { useGaugesVotingContract } from 'hooks/useContract' import { useMemo } from 'react' import { publicClient as getPublicClient } from 'utils/viem' -import { Hex, isAddressEqual, zeroAddress } from 'viem' +import { isAddressEqual } from 'utils' +import { Hex, zeroAddress } from 'viem' import { useVeCakeUserInfo } from 'views/CakeStaking/hooks/useVeCakeUserInfo' import { CakePoolType } from 'views/CakeStaking/types' import { useGauges } from './useGauges' diff --git a/apps/web/src/views/PoolDetail/components/BreadcrumbNav.tsx b/apps/web/src/views/PoolDetail/components/BreadcrumbNav.tsx index 3c8073a8b5281..0cda2ff1eb709 100644 --- a/apps/web/src/views/PoolDetail/components/BreadcrumbNav.tsx +++ b/apps/web/src/views/PoolDetail/components/BreadcrumbNav.tsx @@ -1,6 +1,7 @@ import { useTranslation } from '@pancakeswap/localization' -import { Breadcrumbs, CopyButton, Flex, Link, ScanLink, Text } from '@pancakeswap/uikit' +import { Breadcrumbs, CopyButton, Flex, ScanLink, Text } from '@pancakeswap/uikit' import { ChainLinkSupportChains, multiChainId, multiChainScan } from 'state/info/constant' +import { NextLinkFromReactRouter } from '@pancakeswap/widgets-internal' import { useChainNameByQuery } from 'state/info/hooks' import { getBlockExploreLink } from 'utils' import { usePoolSymbol } from '../hooks/usePoolSymbol' @@ -17,9 +18,9 @@ export const BreadcrumbNav: React.FC = () => { return ( - + {t('Farms')} - + {poolSymbol} diff --git a/apps/web/src/views/PoolDetail/components/PoolStatus.tsx b/apps/web/src/views/PoolDetail/components/PoolStatus.tsx index a3a083427acee..59df54bca8690 100644 --- a/apps/web/src/views/PoolDetail/components/PoolStatus.tsx +++ b/apps/web/src/views/PoolDetail/components/PoolStatus.tsx @@ -6,7 +6,8 @@ import { useMemo } from 'react' import { PoolInfo } from 'state/farmsV4/state/type' import { getLpFeesAndApr } from 'utils/getLpFeesAndApr' import { getPercentChange } from 'utils/infoDataHelpers' -import { Address, isAddressEqual } from 'viem' +import { isAddressEqual } from 'utils' +import { Address } from 'viem' import { formatDollarAmount } from 'views/V3Info/utils/numbers' import { ChangePercent } from './ChangePercent' import { PoolTokens } from './PoolTokens' diff --git a/apps/web/src/views/Pools/components/PoolsTable/PoolRow.tsx b/apps/web/src/views/Pools/components/PoolsTable/PoolRow.tsx index e84a8fb172ab8..16f3f50d483c8 100644 --- a/apps/web/src/views/Pools/components/PoolsTable/PoolRow.tsx +++ b/apps/web/src/views/Pools/components/PoolsTable/PoolRow.tsx @@ -11,7 +11,8 @@ import { Token } from '@pancakeswap/swap-sdk-core' import { bscTokens } from '@pancakeswap/tokens' import { GiftTooltip } from 'components/GiftTooltip/GiftTooltip' import styled from 'styled-components' -import { Address, isAddressEqual } from 'viem' +import { isAddressEqual } from 'utils' +import { Address } from 'viem' import { bsc } from 'viem/chains' import { useIsUserDelegated } from 'views/CakeStaking/hooks/useIsUserDelegated' import { useChainId } from 'wagmi' diff --git a/apps/web/src/views/universalFarms/PositionPage.tsx b/apps/web/src/views/universalFarms/PositionPage.tsx index b89d68b0f9f8d..e0900650c0269 100644 --- a/apps/web/src/views/universalFarms/PositionPage.tsx +++ b/apps/web/src/views/universalFarms/PositionPage.tsx @@ -27,7 +27,6 @@ import TransactionsModal from 'components/App/Transactions/TransactionsModal' import GlobalSettings from 'components/Menu/GlobalSettings' import { SettingsMode } from 'components/Menu/GlobalSettings/types' import { ASSET_CDN } from 'config/constants/endpoints' -import assign from 'lodash/assign' import intersection from 'lodash/intersection' import NextLink from 'next/link' import { useCallback, useEffect, useMemo, useState } from 'react' @@ -169,7 +168,7 @@ const useV3Positions = ({ const v3PositionsWithStatus = useMemo( () => v3Positions.map((pos, idx) => - assign(pos, { + Object.assign(pos, { status: getPoolStatus(pos, pools[idx][1]), }), ), diff --git a/apps/web/src/views/universalFarms/components/PoolAprButton/V2PoolAprModal.tsx b/apps/web/src/views/universalFarms/components/PoolAprButton/V2PoolAprModal.tsx index f4fd841e01c0e..57c075610ec53 100644 --- a/apps/web/src/views/universalFarms/components/PoolAprButton/V2PoolAprModal.tsx +++ b/apps/web/src/views/universalFarms/components/PoolAprButton/V2PoolAprModal.tsx @@ -9,7 +9,8 @@ import { useMemo } from 'react' import { useAccountPositionDetailByPool } from 'state/farmsV4/hooks' import { StablePoolInfo, V2PoolInfo } from 'state/farmsV4/state/type' import getLiquidityUrlPathParts from 'utils/getLiquidityUrlPathParts' -import { Address, isAddressEqual } from 'viem' +import { Address } from 'viem' +import { isAddressEqual } from 'utils' import { useMasterChefV2Data } from 'views/Farms/hooks/useMasterChefV2Data' import { useV2LpTokenTotalSupply } from 'views/Farms/hooks/useV2LpTokenTotalSupply' import { useBCakeWrapperRewardPerSecond } from 'views/universalFarms/hooks/useBCakeWrapperInfo' diff --git a/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx b/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx index 443a8511338ae..672a0aff5654c 100644 --- a/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx +++ b/apps/web/src/views/universalFarms/components/PoolListItemAction.tsx @@ -10,7 +10,7 @@ import { memo, useCallback, useMemo } from 'react' import type { PoolInfo } from 'state/farmsV4/state/type' import { multiChainPaths } from 'state/info/constant' import styled, { css } from 'styled-components' -import { isAddressEqual } from 'viem' +import { isAddressEqual } from 'utils' import { useAccount } from 'wagmi' import { PERSIST_CHAIN_KEY } from 'config/constants' import { addQueryToPath } from 'utils/addQueryToPath' diff --git a/apps/web/src/views/universalFarms/hooks/useEstimateUserMultiplier.ts b/apps/web/src/views/universalFarms/hooks/useEstimateUserMultiplier.ts index 9c21e60268c58..25a886dd62aaf 100644 --- a/apps/web/src/views/universalFarms/hooks/useEstimateUserMultiplier.ts +++ b/apps/web/src/views/universalFarms/hooks/useEstimateUserMultiplier.ts @@ -21,7 +21,6 @@ export const useEstimateUserMultiplier = (chainId: number, tokenId?: bigint) => refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false, - refetchInterval: false, staleTime: Infinity, }) } diff --git a/apps/web/src/views/universalFarms/hooks/useMasterChefV3FarmBoosterAddress.ts b/apps/web/src/views/universalFarms/hooks/useMasterChefV3FarmBoosterAddress.ts index 5985bfd41478e..3440ea8c5bae0 100644 --- a/apps/web/src/views/universalFarms/hooks/useMasterChefV3FarmBoosterAddress.ts +++ b/apps/web/src/views/universalFarms/hooks/useMasterChefV3FarmBoosterAddress.ts @@ -12,7 +12,6 @@ export const useMasterChefV3FarmBoosterAddress = (chainId: number) => { refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false, - refetchInterval: false, staleTime: Infinity, }) } diff --git a/packages/farms/src/getLegacyFarmConfig.ts b/packages/farms/src/getLegacyFarmConfig.ts index 002561ab752a1..f19cd1baeef9f 100644 --- a/packages/farms/src/getLegacyFarmConfig.ts +++ b/packages/farms/src/getLegacyFarmConfig.ts @@ -1,6 +1,5 @@ import { ChainId, getChainName } from '@pancakeswap/chains' import { getStableSwapPools } from '@pancakeswap/stable-swap-sdk' -import { isAddressEqual } from 'viem' import { supportedChainIdV4 } from './const' import { SerializedFarmConfig, SerializedFarmPublicData, UniversalFarmConfig } from './types' @@ -17,7 +16,10 @@ export async function getLegacyFarmConfig(chainId?: ChainId): Promise 0) { universalConfig = universalConfig.filter((f) => { - return !!f.pid && !legacyFarmConfig.some((legacy) => isAddressEqual(legacy.lpAddress, f.lpAddress)) + return ( + !!f.pid && + !legacyFarmConfig.some((legacy) => legacy.lpAddress?.toLowerCase() === f.lpAddress?.toLowerCase()) + ) }) } @@ -27,7 +29,7 @@ export async function getLegacyFarmConfig(chainId?: ChainId): Promise { - return isAddressEqual(s.lpAddress, farm.lpAddress) + return s.lpAddress?.toLowerCase() === farm.lpAddress?.toLowerCase() }) : undefined const bCakeWrapperAddress = 'bCakeWrapperAddress' in farm ? farm.bCakeWrapperAddress : undefined diff --git a/packages/farms/src/utils.ts b/packages/farms/src/utils.ts index 92f15dcdd530c..e567c3f53054b 100644 --- a/packages/farms/src/utils.ts +++ b/packages/farms/src/utils.ts @@ -1,6 +1,5 @@ import { ChainId } from '@pancakeswap/chains' import { getStableSwapPools } from '@pancakeswap/stable-swap-sdk' -import { isAddressEqual } from 'viem' import { ComputedFarmConfigV3, FarmV3Data, @@ -47,7 +46,7 @@ const formatStableUniversalFarmToSerializedFarm = ( ): LegacyStableFarmConfig | undefined => { const { chainId, lpAddress, pid, token0, token1, stableSwapAddress, bCakeWrapperAddress } = farm const stablePair = getStableSwapPools(chainId).find((pair) => { - return isAddressEqual(pair.stableSwapAddress, stableSwapAddress) + return pair.stableSwapAddress?.toLowerCase() === stableSwapAddress?.toLowerCase() }) if (!stablePair) { diff --git a/packages/smart-router/evm/v3-router/getRoutesWithValidQuote.ts b/packages/smart-router/evm/v3-router/getRoutesWithValidQuote.ts index 10b403ef50de1..2139d2e23ffcf 100644 --- a/packages/smart-router/evm/v3-router/getRoutesWithValidQuote.ts +++ b/packages/smart-router/evm/v3-router/getRoutesWithValidQuote.ts @@ -65,7 +65,10 @@ export async function getRoutesWithValidQuote({ }) const chunks = chunk(routesWithoutQuote, 10) const result = await Promise.all(chunks.map(getQuotes)) - const quotes = result.reduce((acc, cur) => [...acc, ...cur], []) + const quotes = result.reduce((acc, cur) => { + acc.push(...cur) + return acc + }, []) logger.metric('Get quotes', 'success, got', quotes.length, 'quoted routes', quotes) return quotes } diff --git a/packages/smart-router/evm/v3-router/providers/poolProviders/getCandidatePools.ts b/packages/smart-router/evm/v3-router/providers/poolProviders/getCandidatePools.ts index bbba24605895d..77bc073eae045 100644 --- a/packages/smart-router/evm/v3-router/providers/poolProviders/getCandidatePools.ts +++ b/packages/smart-router/evm/v3-router/providers/poolProviders/getCandidatePools.ts @@ -43,5 +43,8 @@ export async function getCandidatePools({ }), ) - return poolSets.reduce((acc, cur) => [...acc, ...cur], []) + return poolSets.reduce((acc, cur) => { + acc.push(...cur) + return acc + }, []) } diff --git a/packages/smart-router/evm/v3-router/providers/poolProviders/poolTvlSelectors.ts b/packages/smart-router/evm/v3-router/providers/poolProviders/poolTvlSelectors.ts index 5d5880e2dae9a..e6dce682b0521 100644 --- a/packages/smart-router/evm/v3-router/providers/poolProviders/poolTvlSelectors.ts +++ b/packages/smart-router/evm/v3-router/providers/poolProviders/poolTvlSelectors.ts @@ -56,7 +56,10 @@ function poolSelectorFactory

({ .sort(sortByTvl) .slice(0, POOL_SELECTION_CONFIG.topNWithEachBaseToken) }) - .reduce((acc, cur) => [...acc, ...cur], []) + .reduce((acc, cur) => { + acc.push(...cur) + return acc + }, []) .sort(sortByTvl) .slice(0, POOL_SELECTION_CONFIG.topNWithBaseToken) @@ -79,7 +82,10 @@ function poolSelectorFactory

({ .sort(sortByTvl) .slice(0, POOL_SELECTION_CONFIG.topNWithEachBaseToken) }) - .reduce((acc, cur) => [...acc, ...cur], []) + .reduce((acc, cur) => { + acc.push(...cur) + return acc + }, []) .sort(sortByTvl) .slice(0, POOL_SELECTION_CONFIG.topNWithBaseToken) @@ -185,9 +191,17 @@ function poolSelectorFactory

({ ) }) }) - .reduce((acc, cur) => [...acc, ...cur], []) + .reduce((acc, cur) => { + acc.push(...cur) + return acc + }, []) // Uniq - .reduce((acc, cur) => (acc.some((p) => p === cur) ? acc : [...acc, cur]), []) + .reduce((acc, cur) => { + if (!acc.some((p) => p === cur)) { + acc.push(cur) + } + return acc + }, []) .sort(sortByTvl) .slice(0, POOL_SELECTION_CONFIG.topNSecondHop) diff --git a/packages/smart-router/evm/v4-router/queries/getV3Pools.ts b/packages/smart-router/evm/v4-router/queries/getV3Pools.ts index 0119f0cf026c3..e75c2db8f3de0 100644 --- a/packages/smart-router/evm/v4-router/queries/getV3Pools.ts +++ b/packages/smart-router/evm/v4-router/queries/getV3Pools.ts @@ -87,7 +87,10 @@ async function fillPoolsWithTicks({ pools, clientProvider, gasLimit }: FillPools const { gasLimit: gasLimitPerCall, retryGasMultiplier } = getV3PoolFetchConfig(chainId) const bitmapIndexes = pools .map(({ tick, fee }, i) => buildBitmapIndexList<{ poolIndex: number }>({ currentTick: tick, fee, poolIndex: i })) - .reduce<{ bitmapIndex: number; poolIndex: number }[]>((acc, cur) => [...acc, ...cur], []) + .reduce<{ bitmapIndex: number; poolIndex: number }[]>((acc, cur) => { + acc.push(...cur) + return acc + }, []) const res = await multicallByGasLimit( bitmapIndexes.map(({ poolIndex, bitmapIndex }) => ({ target: tickLensAddress as Address,