From 4010ee2862cebe0ab6d945fd1686668bd4ff3f0f Mon Sep 17 00:00:00 2001 From: Major <90963895+Majorfi@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:59:44 +0200 Subject: [PATCH] fix: factory part (#391) Co-authored-by: Majorfi --- apps/common/contexts/useCurve.tsx | 7 +- apps/common/schemas/curveSchemas.ts | 49 ++------ pages/vaults/factory.tsx | 169 ++++++++++++++-------------- 3 files changed, 98 insertions(+), 127 deletions(-) diff --git a/apps/common/contexts/useCurve.tsx b/apps/common/contexts/useCurve.tsx index bbb5d51ab..04478b4ad 100755 --- a/apps/common/contexts/useCurve.tsx +++ b/apps/common/contexts/useCurve.tsx @@ -1,4 +1,5 @@ import {createContext, useContext, useMemo} from 'react'; +import {isZeroAddress} from '@yearn-finance/web-lib/utils/address'; import {useFetch} from '@common/hooks/useFetch'; import {coinGeckoPricesSchema} from '@common/schemas/coinGeckoSchemas'; import {curveAllGaugesSchema, curveGaugesFromYearnSchema, curveWeeklyFeesSchema} from '@common/schemas/curveSchemas'; @@ -53,7 +54,7 @@ export const CurveContextApp = ({children}: {children: React.ReactElement}): Rea }); const {data: gaugesFromYearn} = useFetch({ - endpoint: 'https://api.yearn.fi/v1/chains/1/apy-previews/curve-factory', + endpoint: 'https://api.yexporter.io/v1/chains/1/apy-previews/curve-factory', schema: curveGaugesFromYearnSchema }); @@ -83,7 +84,9 @@ export const CurveContextApp = ({children}: {children: React.ReactElement}): Rea cgPrices: cgPrices || defaultProps.cgPrices, gauges: gauges || defaultProps.gauges, isLoadingGauges: isLoadingGauges || defaultProps.isLoadingGauges, - gaugesFromYearn: gaugesFromYearn || defaultProps.gaugesFromYearn + gaugesFromYearn: (gaugesFromYearn || defaultProps.gaugesFromYearn).filter( + (props): boolean => !isZeroAddress(props.gauge_address) + ) }), [curveWeeklyFees, cgPrices, gauges, isLoadingGauges, gaugesFromYearn] ); diff --git a/apps/common/schemas/curveSchemas.ts b/apps/common/schemas/curveSchemas.ts index 3c2ef5831..349e0af30 100644 --- a/apps/common/schemas/curveSchemas.ts +++ b/apps/common/schemas/curveSchemas.ts @@ -64,8 +64,8 @@ export const curveAllGaugesSchema = z.object({ export const curveGaugeFromYearnSchema = z.object({ gauge_name: z.string(), - gauge_address: z.string().transform(toAddress), - pool_address: z.string().transform(toAddress), + gauge_address: z.string().optional().transform(toAddress), + pool_address: z.string().optional().transform(toAddress), pool_coins: z .object({ name: z.string().optional(), @@ -74,44 +74,15 @@ export const curveGaugeFromYearnSchema = z.object({ }) .array() .optional(), - lp_token: z.string().transform(toAddress), - weight: z.string(), - inflation_rate: z.string(), - working_supply: z.string(), - apr: z.object({ + lp_token: z.string().optional().transform(toAddress), + weight: z.string().optional().default('0'), + inflation_rate: z.string().default('0'), + working_supply: z.string().default('0'), + apy: z.object({ type: z.string(), - netAPR: z.number().default(0), - fees: z.object({ - performance: z.number().default(0), - withdrawal: z.number().default(0), - management: z.number().default(0), - keepCRV: z.number().default(0), - keepVelo: z.number().default(0), - cvxKeepCRV: z.number().default(0) - }), - extra: z.object({ - stakingRewardsAPR: z.number().default(0) - }), - points: z.object({ - weekAgo: z.number().default(0), - monthAgo: z.number().default(0), - inception: z.number().default(0) - }), - forwardAPR: z.object({ - type: z.string(), - netAPR: z.number().default(0), - composite: z.object({ - boost: z.number().default(0), - poolAPY: z.number().default(0), - boostedAPR: z.number().default(0), - baseAPR: z.number().default(0), - cvxAPR: z.number().default(0), - rewardsAPR: z.number().default(0) - }) - }) - }), - updated: z.number(), - block: z.number() + gross_apr: z.number().default(0), + net_apy: z.number().default(0) + }) }); export const curveGaugesFromYearnSchema = curveGaugeFromYearnSchema.array(); diff --git a/pages/vaults/factory.tsx b/pages/vaults/factory.tsx index 5d16891ae..b84884699 100644 --- a/pages/vaults/factory.tsx +++ b/pages/vaults/factory.tsx @@ -1,6 +1,4 @@ -import {useCallback, useEffect, useMemo, useState} from 'react'; -import {Balancer} from 'react-wrap-balancer'; -import {useAsync} from '@react-hookz/web'; +import {useCallback, useMemo, useState} from 'react'; import {VaultListFactory} from '@vaults/components/list/VaultListFactory'; import {YFACTORY_SUPPORTED_NETWORK} from '@vaults/constants'; import {VAULT_FACTORY_ABI} from '@vaults/utils/abi/vaultFactory.abi'; @@ -9,10 +7,10 @@ import {Wrapper} from '@vaults/Wrapper'; import {erc20ABI, multicall} from '@wagmi/core'; import {Button} from '@yearn-finance/web-lib/components/Button'; import {Renderable} from '@yearn-finance/web-lib/components/Renderable'; -import {yToast} from '@yearn-finance/web-lib/components/yToast'; +import {toast} from '@yearn-finance/web-lib/components/yToast'; import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; import {IconLinkOut} from '@yearn-finance/web-lib/icons/IconLinkOut'; -import {toAddress} from '@yearn-finance/web-lib/utils/address'; +import {isZeroAddress, toAddress} from '@yearn-finance/web-lib/utils/address'; import {VAULT_FACTORY_ADDRESS, ZERO_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; import {decodeAsBoolean, decodeAsString} from '@yearn-finance/web-lib/utils/decoder'; import {formatAmount} from '@yearn-finance/web-lib/utils/format.number'; @@ -23,6 +21,7 @@ import {Dropdown} from '@common/components/GaugeDropdown'; import {ImageWithFallback} from '@common/components/ImageWithFallback'; import {CurveContextApp, useCurve} from '@common/contexts/useCurve'; import {useYearn} from '@common/contexts/useYearn'; +import {useAsyncTrigger} from '@common/hooks/useAsyncEffect'; import type {NextRouter} from 'next/router'; import type {ReactElement} from 'react'; @@ -52,7 +51,10 @@ function Factory(): ReactElement { const {mutateVaultList} = useYearn(); const {provider, isActive} = useWeb3(); const {gaugesFromYearn} = useCurve(); - const {toast} = yToast(); + const [filteredGauges, set_filteredGauges] = useState([]); + const [gaugeDisplayData, set_gaugeDisplayData] = useState(undefined); + const [isLoadingGaugeDisplay, set_isLoadingGaugeDisplay] = useState(false); + const [estimate, set_estimate] = useState(0n); const [selectedOption, set_selectedOption] = useState(defaultOption); const [hasError, set_hasError] = useState(false); const [txStatus, set_txStatus] = useState(defaultTxStatus); @@ -62,11 +64,9 @@ function Factory(): ReactElement { ** This means we need to check, for all the gauges if we already have an ** associated vault. **************************************************************************/ - const [{result: filteredGauges}, fetchGaugesAction] = useAsync(async function fetchAlreadyCreatedGauges( - _gaugesFromYearn: TCurveGaugesFromYearn - ): Promise { - if (isZero((_gaugesFromYearn || []).length)) { - return []; + const onRetriggerGaugesAction = useAsyncTrigger(async (): Promise => { + if (isZero((gaugesFromYearn || []).length)) { + return set_filteredGauges([]); } const baseContract = { @@ -74,7 +74,7 @@ function Factory(): ReactElement { abi: VAULT_FACTORY_ABI }; const calls = []; - for (const gauge of _gaugesFromYearn) { + for (const gauge of gaugesFromYearn) { calls.push({ ...baseContract, functionName: 'canCreateVaultPermissionlessly', @@ -85,14 +85,12 @@ function Factory(): ReactElement { contracts: calls, chainId: YFACTORY_SUPPORTED_NETWORK }); - return _gaugesFromYearn.filter((_gauge: TCurveGaugeFromYearn, index: number): boolean => - decodeAsBoolean(canCreateVaults[index]) + set_filteredGauges( + gaugesFromYearn.filter((_gauge: TCurveGaugeFromYearn, index: number): boolean => + decodeAsBoolean(canCreateVaults[index]) + ) ); - }, []); - - useEffect((): void => { - fetchGaugesAction.execute(gaugesFromYearn); - }, [fetchGaugesAction, gaugesFromYearn]); + }, [gaugesFromYearn]); /* 🔵 - Yearn Finance ****************************************************** ** We need to create the possible elements for the dropdown by removing all @@ -118,7 +116,7 @@ function Factory(): ReactElement { tokenAddress: toAddress(gauge.lp_token), poolAddress: toAddress(gauge.pool_address), gaugeAddress: toAddress(gauge.gauge_address), - APY: gauge.apr.netAPR + APY: gauge?.apy?.net_apy || 0 } }) ); @@ -128,49 +126,50 @@ function Factory(): ReactElement { ** Name and symbol from the Curve API are not the one we want to display. ** We need to fetch the name and symbol from the gauge contract. **************************************************************************/ - const [{result: gaugeDisplayData, status}, fetchGaugeDisplayDataAction] = useAsync( - async function fetchGaugeDisplayData(_selectedOption: TDropdownGaugeOption): Promise { - const baseContract = { - address: _selectedOption.value.gaugeAddress, - abi: erc20ABI - }; - const results = await multicall({ - contracts: [ - {...baseContract, functionName: 'name'}, - {...baseContract, functionName: 'symbol'} - ], - chainId: YFACTORY_SUPPORTED_NETWORK - }); - - const name = decodeAsString(results[0]); - const symbol = decodeAsString(results[1]); - return { - name: name.replace('Curve.fi', '').replace('Gauge Deposit', '') || _selectedOption.value.name, - symbol: symbol.replace('-gauge', '').replace('-f', '') || _selectedOption.value.name, - poolAddress: _selectedOption.value.poolAddress, - gaugeAddress: _selectedOption.value.gaugeAddress - }; - }, - undefined - ); + useAsyncTrigger(async (): Promise => { + set_isLoadingGaugeDisplay(true); + const baseContract = { + address: selectedOption.value.gaugeAddress, + abi: erc20ABI + }; + const results = await multicall({ + contracts: [ + {...baseContract, functionName: 'name'}, + {...baseContract, functionName: 'symbol'} + ], + chainId: YFACTORY_SUPPORTED_NETWORK + }); - useEffect((): void => { - fetchGaugeDisplayDataAction.execute(selectedOption); - }, [fetchGaugeDisplayDataAction, selectedOption]); + const name = decodeAsString(results[0]); + const symbol = decodeAsString(results[1]); + set_gaugeDisplayData({ + name: name.replace('Curve.fi', '').replace('Gauge Deposit', '') || selectedOption.value.name, + symbol: symbol.replace('-gauge', '').replace('-f', '') || selectedOption.value.name, + poolAddress: selectedOption.value.poolAddress, + gaugeAddress: selectedOption.value.gaugeAddress + }); + set_isLoadingGaugeDisplay(false); + }, [selectedOption]); /* 🔵 - Yearn Finance ****************************************************** ** Perform a smartContract call to the ZAP contract to get the expected ** out for a given in/out pair with a specific amount. **************************************************************************/ - const [{result: estimate}, actions] = useAsync(async function fetchEstimate(): Promise { + useAsyncTrigger(async (): Promise => { + if (!isActive || toAddress(selectedOption.value.gaugeAddress) === ZERO_ADDRESS) { + return; + } + set_hasError(false); try { - return await gasOfCreateNewVaultsAndStrategies({ - connector: provider, - chainID: YFACTORY_SUPPORTED_NETWORK, - contractAddress: VAULT_FACTORY_ADDRESS, - gaugeAddress: selectedOption.value.gaugeAddress - }); + set_estimate( + await gasOfCreateNewVaultsAndStrategies({ + connector: provider, + chainID: YFACTORY_SUPPORTED_NETWORK, + contractAddress: VAULT_FACTORY_ADDRESS, + gaugeAddress: selectedOption.value.gaugeAddress + }) + ); } catch (error) { const err = error as {reason: string; code: string}; if (err.code === 'UNPREDICTABLE_GAS_LIMIT') { @@ -185,15 +184,9 @@ function Factory(): ReactElement { }); set_hasError(true); } - return 0n; - } - }, 0n); - useEffect((): void => { - if (!isActive || toAddress(selectedOption.value.gaugeAddress) === ZERO_ADDRESS) { - return; + set_estimate(0n); } - actions.execute(); - }, [actions, isActive, provider, selectedOption, selectedOption.value.gaugeAddress]); + }, [isActive, provider, selectedOption.value.gaugeAddress]); const onCreateNewVault = useCallback(async (): Promise => { const result = await createNewVaultsAndStrategies({ @@ -205,10 +198,10 @@ function Factory(): ReactElement { }); if (result.isSuccessful) { await setTimeout(async (): Promise => { - await Promise.all([fetchGaugesAction.execute(gaugesFromYearn), mutateVaultList()]); + await Promise.all([onRetriggerGaugesAction(), mutateVaultList()]); }, 1000); } - }, [fetchGaugesAction, gaugesFromYearn, mutateVaultList, provider, selectedOption.value.gaugeAddress]); + }, [onRetriggerGaugesAction, mutateVaultList, provider, selectedOption.value.gaugeAddress]); function loadingFallback(): ReactElement { return ( @@ -227,19 +220,17 @@ function Factory(): ReactElement {

{'Create new Vault'}

- - { - 'Deploy a new auto-compounding yVault for any Curve pool with an active liquidity gauge. All factory-deployed vaults have no management fees and a flat 10% performance fee. Permissionless finance just got permissionless-er. To learn more, check our ' - } - - {'docs'} - - {'.'} - + { + 'Deploy a new auto-compounding yVault for any Curve pool with an active liquidity gauge. All factory-deployed vaults have no management fees and a flat 10% performance fee. Permissionless finance just got permissionless-er. To learn more, check our ' + } + + {'docs'} + + {'.'}

@@ -261,10 +252,10 @@ function Factory(): ReactElement {

{'Vault name'}

- {!gaugeDisplayData ? '' : `Curve ${gaugeDisplayData.name} Factory`} + {!gaugeDisplayData?.name ? '' : `Curve ${gaugeDisplayData.name} Factory`}
@@ -272,10 +263,10 @@ function Factory(): ReactElement {

{'Symbol'}

- {!gaugeDisplayData ? '' : `yvCurve-${gaugeDisplayData.symbol}-f`} + {!gaugeDisplayData?.symbol ? '' : `yvCurve-${gaugeDisplayData.symbol}-f`}
@@ -283,7 +274,7 @@ function Factory(): ReactElement {

{'Pool address'}

- {toAddress(gaugeDisplayData?.poolAddress)} + {!gaugeDisplayData?.poolAddress || + isZeroAddress(gaugeDisplayData?.poolAddress) + ? '-' + : toAddress(gaugeDisplayData?.poolAddress)}

{'Gauge address'}

- {toAddress(gaugeDisplayData?.gaugeAddress)} + {!gaugeDisplayData?.gaugeAddress || + isZeroAddress(gaugeDisplayData?.gaugeAddress) + ? '-' + : toAddress(gaugeDisplayData?.gaugeAddress)}