From fbac8f95d764022fa2f7b96b5c495fde87345a30 Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Mon, 23 Oct 2023 21:32:35 -0400 Subject: [PATCH] Reduces number of total RPC calls - memoizes components - uses isClient/setClient for useEffects with rpc calls to prevent double calls --- .../components/Pools/PoolCardBody.tsx | 52 ++++++---- .../components/Pools/PoolHeader.tsx | 99 ++++++++++--------- .../contexts/UserProvider.tsx | 25 +++-- .../synapse-interface/pages/pool/PoolBody.tsx | 12 ++- .../synapse-interface/pages/pool/[poolId].tsx | 9 +- .../pages/pools/PoolCard.tsx | 78 +++++++++------ .../pages/pools/PoolCards.tsx | 31 ++++-- .../utils/actions/getPoolApyData.ts | 34 +++---- .../utils/actions/getPoolData.ts | 95 +++++++++++------- .../synapse-interface/utils/types/index.tsx | 4 +- 10 files changed, 263 insertions(+), 176 deletions(-) diff --git a/packages/synapse-interface/components/Pools/PoolCardBody.tsx b/packages/synapse-interface/components/Pools/PoolCardBody.tsx index 1b7f06d54c..b2e6413b68 100644 --- a/packages/synapse-interface/components/Pools/PoolCardBody.tsx +++ b/packages/synapse-interface/components/Pools/PoolCardBody.tsx @@ -1,31 +1,45 @@ +import { Token } from '@/utils/types' import _ from 'lodash' import numeral from 'numeral' +import { memo } from 'react' import { LoaderIcon } from 'react-hot-toast' -export const PoolCardBody = ({ pool, poolData, poolApyData }) => { - const format = poolData.totalLockedUSD > 1000000 ? '$0,0.0' : '$0,0' - return ( -
-
- -
-
-
- {poolData && numeral(poolData.totalLockedUSD).format(format)} +export const PoolCardBody = memo( + ({ + pool, + poolData, + poolApyData, + }: { + pool: Token + poolData: any + poolApyData: any + }) => { + const format = poolData.totalLockedUSD > 1000000 ? '$0,0.0' : '$0,0' + return ( +
+
+ +
+
+
+ {poolData && numeral(poolData.totalLockedUSD).format(format)} +
+ + {pool.priceUnits} +
- {pool.priceUnits}
-
-
- +
+ +
-
- ) -} + ) + } +) -const PoolTokenIcons = ({ pool }) => { +const PoolTokenIcons = memo(({ pool }: { pool: Token }) => { if (pool.poolTokens.length === 3) { return (
@@ -67,7 +81,7 @@ const PoolTokenIcons = ({ pool }) => {
) } -} +}) const ApyDisplay = ({ pool, poolApyData }) => { if (!pool.incentivized) { diff --git a/packages/synapse-interface/components/Pools/PoolHeader.tsx b/packages/synapse-interface/components/Pools/PoolHeader.tsx index 4a38907898..1fa3fc1a5a 100644 --- a/packages/synapse-interface/components/Pools/PoolHeader.tsx +++ b/packages/synapse-interface/components/Pools/PoolHeader.tsx @@ -1,64 +1,69 @@ import _ from 'lodash' -import { useMemo } from 'react' +import { memo, useMemo } from 'react' import Link from 'next/link' -import { useNetwork } from 'wagmi' +import { Address, useNetwork } from 'wagmi' import { getPoolUrl } from '@urls' import { CHAINS_BY_ID } from '@/constants/chains' import { usePortfolioState } from '@/slices/portfolio/hooks' import { RightArrow } from '@/components/icons/RightArrow' +import { Token } from '@/utils/types' -export const PoolHeader = ({ pool, address }) => { - const { chain: connectedChain } = useNetwork() - const chain = CHAINS_BY_ID[pool.chainId] - const { balancesAndAllowances } = usePortfolioState() - const canDeposit = useMemo(() => { - const balancesForChain = _(balancesAndAllowances[pool.chainId]) - .pickBy((value, _key) => value.balance > 0n) - .value() +export const PoolHeader = memo( + ({ pool, address }: { pool: Token; address: Address }) => { + const { chain: connectedChain } = useNetwork() + const chain = CHAINS_BY_ID[pool.chainId] + const { balancesAndAllowances } = usePortfolioState() + const canDeposit = useMemo(() => { + const balancesForChain = _(balancesAndAllowances[pool.chainId]) + .pickBy((value, _key) => value.balance > 0n) + .value() - if (Object.keys(balancesForChain).length === 0) return false + if (Object.keys(balancesForChain).length === 0) return false - if (!address) { - return null - } + if (!address) { + return null + } - return _.some(pool.nativeTokens, (poolToken) => { - const poolAddress = _.get(poolToken, `addresses.${pool.chainId}`) - return _.some(balancesForChain, (balance) => { - return poolAddress === _.get(balance, `token.addresses.${pool.chainId}`) + return _.some(pool.nativeTokens, (poolToken) => { + const poolAddress = _.get(poolToken, `addresses.${pool.chainId}`) + return _.some(balancesForChain, (balance) => { + return ( + poolAddress === _.get(balance, `token.addresses.${pool.chainId}`) + ) + }) }) - }) - }, [pool, balancesAndAllowances, address]) - return ( -
-
- -
{chain.name}
-
{pool.symbol}
- {connectedChain && connectedChain.id === pool.chainId && ( - - )} -
+ }, [pool, balancesAndAllowances, address]) + return ( +
+
+ +
{chain.name}
+
{pool.symbol}
+ {connectedChain && connectedChain.id === pool.chainId && ( + + )} +
- {canDeposit && pool.incentivized ? ( - -
-
Deposit
- + {canDeposit && pool.incentivized ? ( + +
+
Deposit
+ +
+ + ) : ( +
+
- - ) : ( -
- -
- )} -
- ) -} + )} +
+ ) + } +) const ConnectedIndicator = () => { return ( diff --git a/packages/synapse-interface/contexts/UserProvider.tsx b/packages/synapse-interface/contexts/UserProvider.tsx index 4af7e5a9cd..876ed75991 100644 --- a/packages/synapse-interface/contexts/UserProvider.tsx +++ b/packages/synapse-interface/contexts/UserProvider.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useRef } from 'react' +import { createContext, useContext, useEffect, useRef, useState } from 'react' import { Chain, useAccount, useNetwork } from 'wagmi' import { segmentAnalyticsEvent } from './SegmentAnalyticsProvider' import { useRouter } from 'next/router' @@ -18,6 +18,7 @@ const WalletStatusContext = createContext(undefined) export const UserProvider = ({ children }) => { const dispatch = useAppDispatch() const { chain } = useNetwork() + const [isClient, setIsClient] = useState(false) const router = useRouter() const { query, pathname } = router const { address, connector } = useAccount({ @@ -40,6 +41,18 @@ export const UserProvider = ({ children }) => { }, [chain]) const prevChain = prevChainRef.current + useEffect(() => { + setIsClient(true) + }, []) + + useEffect(() => { + if (isClient) { + dispatch(fetchSynPrices()) + dispatch(fetchEthPrice()) + dispatch(fetchAvaxPrice()) + } + }, [isClient]) + useEffect(() => { if (chain) { dispatch(setSwapChainId(chain.id)) @@ -65,7 +78,7 @@ export const UserProvider = ({ children }) => { useEffect(() => { ;(async () => { - if (address && chain?.id) { + if (isClient && address && chain?.id) { try { await dispatch(fetchAndStorePortfolioBalances(address)) } catch (error) { @@ -77,13 +90,7 @@ export const UserProvider = ({ children }) => { dispatch(resetPortfolioState()) } })() - }, [chain, address]) - - useEffect(() => { - dispatch(fetchSynPrices()) - dispatch(fetchEthPrice()) - dispatch(fetchAvaxPrice()) - }, []) + }, [chain, address, isClient]) return ( diff --git a/packages/synapse-interface/pages/pool/PoolBody.tsx b/packages/synapse-interface/pages/pool/PoolBody.tsx index ac79974bda..1554762aac 100644 --- a/packages/synapse-interface/pages/pool/PoolBody.tsx +++ b/packages/synapse-interface/pages/pool/PoolBody.tsx @@ -27,6 +27,7 @@ const PoolBody = ({ address?: Address connectedChainId?: number }) => { + const [isClient, setIsClient] = useState(false) const { chains, switchNetwork } = useSwitchNetwork() const { openConnectModal } = useConnectModal() @@ -35,19 +36,22 @@ const PoolBody = ({ const { pool, poolAPYData } = useSelector( (state: RootState) => state.poolData ) - const { poolUserData } = useSelector((state: RootState) => state.poolUserData) const [stakedBalance, setStakedBalance] = useState({ amount: 0n, reward: 0n, }) useEffect(() => { - if (pool) { + setIsClient(true) + }, []) + + useEffect(() => { + if (pool && isClient) { segmentAnalyticsEvent(`[Pool] arrives`, { poolName: pool?.poolName, }) } - if (address) { + if (address && isClient) { getStakedBalance( address as Address, pool.chainId, @@ -62,7 +66,7 @@ const PoolBody = ({ } else { setStakedBalance({ amount: 0n, reward: 0n }) } - }, []) + }, [isClient]) if (!pool) return null diff --git a/packages/synapse-interface/pages/pool/[poolId].tsx b/packages/synapse-interface/pages/pool/[poolId].tsx index d364b3cdad..88702a004b 100644 --- a/packages/synapse-interface/pages/pool/[poolId].tsx +++ b/packages/synapse-interface/pages/pool/[poolId].tsx @@ -37,12 +37,17 @@ const PoolPage = () => { const { address } = useAccount() const { chain } = useNetwork() const [connectedChainId, setConnectedChainId] = useState(0) + const [isClient, setIsClient] = useState(false) const { pool, isLoading } = useSelector((state: RootState) => state.poolData) const dispatch: any = useDispatch() + useEffect(() => { + setIsClient(true) + }, []) + useEffect(() => { const handleRouteChange = () => { dispatch(resetPoolData()) @@ -62,11 +67,11 @@ const PoolPage = () => { }, [chain]) useEffect(() => { - if (poolId) { + if (poolId && isClient) { dispatch(resetPoolData()) dispatch(fetchPoolData({ poolName: String(poolId) })) } - }, [poolId, address]) + }, [poolId, address, isClient]) return ( diff --git a/packages/synapse-interface/pages/pools/PoolCard.tsx b/packages/synapse-interface/pages/pools/PoolCard.tsx index 9a0d847575..798c4fc885 100644 --- a/packages/synapse-interface/pages/pools/PoolCard.tsx +++ b/packages/synapse-interface/pages/pools/PoolCard.tsx @@ -8,7 +8,6 @@ import { Token } from '@types' import { STAKE_PATH, getPoolUrl } from '@urls' import { getSinglePoolData } from '@utils/actions/getPoolData' import { getPoolApyData } from '@utils/actions/getPoolApyData' -import { useAppSelector } from '@/store/hooks' import { usePortfolioState } from '@/slices/portfolio/hooks' import { getStakedBalance } from '@/utils/actions/getStakedBalance' import { formatBigIntToString } from '@/utils/bigint/format' @@ -19,13 +18,18 @@ import { PoolCardBody } from '../../components/Pools/PoolCardBody' const PoolCard = memo( ({ pool, - chainId, address, + ethPrice, + avaxPrice, + synPrices, }: { pool: Token - chainId: number address: string + ethPrice: any + avaxPrice: any + synPrices: any }) => { + const [isClient, setIsClient] = useState(false) const [poolData, setPoolData] = useState(undefined) const [poolApyData, setPoolApyData] = useState(undefined) const [stakedBalance, setStakedBalance] = useState({ @@ -33,43 +37,53 @@ const PoolCard = memo( reward: 0n, }) const { isDisconnected } = useAccount() - const { synPrices, ethPrice, avaxPrice } = useAppSelector( - (state) => state.priceData - ) let popup: string + useEffect(() => { + setIsClient(true) + }, []) useEffect(() => { - if (chainId && pool) { + if (pool && isClient) { // TODO - separate the apy and tvl so they load async. - getSinglePoolData(chainId, pool, { synPrices, ethPrice, avaxPrice }) + getSinglePoolData(pool.chainId, pool, { + synPrices, + ethPrice, + avaxPrice, + }) .then((res) => { setPoolData(res) }) .catch((err) => { console.log('Could not get Pool Data: ', err) }) - getPoolApyData(chainId, pool, { synPrices, ethPrice, avaxPrice }) + getPoolApyData(pool.chainId, pool, { synPrices, ethPrice, avaxPrice }) .then((res) => { setPoolApyData(res) }) .catch((err) => { console.log('Could not get Pool APY Data: ', err) }) + } + }, [pool, isClient]) - if (address) { - getStakedBalance(address as Address, chainId, pool.poolId[chainId]) - .then((res) => { - setStakedBalance(res) - }) - .catch((err) => { - console.log('Could not get staked balances: ', err) - }) - } else { - setStakedBalance({ amount: 0n, reward: 0n }) - } + useEffect(() => { + if (address && isClient) { + getStakedBalance( + address as Address, + pool.chainId, + pool.poolId[pool.chainId] + ) + .then((res) => { + setStakedBalance(res) + }) + .catch((err) => { + console.log('Could not get staked balances: ', err) + }) + } else { + setStakedBalance({ amount: 0n, reward: 0n }) } - }, [synPrices, ethPrice, avaxPrice, chainId, pool, address]) + }, [address, isClient]) /* useEffect triggers: address, isDisconnected, popup @@ -84,20 +98,20 @@ const PoolCard = memo( return (
- {pool && } + {pool && } {pool && poolData && poolApyData && diff --git a/packages/synapse-interface/pages/pools/PoolCards.tsx b/packages/synapse-interface/pages/pools/PoolCards.tsx index d987e016ac..db7c73e7f9 100644 --- a/packages/synapse-interface/pages/pools/PoolCards.tsx +++ b/packages/synapse-interface/pages/pools/PoolCards.tsx @@ -2,10 +2,18 @@ import React, { memo } from 'react' import PoolCard from './PoolCard' import { LoaderIcon } from 'react-hot-toast' +import { useAppSelector } from '@/store/hooks' const PoolCards = memo( ({ pools, address }: { pools: any; address: string }) => { const poolChainIds = pools ? Object.keys(pools) : [] + const { synPrices, ethPrice, avaxPrice } = useAppSelector( + (state) => state.priceData + ) + + if (!ethPrice) { + return null + } return ( <> @@ -13,18 +21,21 @@ const PoolCards = memo( poolChainIds.map((chainId) => { return ( - {pools[chainId] && + {synPrices.synPrice && + ethPrice && + avaxPrice && + pools[chainId] && pools[chainId]?.length > 0 && - pools[chainId].map((pool, i) => { + pools[chainId].map((pool) => { return ( -
- -
+ ) })}
diff --git a/packages/synapse-interface/utils/actions/getPoolApyData.ts b/packages/synapse-interface/utils/actions/getPoolApyData.ts index 4c7543b240..e6595a8a1c 100644 --- a/packages/synapse-interface/utils/actions/getPoolApyData.ts +++ b/packages/synapse-interface/utils/actions/getPoolApyData.ts @@ -2,7 +2,7 @@ import { formatUnits } from '@ethersproject/units' import { SYN_ETH_SUSHI_TOKEN } from '@constants/tokens/sushiMaster' import { MINICHEF_ADDRESSES } from '@constants/minichef' import { Token } from '@types' -import { fetchBalance, readContracts, fetchToken, Address } from '@wagmi/core' +import { readContracts, Address, erc20ABI } from '@wagmi/core' import { MINICHEF_ABI } from '@abis/miniChef' import { getSynPrices } from '@utils/actions/getPrices' @@ -48,29 +48,27 @@ export const getPoolApyData = async ( chainId, args: [poolToken.poolId[chainId]], }, + { + address: poolToken.addresses[chainId] as Address, + abi: erc20ABI, + functionName: 'balanceOf', + chainId, + args: [minichefAddress], + }, + { + address: poolToken.addresses[chainId] as Address, + abi: erc20ABI, + functionName: 'totalSupply', + chainId, + }, ], }) const synapsePerSecondResult: bigint = data[0].result const totalAllocPointsResult: bigint = data[1].result const poolInfoResult: PoolInfoResult = data[2].result - - const lpTokenBalanceResult = - ( - await fetchBalance({ - address: minichefAddress, - chainId, - token: poolToken.addresses[chainId] as Address, - }) - )?.value ?? 0n - - const lpTokenSupplyResult = - ( - await fetchToken({ - address: poolToken.addresses[chainId] as Address, - chainId, - }) - )?.totalSupply?.value ?? 0n + const lpTokenBalanceResult: bigint = data[3].result ?? 0n + const lpTokenSupplyResult: bigint = data[4].result ?? 0n const synPriceData = prices?.synPrices ?? (await getSynPrices()) const synapsePerSecond: bigint = synapsePerSecondResult ?? 0n diff --git a/packages/synapse-interface/utils/actions/getPoolData.ts b/packages/synapse-interface/utils/actions/getPoolData.ts index 6e66cb50b5..0fb296cb1b 100644 --- a/packages/synapse-interface/utils/actions/getPoolData.ts +++ b/packages/synapse-interface/utils/actions/getPoolData.ts @@ -1,9 +1,11 @@ -import { getEthPrice, getAvaxPrice } from '@utils/actions/getPrices' -import { getPoolTokenInfoArr, getTokenBalanceInfo } from '@utils/poolDataFuncs' -import { Address, fetchBalance, fetchToken } from '@wagmi/core' +import { erc20ABI, multicall } from '@wagmi/core' import { Token, PoolData } from '@types' +import { formatBigIntToString } from '@utils/bigint/format' +import { getPoolTokenInfoArr, getTokenBalanceInfo } from '@utils/poolDataFuncs' +import { getEthPrice, getAvaxPrice } from '@utils/actions/getPrices' -import { getCorePoolData } from './getCorePoolData' +import lpTokenABI from '@/constants/abis/lpToken.json' +import { SWAP_ABI } from '@/constants/abis/swap' export const getBalanceData = async ({ pool, @@ -16,46 +18,77 @@ export const getBalanceData = async ({ address: string lpTokenAddress: string }) => { + const tokens: Token[] = [...pool.poolTokens, pool] const tokenBalances = [] let poolTokenSum = 0n let lpTokenBalance = 1n - const lpTotalSupply = - ( - await fetchToken({ - address: lpTokenAddress as Address, - chainId, - }) - )?.totalSupply?.value ?? 0n - const tokens: Token[] = [...pool.poolTokens, pool] - for (const token of tokens) { - const isLP = token.addresses[chainId] === lpTokenAddress + const multicallInputs = [] - const rawBalanceResult = await fetchBalance({ - address: address as Address, + const one = { + address: lpTokenAddress, + abi: lpTokenABI, + functionName: 'totalSupply', + chainId, + } + + multicallInputs.push(one) + + for (const token of tokens) { + multicallInputs.push({ + address: token.addresses[chainId], + abi: erc20ABI, + functionName: 'balanceOf', chainId, - token: token.addresses[chainId] as Address, + args: [address], }) + } + const two = { + address: pool.swapAddresses[chainId], + abi: SWAP_ABI, + functionName: 'swapStorage', + chainId, + } + + const three = { + address: pool.swapAddresses[chainId], + abi: SWAP_ABI, + functionName: 'getVirtualPrice', + chainId, + } + + multicallInputs.push(two) + multicallInputs.push(three) + + const multicallData: any[] = await multicall({ + contracts: multicallInputs, + chainId, + }).catch((error) => { + console.error('Multicall failed:', error) + return [] + }) + + const lpTotalSupply = multicallData[0].result ?? 0n + + tokens.forEach((token, index) => { + const isLP = token.addresses[chainId] === lpTokenAddress - // add to balances tokenBalances.push({ - rawBalance: rawBalanceResult?.value ?? 0n, - balance: rawBalanceResult?.formatted ?? '0', + rawBalance: multicallData[index + 1].result ?? 0n, + balance: formatBigIntToString( + multicallData[index + 1].result, + token.decimals[chainId] + ), token, isLP, }) - // set lp variables if (isLP) { - lpTokenBalance = rawBalanceResult?.value - continue + lpTokenBalance = multicallData[index + 1].result } - // running sum of all tokens in the pool - if (rawBalanceResult?.formatted) { - poolTokenSum = - poolTokenSum + BigInt(Math.round(Number(rawBalanceResult?.formatted))) - } - } + + poolTokenSum = poolTokenSum + multicallData[index + 1].result + }) return { tokenBalances, @@ -85,8 +118,6 @@ export const getSinglePoolData = async ( lpTokenAddress, }) - const { swapFee, virtualPrice } = await getCorePoolData(poolAddress, chainId) - const ethPrice = prices?.ethPrice ?? (await getEthPrice()) const avaxPrice = prices?.avaxPrice ?? (await getAvaxPrice()) @@ -113,7 +144,5 @@ export const getSinglePoolData = async ( tokens: poolTokensMatured, totalLocked: tokenBalancesSum, totalLockedUSD: tokenBalancesUSD, - virtualPrice, - swapFee, } } diff --git a/packages/synapse-interface/utils/types/index.tsx b/packages/synapse-interface/utils/types/index.tsx index 00c75bcde8..bf248c6ace 100644 --- a/packages/synapse-interface/utils/types/index.tsx +++ b/packages/synapse-interface/utils/types/index.tsx @@ -46,9 +46,9 @@ export type PoolData = { tokens: PoolToken[] totalLocked: number totalLockedUSD: number - virtualPrice: bigint + virtualPrice?: bigint nativeTokens?: any - swapFee: bigint + swapFee?: bigint } export type BridgeQuote = {