Skip to content

Commit

Permalink
Feat/veyfi rewards (#407)
Browse files Browse the repository at this point in the history
* feat: unlock rewards

* fix: chain

---------

Co-authored-by: Majorfi <[email protected]>
  • Loading branch information
Majorfi and Majorfi committed Oct 25, 2023
1 parent aeaad65 commit 4a0f967
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 68 deletions.
209 changes: 155 additions & 54 deletions apps/veyfi/components/RewardsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import {useCallback, useMemo, useState} from 'react';
import {useGauge} from '@veYFI/contexts/useGauge';
import {useOption} from '@veYFI/contexts/useOption';
import {YFI_REWARD_POOL_ABI} from '@veYFI/utils/abi/YFIRewardPool.abi';
import * as GaugeActions from '@veYFI/utils/actions/gauge';
import {VEYFI_CHAIN_ID} from '@veYFI/utils/constants';
import {VEYFI_CHAIN_ID, VEYFI_DYFI_REWARD_POOL, VEYFI_YFI_REWARD_POOL} from '@veYFI/utils/constants';
import {prepareWriteContract} from '@wagmi/core';
import {Button} from '@yearn-finance/web-lib/components/Button';
import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
import {toAddress, truncateHex} from '@yearn-finance/web-lib/utils/address';
import {isZeroAddress, toAddress, truncateHex} from '@yearn-finance/web-lib/utils/address';
import {YFI_ADDRESS} from '@yearn-finance/web-lib/utils/constants';
import {toBigInt, toNormalizedBN} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatCounterValue} from '@yearn-finance/web-lib/utils/format.value';
import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction';
import {AmountInput} from '@common/components/AmountInput';
import {Dropdown} from '@common/components/Dropdown';
import {Input} from '@common/components/Input';
import {useYearn} from '@common/contexts/useYearn';
import {useAsyncTrigger} from '@common/hooks/useAsyncEffect';
import {useTokenPrice} from '@common/hooks/useTokenPrice';

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

export function RewardsTab(): ReactElement {
function GaugeRewards(): ReactElement {
const [selectedGauge, set_selectedGauge] = useState<TDropdownOption>();
const {provider, isActive} = useWeb3();
const {gaugesMap, positionsMap, refresh: refreshGauges} = useGauge();
Expand Down Expand Up @@ -93,66 +98,162 @@ export function RewardsTab(): ReactElement {
</Button>
</div>
</div>
</div>
);
}

<div className={'h-[1px] w-full bg-neutral-300'} />
function BoostRewards(): ReactElement {
const {provider, address} = useWeb3();
const {dYFIPrice} = useOption();
const [claimable, set_claimable] = useState<TNormalizedBN>(toNormalizedBN(0));
const [claimStatus, set_claimStatus] = useState(defaultTxStatus);

<div className={'flex flex-col opacity-40'}>
<div className={'flex flex-col gap-4'}>
<h2 className={'m-0 text-2xl font-bold'}>{'veYFI boost rewards'}</h2>
<div className={'text-neutral-600'}>
<p className={'w-2/3 whitespace-break-spaces'}>
{
'These are rewards clawed from the game theoretically suboptimal hands of gauge stakers who farm without a max boost. Their loss is your gain (literally).'
}
</p>
</div>
</div>
const onRefreshClaimable = useAsyncTrigger(async (): Promise<void> => {
if (isZeroAddress(address)) {
set_claimable(toNormalizedBN(0));
return;
}
try {
const {result} = await prepareWriteContract({
chainId: VEYFI_CHAIN_ID,
address: VEYFI_DYFI_REWARD_POOL,
abi: YFI_REWARD_POOL_ABI,
functionName: 'claim',
account: address
});
set_claimable(toNormalizedBN(result, 18));
} catch (error) {
console.warn(`[err - BoostRewards]: static call reverted when trying to get claimable amount.`);
set_claimable(toNormalizedBN(0));
}
}, [address]);

<div className={'mt-10 grid grid-cols-1 gap-4 md:grid-cols-4'}>
<Input
label={'Unclaimed veYFI boost rewards (dYFI)'}
value={'Coming soon…'}
isDisabled
/>
<Button
className={'w-full md:mt-7'}
onClick={onClaim}
isDisabled={!isActive || toBigInt(selectedGaugeRewards.raw) === 0n || !claimStatus.none}
isBusy={claimStatus.pending}>
{'Claim'}
</Button>
const onClaim = useCallback(async (): Promise<void> => {
const result = await GaugeActions.claimBoostRewards({
connector: provider,
chainID: VEYFI_CHAIN_ID,
contractAddress: VEYFI_DYFI_REWARD_POOL,
statusHandler: set_claimStatus
});
if (result.isSuccessful) {
onRefreshClaimable();
}
}, [provider, onRefreshClaimable]);

return (
<div className={'flex flex-col'}>
<div className={'flex flex-col gap-4'}>
<h2 className={'m-0 text-2xl font-bold'}>{'veYFI boost rewards'}</h2>
<div className={'text-neutral-600'}>
<p className={'w-2/3 whitespace-break-spaces'}>
{
'These are rewards clawed from the game theoretically suboptimal hands of gauge stakers who farm without a max boost. Their loss is your gain (literally).'
}
</p>
</div>
</div>

<div className={'h-[1px] w-full bg-neutral-300'} />
<div className={'mt-10 grid grid-cols-1 gap-4 md:grid-cols-4'}>
<AmountInput
label={'Unclaimed veYFI boost rewards (dYFI)'}
amount={claimable}
legend={formatCounterValue(claimable.normalized, dYFIPrice)}
disabled
/>
<Button
className={'w-full md:mt-7'}
onClick={onClaim}
isDisabled={isZeroAddress(address) || toBigInt(claimable.raw) === 0n || !claimStatus.none}
isBusy={claimStatus.pending}>
{'Claim'}
</Button>
</div>
</div>
);
}

<div className={'flex flex-col opacity-40'}>
<div className={'flex flex-col gap-4'}>
<h2 className={'m-0 text-2xl font-bold'}>{'veYFI exit rewards'}</h2>
<div className={'text-neutral-600'}>
<p className={'w-2/3 whitespace-break-spaces'}>
{
'When some spaghetti handed locker takes an early exit from their veYFI lock, their penalty is distributed amongst other lockers. It’s like a loyalty bonus, but instead of cheaper groceries you get sweet sweet YFI.'
}
</p>
</div>
</div>
function ExitRewards(): ReactElement {
const {provider, address} = useWeb3();
const yfiPrice = useTokenPrice(YFI_ADDRESS);
const [claimable, set_claimable] = useState<TNormalizedBN>(toNormalizedBN(0));
const [claimStatus, set_claimStatus] = useState(defaultTxStatus);

<div className={'mt-10 grid grid-cols-1 gap-4 md:grid-cols-4'}>
<Input
label={'Unclaimed veYFI exit rewards (YFI)'}
value={'Coming soon…'}
isDisabled
/>
<Button
className={'w-full md:mt-7'}
onClick={onClaim}
isDisabled={!isActive || toBigInt(selectedGaugeRewards.raw) === 0n || !claimStatus.none}
isBusy={claimStatus.pending}>
{'Claim'}
</Button>
const onRefreshClaimable = useAsyncTrigger(async (): Promise<void> => {
if (isZeroAddress(address)) {
set_claimable(toNormalizedBN(0));
return;
}
try {
const {result} = await prepareWriteContract({
chainId: VEYFI_CHAIN_ID,
address: VEYFI_YFI_REWARD_POOL,
abi: YFI_REWARD_POOL_ABI,
functionName: 'claim',
account: address
});
set_claimable(toNormalizedBN(result, 18));
} catch (error) {
console.error(`[err - ExitRewards]: static call reverted when trying to get claimable amount.`);
set_claimable(toNormalizedBN(0));
}
}, [address]);

const onClaim = useCallback(async (): Promise<void> => {
const result = await GaugeActions.claimBoostRewards({
connector: provider,
chainID: VEYFI_CHAIN_ID,
contractAddress: VEYFI_YFI_REWARD_POOL,
statusHandler: set_claimStatus
});
if (result.isSuccessful) {
onRefreshClaimable();
}
}, [provider, onRefreshClaimable]);

return (
<div className={'flex flex-col'}>
<div className={'flex flex-col gap-4'}>
<h2 className={'m-0 text-2xl font-bold'}>{'veYFI exit rewards'}</h2>
<div className={'text-neutral-600'}>
<p className={'w-2/3 whitespace-break-spaces'}>
{
'When some spaghetti handed locker takes an early exit from their veYFI lock, their penalty is distributed amongst other lockers. It’s like a loyalty bonus, but instead of cheaper groceries you get sweet sweet YFI.'
}
</p>
</div>
</div>

<div className={'mt-10 grid grid-cols-1 gap-4 md:grid-cols-4'}>
<AmountInput
label={'Unclaimed veYFI exit rewards (YFI)'}
amount={claimable}
legend={formatCounterValue(claimable.normalized, yfiPrice)}
disabled
/>
<Button
className={'w-full md:mt-7'}
onClick={onClaim}
isDisabled={isZeroAddress(address) || toBigInt(claimable.raw) === 0n || !claimStatus.none}
isBusy={claimStatus.pending}>
{'Claim'}
</Button>
</div>
</div>
);
}

export function RewardsTab(): ReactElement {
return (
<div className={'flex flex-col gap-6 md:gap-10'}>
<GaugeRewards />

<div className={'h-[1px] w-full bg-neutral-300'} />

<BoostRewards />

<div className={'h-[1px] w-full bg-neutral-300'} />

<ExitRewards />
</div>
);
}
19 changes: 12 additions & 7 deletions apps/veyfi/contexts/useOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,18 @@ export const OptionContextApp = memo(function OptionContextApp({children}: {chil
const yfiPrice = useTokenPrice(YFI_ADDRESS);

const getRequiredEth = useCallback(async (amount: bigint): Promise<bigint> => {
return readContract({
address: VEYFI_OPTIONS_ADDRESS,
abi: VEYFI_OPTIONS_ABI,
functionName: 'eth_required',
args: [amount],
chainId: VEYFI_CHAIN_ID
});
try {
const result = await readContract({
address: VEYFI_OPTIONS_ADDRESS,
abi: VEYFI_OPTIONS_ABI,
functionName: 'eth_required',
args: [amount],
chainId: VEYFI_CHAIN_ID
});
return result;
} catch (error) {
return BIG_ZERO;
}
}, []);

const refreshPrice = useAsyncTrigger(async (): Promise<void> => {
Expand Down
1 change: 0 additions & 1 deletion apps/veyfi/hooks/useVeYFIAPR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ function useVeYFIAPR({dYFIPrice}: TUseVeYFIAPR): number {
yfiPrice
);
}, [rate, dYFIPrice, yfiPrice, veYFISupply]);
console.warn(APR);

return APR;
}
Expand Down
Loading

0 comments on commit 4a0f967

Please sign in to comment.