Skip to content

Commit

Permalink
feat: table and apr
Browse files Browse the repository at this point in the history
  • Loading branch information
Majorfi committed Oct 18, 2023
1 parent 2a8d994 commit 9bd444b
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 57 deletions.
31 changes: 29 additions & 2 deletions apps/common/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {usePagination} from '@common/hooks/usePagination';
import {IconChevronPlain} from '@common/icons/IconChevronPlain';

import type {ReactElement} from 'react';
import type {TNormalizedBN} from '@common/types/types';

type TSortOrder = 'asc' | 'desc';

Expand Down Expand Up @@ -36,9 +37,10 @@ type TTableProps<T> = {
initialSortBy?: Extract<keyof T, string>;
onRowClick?: (item: T) => void;
itemsPerPage?: number;
isLoading?: boolean;
}

export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, itemsPerPage}: TTableProps<T>): ReactElement {
export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, itemsPerPage, isLoading}: TTableProps<T>): ReactElement {
const [{sortedBy, order}, set_state] = useState<TState<T>>({sortedBy: initialSortBy, order: 'desc'});

const sortedData = useMemo((): T[] => {
Expand Down Expand Up @@ -112,6 +114,25 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
))}
</div>

{currentItems.length === 0 && isLoading ?
(
<div className={'flex h-96 w-full flex-col items-center justify-center px-10 py-2'}>
<b className={'text-lg'}>{'Fetching gauge data'}</b>
<p className={'text-neutral-600'}>{'We are retrieving the gauges. Please wait.'}</p>
<div className={'flex h-10 items-center justify-center'}>
<span className={'loader'} />
</div>
</div>
) : currentItems.length === 0 && !isLoading ?
(
<div className={'flex h-96 w-full flex-col items-center justify-center px-10 py-2'}>
<b className={'text-lg'}>{'No Gauges'}</b>
<p className={'text-neutral-600'}>
{'No gauges available.'}
</p>
</div>
) : null
}
{currentItems.map((item, rowIndex): ReactElement => (
<div
key={`row_${rowIndex}`}
Expand Down Expand Up @@ -143,7 +164,13 @@ export function Table<T>({metadata, data, columns, initialSortBy, onRowClick, it
{!fullWidth && <label className={'inline text-start text-sm text-neutral-500 md:hidden'}>{label}</label>}
<div
className={cl(
isZero(item[key] as number) ? 'text-neutral-400' : 'text-neutral-900',
(
isZero(item[key] as number)
||
(item[key] as TNormalizedBN)?.raw !== undefined && isZero((item[key] as TNormalizedBN).raw)
||
(item[key] === undefined || item[key] === null)
) ? 'text-neutral-400' : 'text-neutral-900',
isNumber ? 'font-number' : 'font-aeonik',
fullWidth ? 'w-full' : undefined
)}>
Expand Down
107 changes: 71 additions & 36 deletions apps/veyfi/components/GaugesTab.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {useCallback, useState} from 'react';
import {useDeepCompareMemo} from '@react-hookz/web';
import {useGauge} from '@veYFI/contexts/useGauge';
import {useOption} from '@veYFI/contexts/useOption';
import {approveAndStake, stake, unstake} from '@veYFI/utils/actions/gauge';
import {VEYFI_CHAIN_ID} from '@veYFI/utils/constants';
import {SECONDS_PER_YEAR, VEYFI_CHAIN_ID} from '@veYFI/utils/constants';
import {Button} from '@yearn-finance/web-lib/components/Button';
import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
import {allowanceKey, toAddress, truncateHex} from '@yearn-finance/web-lib/utils/address';
import {toBigInt, toNormalizedBN} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatToNormalizedValue, toBigInt, toNormalizedBN} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatAmount, formatPercent} from '@yearn-finance/web-lib/utils/format.number';
import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction';
import {ImageWithFallback} from '@common/components/ImageWithFallback';
Expand All @@ -26,7 +27,7 @@ type TGaugeData = {
vaultName: string,
vaultApy: number,
vaultDeposited: TNormalizedBN,
gaugeApy: number,
gaugeAPR: number,
gaugeBoost: number,
gaugeStaked: TNormalizedBN,
allowance: TNormalizedBN,
Expand All @@ -38,11 +39,11 @@ function GaugeTabButtons({isApproved, vaultAddress, gaugeAddress, vaultDeposited
const {provider, address, isActive} = useWeb3();
const {refresh: refreshGauges} = useGauge();
const {refresh: refreshBalances} = useWallet();
const refreshData = (): unknown => Promise.all([refreshGauges(), refreshBalances()]);
const [approveAndStakeStatus, set_approveAndStakeStatus] = useState(defaultTxStatus);
const [stakeStatus, set_stakeStatus] = useState(defaultTxStatus);
const [unstakeStatus, set_unstakeStatus] = useState(defaultTxStatus);
const userAddress = address as TAddress;
const refreshData = useCallback((): unknown => Promise.all([refreshGauges(), refreshBalances()]), [refreshGauges, refreshBalances]);

const onApproveAndStake = useCallback(async (vaultAddress: TAddress, gaugeAddress: TAddress, amount: bigint): Promise<void> => {
const response = await approveAndStake({
Expand Down Expand Up @@ -121,36 +122,60 @@ function GaugeTabButtons({isApproved, vaultAddress, gaugeAddress, vaultDeposited

export function GaugesTab(): ReactElement {
const {address} = useWeb3();
const {gaugeAddresses, gaugesMap, positionsMap, allowancesMap} = useGauge();
const {vaults} = useYearn();
const {gaugesMap, positionsMap, allowancesMap} = useGauge();
const {vaults, prices} = useYearn();
const {balances} = useWallet();
const {dYFIPrice} = useOption();
const [isLoadingGauges, set_isLoadingGauges] = useState(true);
const userAddress = address as TAddress;

const gaugesData = useDeepCompareMemo((): TGaugeData[] => {
return (
gaugeAddresses.map((address): TGaugeData => {
const gauge = gaugesMap[address];
const vaultAddress = toAddress(gauge?.vaultAddress);
const vault = vaults[vaultAddress];

return ({
gaugeAddress: address,
vaultAddress,
decimals: gauge?.decimals ?? 18,
vaultIcon: `${process.env.BASE_YEARN_ASSETS_URI}/1/${vaultAddress}/logo-128.png`,
vaultName: vault?.display_name ?? `Vault ${truncateHex(vaultAddress, 4)}`,
vaultApy: vault?.apy.net_apy ?? 0,
vaultDeposited: balances[vaultAddress],
gaugeApy: 0, // TODO: gauge apy calcs
gaugeBoost: positionsMap[address]?.boost ?? 1,
gaugeStaked: positionsMap[address]?.deposit ?? toNormalizedBN(0),
allowance: allowancesMap[allowanceKey(VEYFI_CHAIN_ID, vaultAddress, address, userAddress)],
isApproved: toBigInt(allowancesMap[allowanceKey(VEYFI_CHAIN_ID, vaultAddress, address, userAddress)]?.raw) >= toBigInt(balances[vaultAddress]?.raw),
actions: undefined
});
})
);
}, [gaugesMap, gaugeAddresses, vaults, balances, positionsMap, allowancesMap, userAddress]);
if (!vaults || Object.values(vaults).length === 0) {
return [];
}

const data: TGaugeData[] = [];
for (const gauge of Object.values(gaugesMap)) {
if (!gauge) {
continue;
}

const vault = vaults[toAddress(gauge?.vaultAddress)];
const tokenPrice = formatToNormalizedValue(toBigInt(prices?.[vault.token.address] || 0), 6);
const boost = Number(positionsMap[gauge.address]?.boost || 1);
let APRFor10xBoost = Number(gauge?.rewardRate.normalized || 0) * dYFIPrice * SECONDS_PER_YEAR / Number(gauge?.totalStaked.normalized || 0) / tokenPrice * 100;
if (tokenPrice === 0 || Number(gauge?.totalStaked.normalized || 0) === 0) {
APRFor10xBoost = 0;
}

console.warn(`${Number(gauge?.rewardRate.normalized || 0)} x ${dYFIPrice} x ${SECONDS_PER_YEAR} / ${Number(gauge?.totalStaked.normalized || 0)} / ${tokenPrice} = ${APRFor10xBoost}`);

// gauge.rewardRate() * dYFI_price * seconds_per_year / gauge.totalAssets() / vault_token_price


// const APR = rewardRate * 31556952n / totalAssets.raw;
// console.log(APR)
// gauge.rewardRate() * seconds_per_year / gauge.totalAssets() / vault_token_price

data.push({
gaugeAddress: gauge.address,
vaultAddress: vault.address,
decimals: gauge.decimals,
vaultIcon: `${process.env.BASE_YEARN_ASSETS_URI}/1/${vault.address}/logo-128.png`,
vaultName: vault?.display_name ?? `Vault ${truncateHex(vault.address, 4)}`,
vaultApy: vault?.apy.net_apy ?? 0,
vaultDeposited: balances[vault.address],
gaugeAPR: APRFor10xBoost,
gaugeBoost: boost,
gaugeStaked: positionsMap[gauge.address]?.deposit ?? toNormalizedBN(0),
allowance: allowancesMap[allowanceKey(VEYFI_CHAIN_ID, vault.address, gauge.address, userAddress)],
isApproved: toBigInt(allowancesMap[allowanceKey(VEYFI_CHAIN_ID, vault.address, gauge.address, userAddress)]?.raw) >= toBigInt(balances[vault.address]?.raw),
actions: undefined
});
}
set_isLoadingGauges(false);
return data;
}, [gaugesMap, vaults, balances, positionsMap, allowancesMap, userAddress]);


return (
Expand All @@ -160,7 +185,7 @@ export function GaugesTab(): ReactElement {
{
key: 'vaultName',
label: 'Asset',
columnSpan: 2,
columnSpan: 3,
sortable: true,
fullWidth: true,
className: 'my-4 md:my-0',
Expand Down Expand Up @@ -193,9 +218,17 @@ export function GaugesTab(): ReactElement {
format: ({vaultDeposited}): string => formatAmount(vaultDeposited?.normalized || 0, 2, 6)
},
{
key: 'gaugeApy',
label: 'Gauge APY',
sortable: true
key: 'gaugeAPR',
label: 'Gauge APR',
columnSpan: 2,
sortable: true,
className: 'whitespace-break text-right',
format: ({gaugeAPR}): string => {
// if (gaugeAPR === 0) {
// return formatAmount(gaugeAPR, 2, 2);
// }
return `${formatAmount(gaugeAPR / 10, 2, 2)}% → ${formatAmount(gaugeAPR, 2, 2)}%`;
}
},
{
key: 'gaugeBoost',
Expand All @@ -207,6 +240,7 @@ export function GaugesTab(): ReactElement {
key: 'gaugeStaked',
label: 'Staked',
sortable: true,
className: 'mr-4',
format: ({gaugeStaked}): string => formatAmount(gaugeStaked?.normalized || 0, 2, 6)
},
{
Expand All @@ -218,9 +252,10 @@ export function GaugesTab(): ReactElement {
transform: (props): ReactElement => <GaugeTabButtons {...props} />
}
]}
isLoading={isLoadingGauges}
data={gaugesData}
columns={9}
initialSortBy={'gaugeApy'}
columns={11}
initialSortBy={'gaugeAPR'}
/>
</div>
);
Expand Down
30 changes: 17 additions & 13 deletions apps/veyfi/components/RewardsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useState} from 'react';
import {useCallback, useMemo, useState} from 'react';
import {useGauge} from '@veYFI/contexts/useGauge';
import {useOption} from '@veYFI/contexts/useOption';
import * as GaugeActions from '@veYFI/utils/actions/gauge';
Expand All @@ -19,7 +19,7 @@ import type {TDropdownOption} from '@common/components/Dropdown';
export function RewardsTab(): ReactElement {
const [selectedGauge, set_selectedGauge] = useState<TDropdownOption>();
const {provider, isActive} = useWeb3();
const {gaugeAddresses, gaugesMap, positionsMap, refresh: refreshGauges} = useGauge();
const {gaugesMap, positionsMap, refresh: refreshGauges} = useGauge();
const {dYFIPrice} = useOption();
const {vaults} = useYearn();
const refreshData = useCallback((): unknown => Promise.all([refreshGauges()]), [refreshGauges]);
Expand All @@ -39,18 +39,22 @@ export function RewardsTab(): ReactElement {
}
}, [provider, refreshData, selectedGaugeAddress]);

const gaugeOptions = gaugeAddresses.filter((address): boolean => toBigInt(positionsMap[address]?.reward?.raw) > 0n ?? false)
.map((address): TDropdownOption => {
const gauge = gaugesMap[address];
const vaultAddress = toAddress(gauge?.vaultAddress);
const vault = vaults[vaultAddress];
const gaugeOptions = useMemo((): TDropdownOption[] => {
const options: TDropdownOption[] = [];
for (const gauge of Object.values(gaugesMap)) {
if (!gauge || toBigInt(positionsMap[gauge.address]?.reward?.raw) === 0n) {
continue;
}
const vault = vaults[toAddress(gauge?.vaultAddress)];

return {
id: address,
label: vault?.display_name ?? `Vault ${truncateHex(vaultAddress, 4)}`,
icon: `${process.env.BASE_YEARN_ASSETS_URI}/1/${vaultAddress}/logo-128.png`
};
});
options.push({
id: gauge.address,
label: vault?.display_name ?? `Vault ${truncateHex(vault.address, 4)}`,
icon: `${process.env.BASE_YEARN_ASSETS_URI}/1/${vault.address}/logo-128.png`
});
}
return options;
}, [gaugesMap, positionsMap, vaults]);

return (
<div className={'flex flex-col gap-6 md:gap-10'}>
Expand Down
18 changes: 12 additions & 6 deletions apps/veyfi/contexts/useGauge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type TGauge = {
symbol: string,
decimals: number,
totalStaked: TNormalizedBN,
// apy?: number;
rewardRate: TNormalizedBN,
}

export type TGaugePosition = {
Expand All @@ -33,14 +33,12 @@ export type TGaugePosition = {
}

export type TGaugeContext = {
gaugeAddresses: TAddress[],
gaugesMap: TDict<TGauge | undefined>,
positionsMap: TDict<TGaugePosition | undefined>,
allowancesMap: TDict<TNormalizedBN>,
refresh: () => void,
}
const defaultProps: TGaugeContext = {
gaugeAddresses: [],
gaugesMap: {},
positionsMap: {},
allowancesMap: {},
Expand All @@ -62,17 +60,26 @@ export const GaugeContextApp = memo(function GaugeContextApp({children}: {childr
{address: gaugeAddress, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'name'},
{address: gaugeAddress, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'symbol'},
{address: gaugeAddress, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'decimals'},
{address: gaugeAddress, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'totalAssets'}
{address: gaugeAddress, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'totalAssets'},
{address: gaugeAddress, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'rewardRate'}
]
});
const decimals = Number(decodeAsBigInt(results[3])) || decodeAsNumber(results[3]);
const totalAssets = toNormalizedBN(decodeAsBigInt(results[4]), decimals);
const rewardRate = toNormalizedBN(decodeAsBigInt(results[5]), 18);

//Debug value to test
// const totalAssets = toNormalizedBN(400000000000000000000n, decimals);
// const rewardRate = toNormalizedBN(3306878306878n, 18);

return ({
address: gaugeAddress,
vaultAddress: decodeAsAddress(results[0]),
name: decodeAsString(results[1]),
symbol: decodeAsString(results[2]),
decimals: decimals,
totalStaked: toNormalizedBN(decodeAsBigInt(results[4]), decimals)
totalStaked: totalAssets,
rewardRate
});
});

Expand Down Expand Up @@ -183,7 +190,6 @@ export const GaugeContextApp = memo(function GaugeContextApp({children}: {childr
}, [refreshAllowances, refreshPositions, refreshVotingEscrow]);

const contextValue = useDeepCompareMemo((): TGaugeContext => ({
gaugeAddresses: gauges.map((gauge): TAddress => toAddress(gauge.address)),
gaugesMap: keyBy(gauges, 'address'),
positionsMap: positionsMap,
allowancesMap: allowancesMap ?? {},
Expand Down
1 change: 1 addition & 0 deletions apps/veyfi/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const VEYFI_DYFI_ADDRESS = toAddress('0x41252E8691e964f7DE35156B68493bAb6
export const SNAPSHOT_DELEGATE_REGISTRY_ADDRESS = toAddress('0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446');
export const YEARN_SNAPSHOT_SPACE = 'veyfi.eth';

export const SECONDS_PER_YEAR = 31556952;
export const MAX_LOCK_TIME: TWeeks = 208;
export const MIN_LOCK_TIME: TWeeks = 1;
export const MIN_LOCK_AMOUNT: TWeeks = 1;
Expand Down

0 comments on commit 9bd444b

Please sign in to comment.