diff --git a/apps/veyfi/components/RedeemTab.tsx b/apps/veyfi/components/RedeemTab.tsx index 75512a1f8..612a29cc8 100644 --- a/apps/veyfi/components/RedeemTab.tsx +++ b/apps/veyfi/components/RedeemTab.tsx @@ -108,7 +108,9 @@ export function RedeemTab(): ReactElement { 'Got dYFI, want YFI? You’ve come to the right place. Redeem dYFI for YFI by paying the redemption cost in ETH. Enjoy your cheap YFI anon.' }

- + {`Current discount: ${formatAmount(Number(discount.normalized) * 100, 2, 2)}%`} @@ -125,10 +127,14 @@ export function RedeemTab(): ReactElement { error={redeemAmountError} legend={
-

+

{formatCounterValue(redeemAmount.normalized, dYFIPrice)}

-

{`You have: ${formatAmount( +

{`You have: ${formatAmount( dYFIBalance.normalized, 2, 6 @@ -142,13 +148,17 @@ export function RedeemTab(): ReactElement { amount={ethRequired} legend={

-

+

{formatCounterValue( ethRequired.normalized, Number(ethBalance.price.normalized) ?? 0 )}

-

{`You have: ${formatAmount( +

{`You have: ${formatAmount( ethBalance.balance.normalized, 2, 6 @@ -163,10 +173,14 @@ export function RedeemTab(): ReactElement { amount={redeemAmount} legend={

-

+

{formatCounterValue(redeemAmount.normalized, yfiPrice)}

-

{`You have: ${formatAmount( +

{`You have: ${formatAmount( yfiBalance.normalized, 2, 6 diff --git a/apps/veyfi/contexts/useGauge.tsx b/apps/veyfi/contexts/useGauge.tsx index eab7b38f3..66e24403b 100644 --- a/apps/veyfi/contexts/useGauge.tsx +++ b/apps/veyfi/contexts/useGauge.tsx @@ -1,4 +1,4 @@ -import React, {createContext, memo, useContext, useState} from 'react'; +import React, {createContext, memo, useCallback, useContext, useState} from 'react'; import {FixedNumber} from 'ethers'; import {useDeepCompareMemo} from '@react-hookz/web'; import {VEYFI_GAUGE_ABI} from '@veYFI/utils/abi/veYFIGauge.abi'; @@ -145,7 +145,7 @@ export const GaugeContextApp = memo(function GaugeContextApp({children}: {childr set_positionsMap(allPositionsAsMap); }, [address, gauges, isActive]); - const refresh = useAsyncTrigger(async (): Promise => { + const refresh = useCallback(async (): Promise => { refreshVotingEscrow(); refreshPositions(); }, [refreshPositions, refreshVotingEscrow]); diff --git a/apps/veyfi/contexts/useOption.tsx b/apps/veyfi/contexts/useOption.tsx index a50149458..21950d3ee 100644 --- a/apps/veyfi/contexts/useOption.tsx +++ b/apps/veyfi/contexts/useOption.tsx @@ -75,7 +75,7 @@ export const OptionContextApp = memo(function OptionContextApp({children}: {chil set_position(toNormalizedBN(dYFIBalance)); }, [userAddress]); - const refresh = useAsyncTrigger(async (): Promise => { + const refresh = useCallback(async (): Promise => { refreshPrice(); refreshPositions(); }, [refreshPrice, refreshPositions]); diff --git a/apps/veyfi/hooks/useVeYFIAPR.ts b/apps/veyfi/hooks/useVeYFIAPR.ts new file mode 100644 index 000000000..f558b57d8 --- /dev/null +++ b/apps/veyfi/hooks/useVeYFIAPR.ts @@ -0,0 +1,137 @@ +import {useMemo, useState} from 'react'; +import {useContractRead} from 'wagmi'; +import {VEYFI_ABI} from '@veYFI/utils/abi/veYFI.abi'; +import {VEYFI_GAUGE_ABI} from '@veYFI/utils/abi/veYFIGauge.abi'; +import {SECONDS_PER_YEAR, VE_YFI_GAUGES, VEYFI_CHAIN_ID} from '@veYFI/utils/constants'; +import {readContracts} from '@wagmi/core'; +import {toAddress} from '@yearn-finance/web-lib/utils/address'; +import {VEYFI_ADDRESS, YFI_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; +import {decodeAsBigInt} from '@yearn-finance/web-lib/utils/decoder'; +import {toBigInt, toNormalizedBN} from '@yearn-finance/web-lib/utils/format.bigNumber'; +import {getClient} from '@yearn-finance/web-lib/utils/wagmi/utils'; +import {useAsyncTrigger} from '@common/hooks/useAsyncEffect'; +import {useTokenPrice} from '@common/hooks/useTokenPrice'; + +import type {TAddress} from '@yearn-finance/web-lib/types'; +import type {TNormalizedBN} from '@common/types/types'; + +type TUseVeYFIAPR = { + dYFIPrice: number; +}; +function useVeYFIAPR({dYFIPrice}: TUseVeYFIAPR): number { + const [rate, set_rate] = useState(0n); + const yfiPrice = useTokenPrice(YFI_ADDRESS); + const {data: veYFISupply} = useContractRead({ + address: VEYFI_ADDRESS, + abi: VEYFI_ABI, + functionName: 'totalSupply', + chainId: VEYFI_CHAIN_ID + }); + + useAsyncTrigger(async (): Promise => { + const publicClient = getClient(VEYFI_CHAIN_ID); + const rangeLimit = toBigInt(process.env.RANGE_LIMIT); + const currentBlockNumber = await publicClient.getBlockNumber(); + const from = 18373500n; + + const depositors: [{address: TAddress; gauge: TAddress; balance: TNormalizedBN}] = [] as any; + /* 🔵 - Yearn Finance ********************************************************************** + ** First we need to retrieve all the depositors in a gauge + ******************************************************************************************/ + for (let i = from; i < currentBlockNumber; i += rangeLimit) { + const logs = await publicClient.getLogs({ + address: VE_YFI_GAUGES, + events: [ + { + anonymous: false, + inputs: [ + {indexed: true, internalType: 'address', name: 'caller', type: 'address'}, + {indexed: true, internalType: 'address', name: 'owner', type: 'address'}, + {indexed: false, internalType: 'uint256', name: 'assets', type: 'uint256'}, + {indexed: false, internalType: 'uint256', name: 'shares', type: 'uint256'} + ], + name: 'Deposit', + type: 'event' + } + ], + fromBlock: i, + toBlock: i + rangeLimit + }); + for (const log of logs) { + depositors.push({address: toAddress(log.args.owner), gauge: log.address, balance: toNormalizedBN(0)}); + } + } + + /* 🔵 - Yearn Finance ********************************************************************** + ** Then, for each one of theses depositors, we need to check the current boostedBalance. + ******************************************************************************************/ + const allDepositorsBalances = await readContracts({ + contracts: depositors.map(({gauge, address}): any => ({ + address: gauge, + abi: VEYFI_GAUGE_ABI, + chainId: VEYFI_CHAIN_ID, + functionName: 'boostedBalanceOf', + args: [address] + })) + }); + for (let i = 0; i < depositors.length; i++) { + depositors[i].balance = toNormalizedBN(decodeAsBigInt(allDepositorsBalances[i]), 18); + } + + // Remove duplicates (on address and gauge) + const seen = new Set(); + const depositorsWithoutDuplicates = depositors.filter((depositor): boolean => { + const isDuplicate = seen.has(depositor.address + depositor.gauge); + seen.add(depositor.address + depositor.gauge); + return !isDuplicate; + }); + + // remove depositors with 0 balance + const depositorsWithBalance = depositorsWithoutDuplicates.filter( + (depositor): boolean => depositor.balance.raw > 0n + ); + + /* 🔵 - Yearn Finance ********************************************************************** + ** Then, for each gauge we need to know the totalSupply and the rewardRate + ******************************************************************************************/ + const calls = []; + for (const gauge of VE_YFI_GAUGES) { + calls.push({address: gauge, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'totalSupply'}); + calls.push({address: gauge, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'rewardRate'}); + } + const totalSupplyAndRewardRate = await readContracts({ + contracts: calls + }); + + /* 🔵 - Yearn Finance ********************************************************************** + ** Then we can calculate the rate for each gauge + ******************************************************************************************/ + let rate = 0n; + let index = 0; + for (const gauge of VE_YFI_GAUGES) { + const supply = decodeAsBigInt(totalSupplyAndRewardRate[index++]); + const rewardRate = decodeAsBigInt(totalSupplyAndRewardRate[index++]); + let boosted = 0n; + for (const depositor of depositorsWithBalance) { + if (toAddress(depositor.gauge) === toAddress(gauge)) { + boosted += depositor.balance.raw; + } + } + rate += (rewardRate * (supply - boosted)) / supply; + } + set_rate(rate); + }, []); + + const APR = useMemo((): number => { + return ( + (Number(toNormalizedBN(rate).normalized) * SECONDS_PER_YEAR * dYFIPrice) / + Number(toNormalizedBN(toBigInt(veYFISupply)).normalized) / + yfiPrice + ); + }, [rate, dYFIPrice, yfiPrice, veYFISupply]); + console.warn(APR); + + return APR; +} + +export {useVeYFIAPR}; diff --git a/apps/veyfi/utils/constants.ts b/apps/veyfi/utils/constants.ts index 1f317ee8e..10fc185d8 100644 --- a/apps/veyfi/utils/constants.ts +++ b/apps/veyfi/utils/constants.ts @@ -9,6 +9,8 @@ export const VEYFI_SUPPORTED_NETWORK = 1; export const VEYFI_REGISTRY_ADDRESS: TAddress = toAddress(''); // TODO: update once deployed export const VEYFI_OPTIONS_ADDRESS = toAddress('0x2fBa208E1B2106d40DaA472Cb7AE0c6C7EFc0224'); export const VEYFI_DYFI_ADDRESS = toAddress('0x41252E8691e964f7DE35156B68493bAb6797a275'); +export const VEYFI_ADDRESS = toAddress('0x90c1f9220d90d3966FbeE24045EDd73E1d588aD5'); +export const VEYFI_POSITION_HELPER_ADDRESS = toAddress('0x5A70cD937bA3Daec8188E937E243fFa43d6ECbe8'); export const SNAPSHOT_DELEGATE_REGISTRY_ADDRESS = toAddress('0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446'); export const YEARN_SNAPSHOT_SPACE = 'veyfi.eth'; diff --git a/next.config.js b/next.config.js index 67289af0c..9f03ad989 100755 --- a/next.config.js +++ b/next.config.js @@ -98,6 +98,7 @@ const config = { PARTNER_ID_ADDRESS: '0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52', SHOULD_USE_PARTNER_CONTRACT: true, YDAEMON_BASE_URI: process.env.YDAEMON_BASE_URI, + RANGE_LIMIT: 1_000_000, // YDAEMON_BASE_URI: 'https://ydaemon.ycorpo.com', // YDAEMON_BASE_URI: 'http://localhost:8080', diff --git a/pages/veyfi/index.tsx b/pages/veyfi/index.tsx index 612bfbdf5..9da159698 100755 --- a/pages/veyfi/index.tsx +++ b/pages/veyfi/index.tsx @@ -2,10 +2,12 @@ import {RedeemTab} from '@veYFI/components/RedeemTab'; import {RewardsTab} from '@veYFI/components/RewardsTab'; import {TabManageGauges} from '@veYFI/components/TabManageGauges'; import {TabManageVeYFI} from '@veYFI/components/TabManageVeYFI'; +import {useOption} from '@veYFI/contexts/useOption'; import {useVotingEscrow} from '@veYFI/contexts/useVotingEscrow'; +import {useVeYFIAPR} from '@veYFI/hooks/useVeYFIAPR'; import {Wrapper} from '@veYFI/Wrapper'; import {formatToNormalizedValue, toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber'; -import {formatAmount} from '@yearn-finance/web-lib/utils/format.number'; +import {formatAmount, formatPercent} from '@yearn-finance/web-lib/utils/format.number'; import {PageProgressBar} from '@common/components/PageProgressBar'; import {SummaryData} from '@common/components/SummaryData'; import {Tabs} from '@common/components/Tabs'; @@ -14,11 +16,38 @@ import {formatDateShort} from '@common/utils'; import type {NextRouter} from 'next/router'; import type {ReactElement} from 'react'; -function Index(): ReactElement { - const {votingEscrow, positions, isLoading} = useVotingEscrow(); +function HeadingData(): ReactElement { + const {votingEscrow, positions} = useVotingEscrow(); + const {dYFIPrice} = useOption(); + const APR = useVeYFIAPR({dYFIPrice}); const totalLockedYFI = formatToNormalizedValue(toBigInt(votingEscrow?.supply), 18); const yourLockedYFI = formatToNormalizedValue(toBigInt(positions?.deposit?.underlyingBalance), 18); + return ( + + ); +} +function Index(): ReactElement { + const {isLoading} = useVotingEscrow(); const tabs = [ {id: 'gauges', label: 'Manage Gauges', content: }, @@ -34,22 +63,7 @@ function Index(): ReactElement {

{'veYFI'}

- +