Skip to content

Commit

Permalink
fix: factory part
Browse files Browse the repository at this point in the history
  • Loading branch information
Majorfi committed Oct 20, 2023
1 parent 61dcd7b commit 35c0a00
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 127 deletions.
7 changes: 5 additions & 2 deletions apps/common/contexts/useCurve.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -53,7 +54,7 @@ export const CurveContextApp = ({children}: {children: React.ReactElement}): Rea
});

const {data: gaugesFromYearn} = useFetch<TCurveGaugesFromYearn>({
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
});

Expand Down Expand Up @@ -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]
);
Expand Down
49 changes: 10 additions & 39 deletions apps/common/schemas/curveSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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();
Expand Down
169 changes: 83 additions & 86 deletions pages/vaults/factory.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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<TCurveGaugesFromYearn>([]);
const [gaugeDisplayData, set_gaugeDisplayData] = useState<TGaugeDisplayData | undefined>(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);
Expand All @@ -62,19 +64,17 @@ 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<TCurveGaugesFromYearn> {
if (isZero((_gaugesFromYearn || []).length)) {
return [];
const onRetriggerGaugesAction = useAsyncTrigger(async (): Promise<void> => {
if (isZero((gaugesFromYearn || []).length)) {
return set_filteredGauges([]);
}

const baseContract = {
address: VAULT_FACTORY_ADDRESS,
abi: VAULT_FACTORY_ABI
};
const calls = [];
for (const gauge of _gaugesFromYearn) {
for (const gauge of gaugesFromYearn) {
calls.push({
...baseContract,
functionName: 'canCreateVaultPermissionlessly',
Expand All @@ -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
Expand 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
}
})
);
Expand All @@ -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<TGaugeDisplayData> {
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<void> => {
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<bigint> {
useAsyncTrigger(async (): Promise<void> => {
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') {
Expand All @@ -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<void> => {
const result = await createNewVaultsAndStrategies({
Expand All @@ -205,10 +198,10 @@ function Factory(): ReactElement {
});
if (result.isSuccessful) {
await setTimeout(async (): Promise<void> => {
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 (
Expand All @@ -227,19 +220,17 @@ function Factory(): ReactElement {
<h2 className={'pb-4 text-3xl font-bold'}>{'Create new Vault'}</h2>
<div className={'w-full md:w-7/12'}>
<p>
<Balancer>
{
'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 '
}
<a
href={'https://docs.yearn.fi/getting-started/products/yvaults/vault-factory'}
target={'_blank'}
className={'text-neutral-900 underline'}
rel={'noreferrer'}>
{'docs'}
</a>
{'.'}
</Balancer>
{
'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 '
}
<a
href={'https://docs.yearn.fi/getting-started/products/yvaults/vault-factory'}
target={'_blank'}
className={'text-neutral-900 underline'}
rel={'noreferrer'}>
{'docs'}
</a>
{'.'}
</p>
</div>
</div>
Expand All @@ -261,37 +252,40 @@ function Factory(): ReactElement {
<div className={'col-span-2 w-full space-y-1'}>
<p className={'text-neutral-600'}>{'Vault name'}</p>
<Renderable
shouldRender={status !== 'loading'}
shouldRender={!isLoadingGaugeDisplay}
fallback={loadingFallback()}>
<div className={'h-10 bg-neutral-200 p-2 text-neutral-600'}>
{!gaugeDisplayData ? '' : `Curve ${gaugeDisplayData.name} Factory`}
{!gaugeDisplayData?.name ? '' : `Curve ${gaugeDisplayData.name} Factory`}
</div>
</Renderable>
</div>

<div className={'col-span-2 w-full space-y-1'}>
<p className={'text-neutral-600'}>{'Symbol'}</p>
<Renderable
shouldRender={status !== 'loading'}
shouldRender={!isLoadingGaugeDisplay}
fallback={loadingFallback()}>
<div className={'h-10 bg-neutral-200 p-2 text-neutral-600'}>
{!gaugeDisplayData ? '' : `yvCurve-${gaugeDisplayData.symbol}-f`}
{!gaugeDisplayData?.symbol ? '' : `yvCurve-${gaugeDisplayData.symbol}-f`}
</div>
</Renderable>
</div>

<div className={'col-span-3 w-full space-y-1'}>
<p className={'text-neutral-600'}>{'Pool address'}</p>
<Renderable
shouldRender={status !== 'loading'}
shouldRender={!isLoadingGaugeDisplay}
fallback={loadingFallback()}>
<div
className={
'flex h-10 flex-row items-center justify-between bg-neutral-200 p-2 font-mono'
}>
<Renderable shouldRender={!!gaugeDisplayData}>
<p className={'overflow-hidden text-ellipsis text-neutral-600'}>
{toAddress(gaugeDisplayData?.poolAddress)}
{!gaugeDisplayData?.poolAddress ||
isZeroAddress(gaugeDisplayData?.poolAddress)
? '-'
: toAddress(gaugeDisplayData?.poolAddress)}
</p>
<a
href={`${getNetwork(YFACTORY_SUPPORTED_NETWORK)
Expand All @@ -310,15 +304,18 @@ function Factory(): ReactElement {
<div className={'col-span-3 w-full space-y-1'}>
<p className={'text-neutral-600'}>{'Gauge address'}</p>
<Renderable
shouldRender={status !== 'loading'}
shouldRender={!isLoadingGaugeDisplay}
fallback={loadingFallback()}>
<div
className={
'flex h-10 flex-row items-center justify-between bg-neutral-200 p-2 font-mono'
}>
<Renderable shouldRender={!!gaugeDisplayData}>
<p className={'overflow-hidden text-ellipsis text-neutral-600'}>
{toAddress(gaugeDisplayData?.gaugeAddress)}
{!gaugeDisplayData?.gaugeAddress ||
isZeroAddress(gaugeDisplayData?.gaugeAddress)
? '-'
: toAddress(gaugeDisplayData?.gaugeAddress)}
</p>
<a
href={`${getNetwork(YFACTORY_SUPPORTED_NETWORK)
Expand Down

0 comments on commit 35c0a00

Please sign in to comment.