Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use TNormalizedBN #33

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions apps/common/components/AmountInput.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type {ReactElement} from 'react';

type TAmountInputProps = {
amount?: string;
amount: string | number;
onAmountChange?: (amount: string) => void;
maxAmount?: string;
maxLabel?: string;
onMaxAmountClick?: () => void;
label?: string;
placeholder?: string;
legend?: string;
Expand All @@ -16,8 +15,7 @@ type TAmountInputProps = {
function AmountInput({
amount,
onAmountChange,
maxAmount,
maxLabel = 'Max',
onMaxAmountClick,
label,
placeholder,
legend,
Expand All @@ -35,19 +33,19 @@ function AmountInput({
)}
<div className={'relative flex w-full items-center justify-center'}>
<input
className={`h-10 w-full p-2 font-mono text-base font-normal outline-none ${maxAmount && !disabled ? 'pr-12' : null} ${error ? 'border border-solid border-[#EA5204] focus:border-[#EA5204]' : 'border-0 border-none'} ${disabled ? 'bg-neutral-300 text-[#5B5B5B]' : null}`}
className={`h-10 w-full p-2 font-mono text-base font-normal outline-none ${error ? 'border border-solid border-[#EA5204] focus:border-[#EA5204]' : 'border-0 border-none'} ${disabled ? 'bg-neutral-300 text-[#5B5B5B]' : null}`}
type={'number'}
aria-label={label}
value={amount}
onChange={onAmountChange ? (e): void => onAmountChange(e.target.value) : undefined}
placeholder={loading ? '' : placeholder ?? '0'}
disabled={disabled}
/>
{maxAmount && !disabled && (
{onMaxAmountClick && !disabled && (
<button
onClick={onAmountChange ? (): void => onAmountChange(maxAmount) : undefined}
onClick={onMaxAmountClick}
className={'absolute right-2 ml-2 h-6 cursor-pointer border-none bg-neutral-900 px-2 py-1 text-xs text-neutral-0 transition-colors hover:bg-neutral-700'}>
{maxLabel}
{'max'}
</button>
)}
</div>
Expand Down
24 changes: 22 additions & 2 deletions apps/common/utils/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import {ethers} from 'ethers';
import request from 'graphql-request';
import {toAddress} from '@yearn-finance/web-lib/utils/address';
import {LPYCRV_TOKEN_ADDRESS, YCRV_CURVE_POOL_ADDRESS, YVBOOST_TOKEN_ADDRESS, YVECRV_TOKEN_ADDRESS} from '@yearn-finance/web-lib/utils/constants';
import {formatToNormalizedValue} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatBN, formatToNormalizedValue} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatAmount} from '@yearn-finance/web-lib/utils/format.number';

import type {BigNumber} from 'ethers';
import type {BigNumber, BigNumberish} from 'ethers';
import type {GraphQLResponse} from 'graphql-request/dist/types';
import type {TDict} from '@yearn-finance/web-lib/utils/types';
import type {TNormalizedBN} from '@common/types/types';
Expand Down Expand Up @@ -83,6 +83,19 @@ export function handleInputChange(
return ({raw: raw, normalized: amount});
}

export function handleInputChangeEventValue(
value: string,
decimals: number
): TNormalizedBN {
let amount = value.replace(/,/g, '.').replace(/[^0-9.]/g, '');
const amountParts = amount.split('.');
if (amountParts.length === 2) {
amount = amountParts[0] + '.' + amountParts[1].slice(0, decimals);
}
const raw = ethers.utils.parseUnits(amount || '0', decimals);
return ({raw: raw, normalized: amount});
}

export function getVaultName(vault: TYearnVault): string {
const baseName = vault.display_name || vault.name || vault.formated_name || 'unknown';
if (baseName.includes(' yVault')) {
Expand All @@ -99,3 +112,10 @@ export const graphFetcher = async (args: [string, string]): Promise<GraphQLRespo
export const formatPercent = (n: number, min = 2, max = 2): string => `${formatAmount(n || 0, min, max)}%`;

export const formatUSD = (n: number, min = 2, max = 2): string => `$ ${formatAmount(n || 0, min, max)}`;

export const DefaultTNormalizedBN: TNormalizedBN = {raw: ethers.constants.Zero, normalized: 0};

export const toNormalizedBN = (value: BigNumberish, decimals?: number): TNormalizedBN => ({
raw: formatBN(value),
normalized: formatToNormalizedValue(formatBN(value), decimals || 18)
});
50 changes: 26 additions & 24 deletions apps/veyfi/components/LockTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,46 @@ import {useTransaction} from '@veYFI/hooks/useTransaction';
import {getVotingPower} from '@veYFI/utils';
import * as VotingEscrowActions from '@veYFI/utils/actions/votingEscrow';
import {MAX_LOCK_TIME, MIN_LOCK_AMOUNT, MIN_LOCK_TIME} from '@veYFI/utils/constants';
import {toRaw} from '@veYFI/utils/format';
import {validateAllowance, validateAmount} from '@veYFI/utils/validations';
import {validateAllowance, validateAmount, validateTime} from '@veYFI/utils/validations';
import {Button} from '@yearn-finance/web-lib/components/Button';
import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
import {toAddress} from '@yearn-finance/web-lib/utils/address';
import {BN, formatUnits} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatUnits} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatAmount} from '@yearn-finance/web-lib/utils/format.number';
import {fromWeeks, getTimeUntil, toSeconds, toTime, toWeeks} from '@yearn-finance/web-lib/utils/time';
import {toBN} from '@yearn-finance/web-lib/utils/to';
import {AmountInput} from '@common/components/AmountInput';
import {useWallet} from '@common/contexts/useWallet';
import {useBalance} from '@common/hooks/useBalance';

import {AmountInput} from '../../common/components/AmountInput';
import {DefaultTNormalizedBN, handleInputChangeEventValue, toNormalizedBN} from '@common/utils';

import type {ethers} from 'ethers';
import type {ReactElement} from 'react';
import type {TAddress} from '@yearn-finance/web-lib/utils/address';

function LockTab(): ReactElement {
const [lockAmount, set_lockAmount] = useState('');
const [lockTime, set_lockTime] = useState('');
const {provider, address} = useWeb3();
const {refresh: refreshBalances} = useWallet();
const {votingEscrow, positions, allowances, isLoading: isLoadingVotingEscrow, refresh: refreshVotingEscrow} = useVotingEscrow();
const tokenBalance = useBalance(toAddress(votingEscrow?.token));
const refreshData = (): unknown => Promise.all([refreshVotingEscrow(), refreshBalances()]);

const [lockAmount, set_lockAmount] = useState(DefaultTNormalizedBN);
const [lockTime, set_lockTime] = useState('');
const [approveLock, approveLockStatus] = useTransaction(VotingEscrowActions.approveLock, refreshData);
const [lock, lockStatus] = useTransaction(VotingEscrowActions.lock, refreshData);
const [increaseLockAmount, increaseLockAmountStatus] = useTransaction(VotingEscrowActions.increaseLockAmount, refreshData);
const tokenBalance = useBalance(toAddress(votingEscrow?.token));

const web3Provider = provider as ethers.providers.Web3Provider;
const userAddress = address as TAddress;
const hasLockedAmount = BN(positions?.deposit?.balance).gt(0);
const hasLockedAmount = toBN(positions?.deposit?.balance).gt(0);

const unlockTime = useMemo((): number => {
return positions?.unlockTime || Date.now() + fromWeeks(toTime(lockTime));
}, [positions?.unlockTime, lockTime]);

const votingPower = useMemo((): string => {
return getVotingPower(BN(positions?.deposit?.underlyingBalance).add(toRaw(lockAmount, 18)).toString(), unlockTime);
return getVotingPower(toBN(positions?.deposit?.underlyingBalance).add(lockAmount.raw).toString(), unlockTime);
}, [positions?.deposit?.underlyingBalance, lockAmount, unlockTime]);

useEffect((): void => {
Expand All @@ -56,19 +57,19 @@ function LockTab(): ReactElement {
tokenAddress: toAddress(votingEscrow?.token),
spenderAddress: toAddress(votingEscrow?.address),
allowances,
amount: toRaw(lockAmount, 18)
amount: lockAmount.raw
});

const {isValid: isValidLockAmount, error: lockAmountError} = validateAmount({
amount: lockAmount || '0',
balance: tokenBalance.normalized.toString(),
minAmountAllowed: hasLockedAmount ? '0' : MIN_LOCK_AMOUNT.toString(),
amount: lockAmount,
balance: tokenBalance,
minAmountAllowed: hasLockedAmount ? DefaultTNormalizedBN : toNormalizedBN(MIN_LOCK_AMOUNT),
shouldDisplayMin: true
});

const {isValid: isValidLockTime, error: lockTimeError} = validateAmount({
amount: lockTime,
minAmountAllowed: hasLockedAmount ? '0' : MIN_LOCK_TIME.toString()
const {isValid: isValidLockTime, error: lockTimeError} = validateTime({
amount: toTime(lockTime),
minAmountAllowed: toTime(hasLockedAmount ? 0 : MIN_LOCK_TIME)
});

const executeApprove = (): void => {
Expand All @@ -79,7 +80,8 @@ function LockTab(): ReactElement {
web3Provider,
userAddress,
votingEscrow.token,
votingEscrow.address
votingEscrow.address,
lockAmount.raw
);
};

Expand All @@ -91,7 +93,7 @@ function LockTab(): ReactElement {
web3Provider,
userAddress,
votingEscrow.address,
toRaw(lockAmount, 18),
lockAmount.raw,
toSeconds(unlockTime)
);
};
Expand All @@ -104,7 +106,7 @@ function LockTab(): ReactElement {
web3Provider,
userAddress,
votingEscrow.address,
toRaw(lockAmount, 18)
lockAmount.raw
);
};

Expand Down Expand Up @@ -154,17 +156,17 @@ function LockTab(): ReactElement {
<div className={'mt-0 grid grid-cols-1 gap-6 md:mt-14 md:grid-cols-2'}>
<AmountInput
label={'YFI'}
amount={lockAmount}
onAmountChange={set_lockAmount}
maxAmount={tokenBalance.normalized > 0 ? tokenBalance.normalized.toFixed(18) : ''}
amount={lockAmount.normalized}
onAmountChange={(amount: string): void => set_lockAmount(handleInputChangeEventValue(amount, 18))}
onMaxAmountClick={(): void => set_lockAmount(tokenBalance)}
legend={`Available: ${formatAmount(tokenBalance.normalized, 4)} YFI`}
error={lockAmountError}
/>
<AmountInput
label={'Current lock period (weeks)'}
amount={toTime(lockTime) === 0 ? '' : Math.floor(toTime(lockTime)).toString()}
onAmountChange={set_lockTime}
maxAmount={(MAX_LOCK_TIME + 1).toString()}
onMaxAmountClick={(): void => set_lockTime((MAX_LOCK_TIME + 1).toString())}
disabled={hasLockedAmount}
legend={'min 1'}
error={lockTimeError}
Expand Down
14 changes: 6 additions & 8 deletions apps/veyfi/components/ManageLockTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {useTransaction} from '@veYFI/hooks/useTransaction';
import {getVotingPower} from '@veYFI/utils';
import * as VotingEscrowActions from '@veYFI/utils/actions/votingEscrow';
import {MAX_LOCK_TIME, MIN_LOCK_TIME} from '@veYFI/utils/constants';
import {validateAmount} from '@veYFI/utils/validations';
import {validateTime} from '@veYFI/utils/validations';
import {Button} from '@yearn-finance/web-lib/components/Button';
import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
import {BN, formatUnits} from '@yearn-finance/web-lib/utils/format.bigNumber';
Expand Down Expand Up @@ -42,9 +42,9 @@ function ManageLockTab(): ReactElement {
return willExtendLock ? getVotingPower(positions?.deposit?.underlyingBalance, newUnlockTime) : positions?.deposit?.balance;
}, [positions?.deposit, newUnlockTime, willExtendLock]);

const {isValid: isValidLockTime, error: lockTimeError} = validateAmount({
amount: votingEscrow ? lockTime : MIN_LOCK_TIME.toString(),
minAmountAllowed: MIN_LOCK_TIME.toString()
const {isValid: isValidLockTime, error: lockTimeError} = validateTime({
amount: toTime(votingEscrow ? lockTime : MIN_LOCK_TIME),
minAmountAllowed: MIN_LOCK_TIME
});

const executeExtendLockTime = (): void => {
Expand Down Expand Up @@ -90,10 +90,8 @@ function ManageLockTab(): ReactElement {
<AmountInput
label={'Increase lock period (weeks)'}
amount={lockTime}
onAmountChange={(amount): unknown => set_lockTime(Math.floor(toTime(amount)).toString())}
maxAmount={
BN(MAX_LOCK_TIME).sub(weeksToUnlock).gt(0) ? BN(MAX_LOCK_TIME).sub(weeksToUnlock).toString() : '0'
}
onAmountChange={(amount): void => set_lockTime(Math.floor(toTime(amount)).toString())}
onMaxAmountClick={(): void => set_lockTime(Math.floor(toTime(BN(MAX_LOCK_TIME).sub(weeksToUnlock).gt(0) ? BN(MAX_LOCK_TIME).sub(weeksToUnlock).toString() : '0')).toString())}
disabled={!hasLockedAmount}
error={lockTimeError}
legend={'min 1'}
Expand Down
2 changes: 1 addition & 1 deletion apps/veyfi/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type TAmount = string;
export type TAmount = string | number;
export type TFormattedAmount = string;
export type TRaw = string;
export type TUnit = string;
11 changes: 3 additions & 8 deletions apps/veyfi/utils/actions/votingEscrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import VEYFI_ABI from '../abi/veYFI.abi';
import type {BigNumber} from 'ethers';
import type {TAddress} from '@yearn-finance/web-lib/utils/address';
import type {TSeconds} from '@yearn-finance/web-lib/utils/time';
import type {TRaw} from '@veYFI/types';

export async function approveLock(
provider: ethers.providers.Web3Provider,
accountAddress: TAddress,
tokenAddress: TAddress,
votingEscrowAddress: TAddress,
amount?: TRaw
amount?: BigNumber
): Promise<ethers.providers.TransactionResponse> {
const signer = provider.getSigner(accountAddress);
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
Expand All @@ -24,7 +23,7 @@ export async function lock(
provider: ethers.providers.Web3Provider,
accountAddress: TAddress,
votingEscrowAddress: TAddress,
amount: TRaw,
amount: BigNumber,
time: TSeconds
): Promise<ethers.providers.TransactionResponse> {
const signer = provider.getSigner(accountAddress);
Expand All @@ -36,12 +35,11 @@ export async function increaseLockAmount(
provider: ethers.providers.Web3Provider,
accountAddress: TAddress,
votingEscrowAddress: TAddress,
amount: TRaw
amount: BigNumber
): Promise<ethers.providers.TransactionResponse> {
const signer = provider.getSigner(accountAddress);
const votingEscrowContract = new ethers.Contract(votingEscrowAddress, VEYFI_ABI, signer);
return await votingEscrowContract.modify_lock(amount, '0', accountAddress);

}

export async function extendLockTime(
Expand All @@ -53,7 +51,6 @@ export async function extendLockTime(
const signer = provider.getSigner(accountAddress);
const votingEscrowContract = new ethers.Contract(votingEscrowAddress, VEYFI_ABI, signer);
return await votingEscrowContract.modify_lock('0', time, accountAddress);

}

export async function withdrawUnlocked(
Expand All @@ -68,7 +65,6 @@ export async function withdrawUnlocked(
throw new Error('Tokens are not yet unlocked');
}
return await votingEscrowContract.withdraw();

}

export async function withdrawLocked(
Expand All @@ -79,5 +75,4 @@ export async function withdrawLocked(
const signer = provider.getSigner(accountAddress);
const votingEscrowContract = new ethers.Contract(votingEscrowAddress, VEYFI_ABI, signer);
return await votingEscrowContract.withdraw();

}
4 changes: 3 additions & 1 deletion apps/veyfi/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {ethers} from 'ethers';
import {toAddress} from '@yearn-finance/web-lib/utils/address';

import type {BigNumber} from 'ethers';
import type {TAddress} from '@yearn-finance/web-lib/utils/address';
import type {TWeeks} from '@yearn-finance/web-lib/utils/time';

Expand All @@ -9,4 +11,4 @@ export const YFI_ADDRESS: TAddress = toAddress('0x0bc529c00C6401aEF6D220BE8C6Ea1

export const MAX_LOCK_TIME: TWeeks = 208;
export const MIN_LOCK_TIME: TWeeks = 1;
export const MIN_LOCK_AMOUNT: TWeeks = 1;
export const MIN_LOCK_AMOUNT: BigNumber = ethers.constants.One;
7 changes: 0 additions & 7 deletions apps/veyfi/utils/format.ts

This file was deleted.

Loading