diff --git a/projects/ui/src/components/App/index.tsx b/projects/ui/src/components/App/index.tsx index 9d95c520f..4955c7bef 100644 --- a/projects/ui/src/components/App/index.tsx +++ b/projects/ui/src/components/App/index.tsx @@ -266,7 +266,7 @@ function Arbitrum() { } export default function App() { - const { isArbitrum, isDev: isTestnet } = useChainState(); + const { isArbitrum, isTestnet } = useChainState(); if (!isArbitrum || (isArbitrum && !isTestnet)) { return ; diff --git a/projects/ui/src/components/Balances/SiloBalancesHistory.tsx b/projects/ui/src/components/Balances/SiloBalancesHistory.tsx index 4ad540f9a..2e00fc459 100644 --- a/projects/ui/src/components/Balances/SiloBalancesHistory.tsx +++ b/projects/ui/src/components/Balances/SiloBalancesHistory.tsx @@ -11,7 +11,7 @@ import { SeasonRange, } from '~/hooks/beanstalk/useSeasonsQuery'; import useFarmerSiloHistory from '~/hooks/farmer/useFarmerSiloHistory'; -import { L1_SILO_WHITELIST } from '~/constants/tokens'; +import { useWhitelistedTokens } from '~/hooks/beanstalk/useTokens'; import MockPlot from '../Silo/MockPlot'; import BlurComponent from '../Common/ZeroState/BlurComponent'; import WalletButton from '../Common/Connection/WalletButton'; @@ -21,6 +21,7 @@ const SiloBalancesHistory: React.FC<{}> = () => { const account = useAccount(); const timeTabParams = useTimeTabState(); const { data, loading } = useFarmerSiloHistory(account, true, false); + const { whitelist } = useWhitelistedTokens(); const formatValue = (value: number) => `$${value?.toLocaleString('en-US', { maximumFractionDigits: 2 })}`; @@ -48,7 +49,7 @@ const SiloBalancesHistory: React.FC<{}> = () => { const queryData: QueryData = { data: filteredSeries as BaseDataPoint[][], loading: loading, - keys: L1_SILO_WHITELIST.map((t) => t.address), + keys: whitelist.map((t) => t.address), error: undefined, }; diff --git a/projects/ui/src/components/Common/Charts/ChartPropProvider.tsx b/projects/ui/src/components/Common/Charts/ChartPropProvider.tsx index 626640ba3..931aec4cf 100644 --- a/projects/ui/src/components/Common/Charts/ChartPropProvider.tsx +++ b/projects/ui/src/components/Common/Charts/ChartPropProvider.tsx @@ -32,13 +32,15 @@ export type BaseDataPoint = { [key: string]: number; }; +type BaseChartMultiStyle = { + stroke: string; // stroke color + fillPrimary: string; // gradient 'to' color + fillSecondary?: string; // gradient 'from' color + strokeWidth?: number; +}; + export type ChartMultiStyles = { - [key: string]: { - stroke: string; // stroke color - fillPrimary: string; // gradient 'to' color - fillSecondary?: string; // gradient 'from' color - strokeWidth?: number; - }; + [key: string]: BaseChartMultiStyle; }; type ChartStyleConfig = { @@ -75,8 +77,8 @@ type Scales = { }; export type Scale = { - xScale: ReturnType; - yScale: ReturnType; + xScale: ReturnType<(typeof SCALES)[keyof typeof SCALES]>; + yScale: ReturnType<(typeof SCALES)[keyof typeof SCALES]>; }; type ChartAccessorProps = { @@ -120,6 +122,7 @@ type UtilProps = { }; export type DataRegion = { yTop: number; yBottom: number }; +// eslint-disable-next-line react/no-unused-prop-types export type BaseGraphProps = { width: number; height: number }; export type ProviderChartProps = { @@ -142,7 +145,7 @@ export type BaseChartProps = { isTWAP?: boolean; useCustomTokenList?: ERC20Token[]; useCustomTooltipNames?: { [key: string]: string }; - tokenPerSeasonFilter?: { [key: string]: { from: number, to: number } }; + tokenPerSeasonFilter?: { [key: string]: { from: number; to: number } }; horizontalLineNumber?: number; stylesConfig?: ChartMultiStyles; stackedArea?: boolean; @@ -184,33 +187,47 @@ const chartPadding = { }; const chartColors = BeanstalkPalette.theme.spring.chart; -const defaultChartStyles: ChartMultiStyles = { - 0: { - stroke: BeanstalkPalette.theme.spring.beanstalkGreen, - fillPrimary: chartColors.primaryLight, - strokeWidth: 2, - }, - 1: { - stroke: chartColors.purple, - fillPrimary: chartColors.purpleLight, - strokeWidth: 2, - }, - 2: { - stroke: chartColors.green, - fillPrimary: chartColors.greenLight, - strokeWidth: 2, - }, - 3: { - stroke: chartColors.yellow, - fillPrimary: chartColors.yellowLight, - strokeWidth: 2, - }, - 4: { - stroke: chartColors.blue, - fillPrimary: chartColors.blueLight, - strokeWidth: 2, - }, + +const chartStylePrimary: BaseChartMultiStyle = { + stroke: BeanstalkPalette.theme.spring.beanstalkGreen, + fillPrimary: chartColors.primaryLight, + strokeWidth: 2, +}; +const chartStylePurple: BaseChartMultiStyle = { + stroke: chartColors.purple, + fillPrimary: chartColors.purpleLight, + strokeWidth: 2, +}; +const chartStyleGreen: BaseChartMultiStyle = { + stroke: chartColors.green, + fillPrimary: chartColors.greenLight, + strokeWidth: 2, }; +const chartStyleYellow: BaseChartMultiStyle = { + stroke: chartColors.yellow, + fillPrimary: chartColors.yellowLight, + strokeWidth: 2, +}; +const chartStyleBlue: BaseChartMultiStyle = { + stroke: chartColors.blue, + fillPrimary: chartColors.blueLight, + strokeWidth: 2, +}; + +const chartStyleArr = [ + chartStylePrimary, + chartStylePurple, + chartStyleGreen, + chartStyleYellow, + chartStyleBlue, +]; + +const defaultChartStyles: ChartMultiStyles = Object.fromEntries( + [...chartStyleArr, ...chartStyleArr].map((chartStyle, i) => [ + i.toString(), + chartStyle, + ]) +); /** * get chart styles for given key diff --git a/projects/ui/src/hooks/beanstalk/useTokens.ts b/projects/ui/src/hooks/beanstalk/useTokens.ts index f78b63022..b9b53a6b8 100644 --- a/projects/ui/src/hooks/beanstalk/useTokens.ts +++ b/projects/ui/src/hooks/beanstalk/useTokens.ts @@ -6,12 +6,14 @@ import LegacyToken, { NativeToken as LegacyNativeToken, } from '~/classes/Token'; import { ChainConstant, SupportedChainId, TokenMap } from '~/constants'; +import * as ADDRESSES from '~/constants/addresses'; import useSdk from '~/hooks/sdk'; import { useAppSelector } from '~/state'; import { BeanPools } from '~/state/bean/pools'; import * as LegacyTokens from '~/constants/tokens'; import { getTokenIndex, isSdkToken } from '~/util'; -import useChainState from '../chain/useChainState'; +import useChainState from '~/hooks/chain/useChainState'; +import { useResolvedChainId } from '../chain/useChainId'; // ------------------------- // Token Instances @@ -89,7 +91,8 @@ export const useTokens = (): { const erc20TokenMap = Object.values(balanceTokens).reduce< TokenMap >((acc, token) => { - if (token.equals(tokens.ETH)) return acc; + const tokenIndex = getTokenIndex(token); + if (tokenIndex === 'eth') return acc; acc[getTokenIndex(token)] = token as ERC20Token; return acc; }, {}); @@ -144,6 +147,31 @@ export const useUnripeTokens = () => { }, [sdk]); }; +export const useGetUnripeTokenWithAddress = () => { + const urTokens = useUnripeTokens(); + + const getUnripeSdkToken = useCallback( + (_address: string) => { + const address = _address.toLowerCase(); + + if (Object.values(ADDRESSES.UNRIPE_BEAN_ADDRESSES).includes(address)) { + return urTokens.UNRIPE_BEAN; + } + + if ( + Object.values(ADDRESSES.UNRIPE_BEAN_WSTETH_ADDRESSES).includes(address) + ) { + return urTokens.UNRIPE_BEAN_WSTETH; + } + }, + [urTokens] + ); + + return { + getUnripeSdkToken, + }; +}; + /** * @param sortByLiquidity - If true, sort Well LP tokens by liquidity (highest to lowest) */ @@ -245,37 +273,40 @@ function getWhitelistSorted( }); } -const i = SupportedChainId.ARBITRUM_MAINNET; +const cARB = SupportedChainId.ARBITRUM_MAINNET; +const cETH = SupportedChainId.ETH_MAINNET; const oldTokenMap: Record | LegacyToken> = { - [LegacyTokens.ETH[i].symbol]: LegacyTokens.ETH, - [LegacyTokens.BEAN[i].symbol]: LegacyTokens.BEAN, - [LegacyTokens.UNRIPE_BEAN[i].symbol]: LegacyTokens.UNRIPE_BEAN, - [LegacyTokens.UNRIPE_BEAN_WSTETH[i].symbol]: LegacyTokens.UNRIPE_BEAN_WSTETH, - [LegacyTokens.WETH[i].symbol]: LegacyTokens.WETH, - [LegacyTokens.DAI[i].symbol]: LegacyTokens.DAI, - [LegacyTokens.USDC[i].symbol]: LegacyTokens.USDC, - [LegacyTokens.USDT[i].symbol]: LegacyTokens.USDT, - [LegacyTokens.WSTETH[i].symbol]: LegacyTokens.WSTETH, - [LegacyTokens.WEETH[i].symbol]: LegacyTokens.WEETH, - [LegacyTokens.WBTC[i].symbol]: LegacyTokens.WBTC, - [LegacyTokens.BEAN_ETH_WELL_LP[i].symbol]: LegacyTokens.BEAN_ETH_WELL_LP, - [LegacyTokens.BEAN_WSTETH_WELL_LP[i].symbol]: + [LegacyTokens.ETH[cARB].symbol]: LegacyTokens.ETH, + [LegacyTokens.BEAN[cARB].symbol]: LegacyTokens.BEAN, + [LegacyTokens.UNRIPE_BEAN[cARB].symbol]: LegacyTokens.UNRIPE_BEAN, + [LegacyTokens.UNRIPE_BEAN_WSTETH[cARB].symbol]: + LegacyTokens.UNRIPE_BEAN_WSTETH, + [LegacyTokens.WETH[cARB].symbol]: LegacyTokens.WETH, + [LegacyTokens.DAI[cARB].symbol]: LegacyTokens.DAI, + [LegacyTokens.USDC[cARB].symbol]: LegacyTokens.USDC, + [LegacyTokens.USDT[cARB].symbol]: LegacyTokens.USDT, + [LegacyTokens.WSTETH[cARB].symbol]: LegacyTokens.WSTETH, + [LegacyTokens.WEETH[cARB].symbol]: LegacyTokens.WEETH, + [LegacyTokens.WBTC[cARB].symbol]: LegacyTokens.WBTC, + [LegacyTokens.BEAN_ETH_WELL_LP[cARB].symbol]: LegacyTokens.BEAN_ETH_WELL_LP, + [LegacyTokens.BEAN_WSTETH_WELL_LP[cARB].symbol]: LegacyTokens.BEAN_WSTETH_WELL_LP, - [LegacyTokens.BEAN_WEETH_WELL_LP[i].symbol]: LegacyTokens.BEAN_WEETH_WELL_LP, - [LegacyTokens.BEAN_WBTC_WELL_LP[i].symbol]: LegacyTokens.BEAN_WBTC_WELL_LP, - [LegacyTokens.BEAN_USDC_WELL_LP[i].symbol]: LegacyTokens.BEAN_USDC_WELL_LP, - [LegacyTokens.BEAN_USDT_WELL_LP[i].symbol]: LegacyTokens.BEAN_USDT_WELL_LP, + [LegacyTokens.BEAN_WEETH_WELL_LP[cARB].symbol]: + LegacyTokens.BEAN_WEETH_WELL_LP, + [LegacyTokens.BEAN_WBTC_WELL_LP[cARB].symbol]: LegacyTokens.BEAN_WBTC_WELL_LP, + [LegacyTokens.BEAN_USDC_WELL_LP[cARB].symbol]: LegacyTokens.BEAN_USDC_WELL_LP, + [LegacyTokens.BEAN_USDT_WELL_LP[cARB].symbol]: LegacyTokens.BEAN_USDT_WELL_LP, [LegacyTokens.STALK.symbol]: LegacyTokens.STALK, [LegacyTokens.SEEDS.symbol]: LegacyTokens.SEEDS, [LegacyTokens.PODS.symbol]: LegacyTokens.PODS, [LegacyTokens.SPROUTS.symbol]: LegacyTokens.SPROUTS, [LegacyTokens.RINSABLE_SPROUTS.symbol]: LegacyTokens.RINSABLE_SPROUTS, - [LegacyTokens.BEAN_CRV3_LP[1].symbol]: LegacyTokens.BEAN_CRV3_LP, - [LegacyTokens.CRV3[1].symbol]: LegacyTokens.CRV3, - [LegacyTokens.BEAN_ETH_UNIV2_LP[1].symbol]: LegacyTokens.BEAN_ETH_UNIV2_LP, - [LegacyTokens.BEAN_LUSD_LP[1].symbol]: LegacyTokens.BEAN_LUSD_LP, - [LegacyTokens.LUSD[1].symbol]: LegacyTokens.LUSD, + [LegacyTokens.BEAN_CRV3_LP[cETH].symbol]: LegacyTokens.BEAN_CRV3_LP, + [LegacyTokens.CRV3[cETH].symbol]: LegacyTokens.CRV3, + [LegacyTokens.BEAN_ETH_UNIV2_LP[cETH].symbol]: LegacyTokens.BEAN_ETH_UNIV2_LP, + [LegacyTokens.BEAN_LUSD_LP[cETH].symbol]: LegacyTokens.BEAN_LUSD_LP, + [LegacyTokens.LUSD[cETH].symbol]: LegacyTokens.LUSD, } as const; export const useGetLegacyToken = () => { @@ -294,3 +325,76 @@ export const useGetLegacyToken = () => { return getLegacyToken; }; + +const makeEntry = ( + a: ChainConstant, + t: ChainConstant +) => ({ + ...(a[cARB] && t[cARB] ? { [a[cARB]]: t } : {}), + ...(a[cETH] && t[cETH] ? { [a[cETH]]: t } : {}), +}); + +const A = ADDRESSES; +const L = LegacyTokens; + +const ADDRESS_TO_CHAIN_CONSTANT: Record> = { + // BEAN + ...makeEntry(A.BEAN_ADDRESSES, L.BEAN), + + // UNRIPE + + ...makeEntry(A.UNRIPE_BEAN_ADDRESSES, L.UNRIPE_BEAN), + ...makeEntry(A.UNRIPE_BEAN_WSTETH_ADDRESSES, L.UNRIPE_BEAN_WSTETH), + + // ERC20 + ...makeEntry(A.WETH_ADDRESSES, L.WETH), + ...makeEntry(A.WSTETH_ADDRESSES, L.WSTETH), + ...makeEntry(A.WEETH_ADDRESSES, L.WEETH), + ...makeEntry(A.WBTC_ADDRESSES, L.WBTC), + ...makeEntry(A.DAI_ADDRESSES, L.DAI), + ...makeEntry(A.USDC_ADDRESSES, L.USDC), + ...makeEntry(A.USDT_ADDRESSES, L.USDT), + ...makeEntry(A.ARB_ADDRESSES, L.ARB), + + // LP + ...makeEntry(A.BEAN_ETH_WELL_ADDRESSES, L.BEAN_ETH_WELL_LP), + ...makeEntry(A.BEAN_WSTETH_ADDRESSS, L.BEAN_WSTETH_WELL_LP), + ...makeEntry(A.BEANWEETH_WELL_ADDRESSES, L.BEAN_WEETH_WELL_LP), + ...makeEntry(A.BEANWBTC_WELL_ADDRESSES, L.BEAN_WBTC_WELL_LP), + ...makeEntry(A.BEANUSDC_WELL_ADDRESSES, L.BEAN_USDC_WELL_LP), + ...makeEntry(A.BEANUSDT_WELL_ADDRESSES, L.BEAN_USDT_WELL_LP), + + // Deprecated + // ...makeEntry(A.CRV3_ADDRESSES, L.CRV3), + // ...makeEntry(A.BEAN_CRV3_ADDRESSES, L.BEAN_CRV3_LP), + // ...makeEntry(A.BEAN_LUSD_ADDRESSES, L.BEAN_LUSD_LP), + // ...makeEntry(A.LUSD_ADDRESSES, L.LUSD), + // ...makeEntry(A.BEAN_ETH_UNIV2_LP_ADDRESSES, L.BEAN_ETH_UNIV2_LP), +} as const; + +/** + * + * @returns a function that takes an address (chain agnostic) and returns the sdk token instance + * for the chain the user is currently on + * + */ +export const useGetNormaliseChainToken = () => { + const { erc20TokenMap } = useTokens(); + const chainId = useResolvedChainId(); + + /** + * returns the sdk token instance for the chain the user is currently on if available + */ + const getToken = useCallback( + (_address: string) => { + const token = ADDRESS_TO_CHAIN_CONSTANT[_address.toLowerCase()]; + + return token?.[chainId] + ? erc20TokenMap[getTokenIndex(token[chainId])] + : undefined; + }, + [erc20TokenMap, chainId] + ); + + return getToken; +}; \ No newline at end of file diff --git a/projects/ui/src/hooks/chain/useChainId.ts b/projects/ui/src/hooks/chain/useChainId.ts index 2695738f9..8d8fd6fe4 100644 --- a/projects/ui/src/hooks/chain/useChainId.ts +++ b/projects/ui/src/hooks/chain/useChainId.ts @@ -1,3 +1,4 @@ +import { ChainResolver } from '@beanstalk/sdk-core'; import { useMemo } from 'react'; import { useAccount } from 'wagmi'; import { SupportedChainId } from '~/constants'; @@ -17,3 +18,9 @@ export default function useChainId() { const { chain } = useAccount(); return useMemo(() => chain?.id || defaultChainId, [chain?.id]); } + +export function useResolvedChainId() { + const chainId = useChainId(); + + return ChainResolver.resolveToMainnetChainId(chainId); +} diff --git a/projects/ui/src/hooks/farmer/useFarmerBalancesBreakdown.ts b/projects/ui/src/hooks/farmer/useFarmerBalancesBreakdown.ts index cf743d95a..cb1b3e7dc 100644 --- a/projects/ui/src/hooks/farmer/useFarmerBalancesBreakdown.ts +++ b/projects/ui/src/hooks/farmer/useFarmerBalancesBreakdown.ts @@ -184,7 +184,10 @@ export function useFarmerBalancesL1Breakdown() { addresses.forEach((address) => { const token = whitelist[address]; const siloBalance = siloBalances[address]; - const tokenBalance = tokenBalances[address] || ZERO_BN; + const tokenBalance = tokenBalances[address] || { + internal: ZERO_BN, + external: ZERO_BN, + }; // Ensure we've loaded a Silo Balance for this token. if (siloBalance) { @@ -222,6 +225,8 @@ export function useFarmerBalancesL1Breakdown() { } }); + console.log('useFarmersBalancesL1Breakdown', prev); + return prev; }, [whitelist, addresses, siloBalances, tokenBalances, getUSD]); } diff --git a/projects/ui/src/hooks/farmer/useFarmerSiloHistory.ts b/projects/ui/src/hooks/farmer/useFarmerSiloHistory.ts index 3c9d84000..614b6d120 100644 --- a/projects/ui/src/hooks/farmer/useFarmerSiloHistory.ts +++ b/projects/ui/src/hooks/farmer/useFarmerSiloHistory.ts @@ -10,14 +10,14 @@ import useSeasonsQuery, { } from '~/hooks/beanstalk/useSeasonsQuery'; import useInterpolateDeposits from '~/hooks/farmer/useInterpolateDeposits'; import useInterpolateStalk from '~/hooks/farmer/useInterpolateStalk'; -import { useFarmerBalancesL1Breakdown } from './useFarmerBalancesBreakdown'; +import useFarmerBalancesBreakdown from './useFarmerBalancesBreakdown'; const useFarmerSiloHistory = ( account: string | undefined, itemizeByToken: boolean = false, includeStalk: boolean = false ) => { - const breakdown = useFarmerBalancesL1Breakdown(); // TODO: Fix me + const breakdown = useFarmerBalancesBreakdown(); /// Data const siloRewardsQuery = useFarmerSiloRewardsQuery({ diff --git a/projects/ui/src/hooks/farmer/useInterpolateDeposits.ts b/projects/ui/src/hooks/farmer/useInterpolateDeposits.ts index 2527ab3e4..da0f42b5b 100644 --- a/projects/ui/src/hooks/farmer/useInterpolateDeposits.ts +++ b/projects/ui/src/hooks/farmer/useInterpolateDeposits.ts @@ -9,9 +9,13 @@ import { interpolateFarmerDepositedValue, SnapshotBeanstalk, } from '~/util/Interpolate'; -import { SupportedChainId, ZERO_BN } from '~/constants'; -import { UNRIPE_BEAN_WSTETH } from '~/constants/tokens'; +import { ZERO_BN } from '~/constants'; import useUnripeUnderlyingMap from '../beanstalk/useUnripeUnderlying'; +import { + useGetNormaliseChainToken, + useUnripeTokens, + useWhitelistedTokens, +} from '../beanstalk/useTokens'; const useInterpolateDeposits = ( siloAssetsQuery: ReturnType, @@ -20,12 +24,11 @@ const useInterpolateDeposits = ( ) => { const unripe = useAppSelector((state) => state._bean.unripe); const beanPools = useAppSelector((state) => state._bean.pools); + const { UNRIPE_BEAN_WSTETH: urBeanLP } = useUnripeTokens(); + const normalizeToken = useGetNormaliseChainToken(); + const { whitelist } = useWhitelistedTokens(); const underlyingMap = useUnripeUnderlyingMap(); - // const sdk = useSdk(); - // BS3TODO: fix me - const urBeanLP = UNRIPE_BEAN_WSTETH[SupportedChainId.ETH_MAINNET]; - return useMemo(() => { if ( priceQuery.loading || @@ -40,15 +43,14 @@ const useInterpolateDeposits = ( // sorted by Season and normalized based on chop rate. const snapshots = siloAssetsQuery.data.farmer.silo.assets .reduce((prev, asset) => { - const tokenAddress = asset.token.toLowerCase(); + const tokenAddress = normalizeToken(asset.token)?.address; + if (!tokenAddress) return prev; prev.push( ...asset.hourlySnapshots.map((snapshot) => { let hourlyDepositedBDV; if (tokenAddress in unripe) { - if ( - tokenAddress.toLowerCase() === urBeanLP.address.toLowerCase() - ) { + if (tokenAddress === urBeanLP.address) { // formula: penalty = amount of BEANwstETH per 1 urBEANwstETH. // bdv of urBEANwstETH = amount * penalty * BDV of 1 BEANwstETH const underlying = underlyingMap[tokenAddress]; @@ -89,9 +91,15 @@ const useInterpolateDeposits = ( return interpolateFarmerDepositedValue( snapshots, priceQuery.data.seasons, - itemizeByToken + itemizeByToken, + 24, + whitelist, + normalizeToken, + ); }, [ + normalizeToken, + whitelist, priceQuery.loading, priceQuery.data?.seasons, siloAssetsQuery.data?.farmer?.silo?.assets, diff --git a/projects/ui/src/hooks/farmer/useInterpolateStalk.ts b/projects/ui/src/hooks/farmer/useInterpolateStalk.ts index b80dd8f26..f802df98b 100644 --- a/projects/ui/src/hooks/farmer/useInterpolateStalk.ts +++ b/projects/ui/src/hooks/farmer/useInterpolateStalk.ts @@ -1,10 +1,9 @@ import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; import { useFarmerSiloRewardsQuery, useWhitelistTokenRewardsQuery } from '~/generated/graphql'; import useSeason from '~/hooks/beanstalk/useSeason'; -import { AppState } from '~/state'; +import { useAppSelector } from '~/state'; import { interpolateFarmerStalk } from '~/util/Interpolate'; -import useSdk from '../sdk'; +import { useWhitelistedTokens } from '../beanstalk/useTokens'; const useInterpolateStalk = ( siloRewardsQuery: ReturnType, @@ -12,21 +11,18 @@ const useInterpolateStalk = ( skip: boolean = false ) => { const season = useSeason(); - const sdk = useSdk(); + const { whitelist } = useWhitelistedTokens(); // Balances - const balances = useSelector< - AppState, - AppState['_farmer']['silo']['balances'] - >((state) => state._farmer.silo.balances); + const balances = useAppSelector((state) => state._farmer.silo.balances); return useMemo(() => { if (skip || !season.gt(0) || !siloRewardsQuery.data?.snapshots?.length || !whitelistQuery.data?.snapshots?.length) return [[], []]; const siloSnapshots = siloRewardsQuery.data.snapshots; const whitelistSnapshots = whitelistQuery.data.snapshots; - return interpolateFarmerStalk(siloSnapshots, whitelistSnapshots, season, undefined, balances, sdk); - }, [skip, siloRewardsQuery.data?.snapshots, whitelistQuery.data?.snapshots, season, balances, sdk]); + return interpolateFarmerStalk(siloSnapshots, whitelistSnapshots, season, undefined, balances, whitelist); + }, [skip, siloRewardsQuery.data?.snapshots, whitelistQuery.data?.snapshots, season, balances, whitelist]); }; export default useInterpolateStalk; diff --git a/projects/ui/src/util/Interpolate.ts b/projects/ui/src/util/Interpolate.ts index 132e9d1e9..55e62bad4 100644 --- a/projects/ui/src/util/Interpolate.ts +++ b/projects/ui/src/util/Interpolate.ts @@ -1,7 +1,7 @@ import BigNumber from 'bignumber.js'; import { DateTime } from 'luxon'; import { TokenMap, ZERO_BN } from '~/constants'; -import { BEAN, SEEDS, SILO_WHITELIST, STALK } from '~/constants/tokens'; +import { BEAN, SEEDS, STALK } from '~/constants/tokens'; import { FarmerSiloRewardsQuery, SeasonalInstantPriceQuery, @@ -15,7 +15,8 @@ import { } from '~/util'; import { BaseDataPoint } from '~/components/Common/Charts/ChartPropProvider'; import { FarmerSiloTokenBalance } from '~/state/farmer/silo'; -import { BeanstalkSDK } from '@beanstalk/sdk'; +import { Token } from '@beanstalk/sdk'; +import { useWhitelistedTokens } from '~/hooks/beanstalk/useTokens'; export type Snapshot = { id: string; @@ -40,7 +41,8 @@ export type SnapshotBeanstalk = { export const addBufferSeasons = ( points: BaseDataPoint[], num: number = 24, - itemizeByToken: boolean = false + whitelist: ReturnType['whitelist'], + itemizeByToken: boolean = false, ) => { if (points.length === 0) return []; const d = DateTime.fromJSDate(points[0].date); @@ -58,8 +60,8 @@ export const addBufferSeasons = ( value: 0, // FIXME: have the chart default to zero if a key isn't provided? ...(itemizeByToken - ? SILO_WHITELIST.reduce>((prev, curr) => { - prev[curr[1].address] = 0; + ? whitelist.reduce>((prev, curr) => { + prev[curr.address] = 0; return prev; }, {}) : undefined), @@ -80,11 +82,10 @@ export const interpolateFarmerStalk = ( season: BigNumber, bufferSeasons: number = 24, farmerSiloBalances: TokenMap, - sdk: BeanstalkSDK + whitelist: ReturnType['whitelist'] ) => { // Sequence let j = 0; - const siloWhitelist = sdk.tokens.siloWhitelist; const minSeason = snapshots[j].season; const maxSeason = season.toNumber(); // current season let currStalk: BigNumber = ZERO_BN; @@ -94,7 +95,7 @@ export const interpolateFarmerStalk = ( secondsToDate(snapshots[j].createdAt) ); let nextSeason: number | undefined = minSeason; - + // Add buffer points before the first snapshot const stalk: BaseDataPoint[] = []; const seeds: BaseDataPoint[] = []; @@ -102,21 +103,27 @@ export const interpolateFarmerStalk = ( function getSeedsPerBdv(_season: number) { let _output: BigNumber = ZERO_BN; - siloWhitelist.forEach((token) => { + whitelist.forEach((token) => { if (farmerSiloBalances[token.address]) { const deposits = farmerSiloBalances[token.address].deposited; - const index = whitelistSnapshots.findLastIndex((snapshot) => snapshot.season <= _season && snapshot.token.id === token.address.toLowerCase()); + const index = whitelistSnapshots.findLastIndex( + (snapshot) => + snapshot.season <= _season && + snapshot.token.id === token.address.toLowerCase() + ); if (index >= 0) { - const seedsPerBdv = BigNumber(whitelistSnapshots[index].stalkEarnedPerSeason).div(1_000_000); - _output = (seedsPerBdv).multipliedBy(deposits.bdv).plus(_output); - }; - }; + const seedsPerBdv = BigNumber( + whitelistSnapshots[index].stalkEarnedPerSeason + ).div(1_000_000); + _output = seedsPerBdv.multipliedBy(deposits.bdv).plus(_output); + } + } }); return _output; - }; + } let lastSeedsSnapshot: BigNumber = ZERO_BN; - const lastSnapshotSeason = snapshots[snapshots.length - 1].season; + const lastSnapshotSeason = snapshots[snapshots.length - 1].season; for (let s = minSeason; s <= maxSeason; s += 1) { if (s === nextSeason) { // Reached a data point for which we have a snapshot. @@ -135,13 +142,13 @@ export const interpolateFarmerStalk = ( currSeeds = lastSeedsSnapshot; } else { currSeeds = getSeedsPerBdv(s); - }; + } // Estimate actual amount of stalk / grown stalk using seeds // Each Seed grows 1/10,000 Stalk per Season - currGrownStalk = currGrownStalk.plus((currSeeds).multipliedBy(1 / 10_000)); + currGrownStalk = currGrownStalk.plus(currSeeds.multipliedBy(1 / 10_000)); currTimestamp = currTimestamp.plus({ hours: 1 }); - }; + } stalk.push({ season: s, date: currTimestamp.toJSDate(), @@ -160,9 +167,9 @@ export const interpolateFarmerStalk = ( } return [ - addBufferSeasons(stalk, bufferSeasons, false), - addBufferSeasons(seeds, bufferSeasons, false), - addBufferSeasons(grownStalk, bufferSeasons, false), + addBufferSeasons(stalk, bufferSeasons, whitelist, false), + addBufferSeasons(seeds, bufferSeasons, whitelist, false), + addBufferSeasons(grownStalk, bufferSeasons, whitelist, false), ] as const; }; @@ -175,10 +182,12 @@ export const interpolateFarmerDepositedValue = ( snapshots: SnapshotBeanstalk[], // oldest season first _prices: SeasonalInstantPriceQuery['seasons'], // most recent season first itemizeByToken: boolean = true, - bufferSeasons: number = 24 + bufferSeasons: number = 24, + whitelist: ReturnType['whitelist'], + normaliseChainToken: (token: string) => Token | undefined ) => { const prices = Array.from(_prices).reverse(); // FIXME: inefficient - if (prices.length === 0) return []; + if (!prices.length || !snapshots.length) return []; // Sequence let j = 0; @@ -189,8 +198,8 @@ export const interpolateFarmerDepositedValue = ( // null if we don't need to itemize by token const currBDVByToken = itemizeByToken - ? SILO_WHITELIST.reduce<{ [address: string]: BigNumber }>((prev, curr) => { - prev[curr[1].address] = ZERO_BN; + ? whitelist.reduce<{ [address: string]: BigNumber }>((prev, curr) => { + prev[curr.address] = ZERO_BN; return prev; }, {}) : null; @@ -237,7 +246,11 @@ export const interpolateFarmerDepositedValue = ( thisBDV = thisBDV.plus(thisSnapshotBDV); if (currBDVByToken) { - const tokenAddr = snapshots[j]?.id.split('-')[1].toLowerCase(); + const snapshotTokenAddress = snapshots[j]?.id + .split('-')[1] + .toLowerCase(); + const tokenAddr = normaliseChainToken(snapshotTokenAddress)?.address; + if (tokenAddr && currBDVByToken[tokenAddr]) { currBDVByToken[tokenAddr] = currBDVByToken[tokenAddr].plus(thisSnapshotBDV); @@ -252,8 +265,8 @@ export const interpolateFarmerDepositedValue = ( date: thisTimestamp.toJSDate(), value: thisBDV.multipliedBy(thisPriceBN).toNumber(), ...(currBDVByToken - ? SILO_WHITELIST.reduce>((prev, token) => { - const addr = token[1].address; + ? whitelist.reduce>((prev, token) => { + const addr = token.address; prev[addr] = currBDVByToken[addr] .multipliedBy(thisPriceBN) .toNumber(); @@ -265,5 +278,5 @@ export const interpolateFarmerDepositedValue = ( currBDV = thisBDV; } - return addBufferSeasons(points, bufferSeasons, Boolean(currBDVByToken)); + return addBufferSeasons(points, bufferSeasons, whitelist, Boolean(currBDVByToken)); };