Skip to content

Commit

Permalink
chore: hook and context refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Majorfi committed Jan 26, 2024
1 parent 2ff6c16 commit c587b12
Show file tree
Hide file tree
Showing 12 changed files with 453 additions and 60 deletions.
20 changes: 10 additions & 10 deletions src/contexts/useYearn.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {createContext, memo, useContext, useEffect} from 'react';
import {deserialize, serialize} from 'wagmi';
import useWallet from '@builtbymom/web3/contexts/useWallet';
import {toAddress} from '@builtbymom/web3/utils';
import {useLocalStorageValue} from '@react-hookz/web';

import {useYearnEarned} from '../hooks/useYearnEarned';
import {useYearnPrices} from '../hooks/useYearnPrices';
import {useYearnTokens} from '../hooks/useYearnTokens';
import {useYearnVaults} from '../hooks/useYearnVaults';
import {useFetchYearnEarnedForUser} from '../hooks/useFetchYearnEarnedForUser';
import {useFetchYearnPrices} from '../hooks/useFetchYearnPrices';
import {useFetchYearnTokens} from '../hooks/useFetchYearnTokens';
import {useFetchYearnVaults} from '../hooks/useFetchYearnVaults';
import {Solver} from '../utils/schemas/yDaemonTokenListBalances';
import {useYearnWallet} from './useYearnWallet';

import type {ReactElement} from 'react';
import type {KeyedMutator} from 'swr';
Expand Down Expand Up @@ -69,7 +69,7 @@ const YearnContext = createContext<TYearnContext>({
});

export const YearnContextApp = memo(function YearnContextApp({children}: {children: ReactElement}): ReactElement {
const {onRefresh} = useWallet();
const {onRefresh} = useYearnWallet();
const {value: maxLoss, set: set_maxLoss} = useLocalStorageValue<bigint>('yearn.fi/max-loss', {
defaultValue: DEFAULT_MAX_LOSS,
parse: (str: string, fallback: bigint): bigint => (str ? deserialize(str) : fallback),
Expand All @@ -88,10 +88,10 @@ export const YearnContextApp = memo(function YearnContextApp({children}: {childr
}
);

const prices = useYearnPrices();
const tokens = useYearnTokens();
const earned = useYearnEarned();
const {vaults, vaultsMigrations, vaultsRetired, isLoading, mutate} = useYearnVaults();
const prices = useFetchYearnPrices();
const tokens = useFetchYearnTokens();
const earned = useFetchYearnEarnedForUser();
const {vaults, vaultsMigrations, vaultsRetired, isLoading, mutate} = useFetchYearnVaults();

useEffect(() => {
const tokensToRefresh: TUseBalancesTokens[] = [];
Expand Down
308 changes: 308 additions & 0 deletions src/contexts/useYearnWallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
import {createContext, memo, useCallback, useContext, useEffect, useMemo, useState} from 'react';
import {useBalances} from '@builtbymom/web3/hooks/useBalances.multichains';
import {toAddress, toNormalizedBN} from '@builtbymom/web3/utils';
import {useDeepCompareMemo} from '@react-hookz/web';
import {onLoadDone, onLoadStart} from '@yearn-finance/web-lib/contexts/useUI';
import {
CRV_TOKEN_ADDRESS,
CVXCRV_TOKEN_ADDRESS,
ETH_TOKEN_ADDRESS,
LPYCRV_TOKEN_ADDRESS,
LPYCRV_V2_TOKEN_ADDRESS,
YCRV_CURVE_POOL_V2_ADDRESS,
YCRV_TOKEN_ADDRESS,
YVBOOST_TOKEN_ADDRESS,
YVECRV_TOKEN_ADDRESS
} from '@yearn-finance/web-lib/utils/constants';

import {useYearn} from './useYearn';

import type {ReactElement} from 'react';
import type {TUseBalancesTokens} from '@builtbymom/web3/hooks/useBalances.multichains';
import type {TAddress, TDict, TNormalizedBN} from '@builtbymom/web3/types';
import type {TYChainTokens, TYToken} from '../types';
import type {TYDaemonVault} from '../utils/schemas/yDaemonVaultsSchemas';

export type TWalletContext = {
getToken: ({address, chainID}: TTokenAndChain) => TYToken;
getBalance: ({address, chainID}: TTokenAndChain) => TNormalizedBN;
getPrice: ({address, chainID}: TTokenAndChain) => TNormalizedBN;
balances: TYChainTokens;
cumulatedValueInV2Vaults: number;
cumulatedValueInV3Vaults: number;
isLoading: boolean;
shouldUseForknetBalances: boolean;
onRefresh: (tokenList?: TUseBalancesTokens[]) => Promise<TYChainTokens>;
triggerForknetBalances: () => void;
};
type TTokenAndChain = {address: TAddress; chainID: number};

const defaultToken: TYToken = {
address: toAddress(''),
name: '',
symbol: '',
decimals: 18,
chainID: 1,
value: 0,
stakingValue: 0,
price: toNormalizedBN(0),
balance: toNormalizedBN(0),
supportedZaps: []
};

const defaultProps = {
getToken: (): TYToken => defaultToken,
getBalance: (): TNormalizedBN => toNormalizedBN(0),
getPrice: (): TNormalizedBN => toNormalizedBN(0),
balances: {},
cumulatedValueInV2Vaults: 0,
cumulatedValueInV3Vaults: 0,
isLoading: true,
shouldUseForknetBalances: false,
onRefresh: async (): Promise<TYChainTokens> => ({}),
triggerForknetBalances: (): void => {}
};

function useYearnTokens({shouldUseForknetBalances}: {shouldUseForknetBalances: boolean}): TUseBalancesTokens[] {
const {vaults, vaultsMigrations, vaultsRetired, isLoadingVaultList} = useYearn();

const availableTokens = useMemo((): TUseBalancesTokens[] => {
if (isLoadingVaultList) {
return [];
}
const tokens: TUseBalancesTokens[] = [];
const tokensExists: TDict<boolean> = {};
const extraTokens: TUseBalancesTokens[] = [];
extraTokens.push(
...[
{chainID: 1, address: ETH_TOKEN_ADDRESS},
{chainID: 10, address: ETH_TOKEN_ADDRESS},
{chainID: 137, address: ETH_TOKEN_ADDRESS},
{chainID: 250, address: ETH_TOKEN_ADDRESS},
{chainID: 8453, address: ETH_TOKEN_ADDRESS},
{chainID: 42161, address: ETH_TOKEN_ADDRESS},
{chainID: 1, address: YCRV_TOKEN_ADDRESS},
{chainID: 1, address: LPYCRV_TOKEN_ADDRESS},
{chainID: 1, address: CRV_TOKEN_ADDRESS},
{chainID: 1, address: YVBOOST_TOKEN_ADDRESS},
{chainID: 1, address: YVECRV_TOKEN_ADDRESS},
{chainID: 1, address: CVXCRV_TOKEN_ADDRESS},
{chainID: 1, address: YCRV_CURVE_POOL_V2_ADDRESS},
{chainID: 1, address: LPYCRV_V2_TOKEN_ADDRESS}
]
);

for (const token of extraTokens) {
tokensExists[token.address] = true;
tokens.push(token);
}

Object.values(vaults || {}).forEach((vault?: TYDaemonVault): void => {
if (!vault) {
return;
}
if (vault?.address && !tokensExists[toAddress(vault?.address)]) {
tokens.push({address: vault.address, chainID: vault.chainID});
tokensExists[vault.address] = true;
}
if (vault?.token?.address && !tokensExists[toAddress(vault?.token?.address)]) {
tokens.push({address: vault.token.address, chainID: vault.chainID});
tokensExists[vault.token.address] = true;
}
if (vault?.staking?.available && !tokensExists[toAddress(vault?.staking?.address)]) {
tokens.push({
address: vault?.staking?.address,
chainID: vault.chainID,
symbol: vault.symbol,
decimals: vault.decimals,
name: vault.name
});
tokensExists[vault?.staking?.address] = true;
}
});

return tokens;
}, [isLoadingVaultList, vaults]);

//List all vaults with a possible migration
const migratableTokens = useMemo((): TUseBalancesTokens[] => {
const tokens: TUseBalancesTokens[] = [];
Object.values(vaultsMigrations || {}).forEach((vault?: TYDaemonVault): void => {
if (!vault) {
return;
}
tokens.push({address: vault.address, chainID: vault.chainID});
});
return tokens;
}, [vaultsMigrations]);

const retiredTokens = useMemo((): TUseBalancesTokens[] => {
const tokens: TUseBalancesTokens[] = [];
Object.values(vaultsRetired || {}).forEach((vault?: TYDaemonVault): void => {
if (!vault) {
return;
}
tokens.push({address: vault.address, chainID: vault.chainID});
});
return tokens;
}, [vaultsRetired]);

const allTokens = useMemo((): TUseBalancesTokens[] => {
const tokens = [...availableTokens, ...migratableTokens, ...retiredTokens];
if (!shouldUseForknetBalances) {
return tokens;
}
for (const token of tokens) {
if (token.chainID === 1) {
//remove it
tokens.push({...token, chainID: 1337});
}
}
return tokens;
}, [availableTokens, migratableTokens, retiredTokens, shouldUseForknetBalances]);

return allTokens;
}

function useYearnBalances({shouldUseForknetBalances}: {shouldUseForknetBalances: boolean}): {
tokens: TYChainTokens;
isLoading: boolean;
onRefresh: (tokenToUpdate?: TUseBalancesTokens[]) => Promise<TYChainTokens>;
} {
const {prices} = useYearn();
const allTokens = useYearnTokens({shouldUseForknetBalances});
const {data: tokensRaw, onUpdate, onUpdateSome, isLoading} = useBalances({tokens: allTokens, prices});

const tokens = useDeepCompareMemo((): TYChainTokens => {
const _tokens = {...tokensRaw};
if (shouldUseForknetBalances) {
_tokens[1] = _tokens[1337]; // eslint-disable-line prefer-destructuring
}

return _tokens as TYChainTokens;
}, [tokensRaw, shouldUseForknetBalances]);

const onRefresh = useCallback(
async (tokenToUpdate?: TUseBalancesTokens[]): Promise<TYChainTokens> => {
if (tokenToUpdate) {
const updatedBalances = await onUpdateSome(tokenToUpdate);
return updatedBalances as TYChainTokens;
}
const updatedBalances = await onUpdate();
return updatedBalances as TYChainTokens;
},
[onUpdate, onUpdateSome]
);

useEffect((): void => {
if (isLoading) {
onLoadStart();
} else {
onLoadDone();
}
}, [isLoading]);

return {tokens, isLoading, onRefresh};
}

/* 🔵 - Yearn Finance **********************************************************
** This context controls most of the user's wallet data we may need to
** interact with our app, aka mostly the balances and the token prices.
******************************************************************************/
const YearnWalletContext = createContext<TWalletContext>(defaultProps);
export const YearnWalletContextApp = memo(function YearnWalletContextApp({
children
}: {
children: ReactElement;
}): ReactElement {
const {vaults, prices, vaultsMigrations} = useYearn();
const [shouldUseForknetBalances, set_shouldUseForknetBalances] = useState<boolean>(false);
const {tokens, isLoading, onRefresh} = useYearnBalances({shouldUseForknetBalances});

const [cumulatedValueInV2Vaults, cumulatedValueInV3Vaults] = useMemo((): [number, number] => {
let cumulatedValueInV2Vaults = 0;
let cumulatedValueInV3Vaults = 0;
for (const [, perChain] of Object.entries(tokens)) {
for (const [tokenAddress, tokenData] of Object.entries(perChain)) {
if (tokenData.value + tokenData.stakingValue === 0) {
continue;
}
if (vaults?.[toAddress(tokenAddress)]) {
if (vaults[toAddress(tokenAddress)].version.split('.')?.[0] === '3') {
cumulatedValueInV3Vaults += tokenData.value + tokenData.stakingValue;
} else {
cumulatedValueInV2Vaults += tokenData.value + tokenData.stakingValue;
}
} else if (vaultsMigrations?.[toAddress(tokenAddress)]) {
if (vaultsMigrations[toAddress(tokenAddress)].version.split('.')?.[0] === '3') {
cumulatedValueInV3Vaults += tokenData.value + tokenData.stakingValue;
} else {
cumulatedValueInV2Vaults += tokenData.value + tokenData.stakingValue;
}
}
}
}
return [cumulatedValueInV2Vaults, cumulatedValueInV3Vaults];
}, [vaults, vaultsMigrations, tokens]);

const getToken = useCallback(
({address, chainID}: TTokenAndChain): TYToken => tokens?.[chainID || 1]?.[address] || defaultToken,
[tokens]
);
const getBalance = useCallback(
({address, chainID}: TTokenAndChain): TNormalizedBN =>
tokens?.[chainID || 1]?.[address]?.balance || toNormalizedBN(0),
[tokens]
);
const getPrice = useCallback(
({address, chainID}: TTokenAndChain): TNormalizedBN => {
const price = tokens?.[chainID || 1]?.[address]?.price;
if (!price) {
return toNormalizedBN(prices?.[chainID]?.[address] || 0, 6) || toNormalizedBN(0);
}
return price;
},
[prices, tokens]
);

/* 🔵 - Yearn Finance ******************************************************
** Setup and render the Context provider to use in the app.
***************************************************************************/
const contextValue = useMemo(
(): TWalletContext => ({
getToken,
getBalance,
getPrice,
balances: tokens,
cumulatedValueInV2Vaults,
cumulatedValueInV3Vaults,
isLoading: isLoading || false,
shouldUseForknetBalances,
onRefresh,
triggerForknetBalances: (): void =>
set_shouldUseForknetBalances((s): boolean => {
const isEnabled = !s;
if (!(window as any).ethereum) {
(window as any).ethereum = {};
}
(window as any).ethereum.useForknetForMainnet = isEnabled;
return isEnabled;
})
}),
[
getToken,
getBalance,
getPrice,
tokens,
cumulatedValueInV2Vaults,
cumulatedValueInV3Vaults,
isLoading,
shouldUseForknetBalances,
onRefresh
]
);

return <YearnWalletContext.Provider value={contextValue}>{children}</YearnWalletContext.Provider>;
});

export const useYearnWallet = (): TWalletContext => useContext(YearnWalletContext);
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import {useWeb3} from '@builtbymom/web3/contexts/useWeb3';
import {useFetch} from '@builtbymom/web3/hooks/useFetch';
import {useDeepCompareMemo} from '@react-hookz/web';

import {useYDaemonBaseURI} from '../hooks/useYDaemonBaseURI';
import {yDaemonEarnedSchema} from '../utils/schemas/yDaemonEarnedSchema';
import {useYDaemonBaseURI} from './useYDaemonBaseURI';

import type {TYDaemonEarned} from '../utils/schemas/yDaemonEarnedSchema';

function useYearnEarned(): TYDaemonEarned {
/******************************************************************************
** The useFetchYearnEarnedForUser hook is used to fetch an estimate of the
** amount earned by the user. This estimate is calculated by the yDaemon API
** based on the events emitted by the yearn contracts catched by the subgraph.
*****************************************************************************/
function useFetchYearnEarnedForUser(): TYDaemonEarned {
const {address} = useWeb3();
const {yDaemonBaseUri: yDaemonBaseUriWithoutChain} = useYDaemonBaseURI();

Expand All @@ -34,4 +39,4 @@ function useYearnEarned(): TYDaemonEarned {
return memorizedEarned;
}

export {useYearnEarned};
export {useFetchYearnEarnedForUser};
Loading

0 comments on commit c587b12

Please sign in to comment.