diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5590c4413..ee90437e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 0.1.25 (28/08/2023)
+- Gauge voting for veYFI under the `gauge-voting` feature flag
+
# 0.1.24 (24/08/2023)
- Add `useFeatureFlag` hook to handle feature flags
diff --git a/apps/common/components/Dropdown.tsx b/apps/common/components/Dropdown.tsx
new file mode 100644
index 000000000..56074c384
--- /dev/null
+++ b/apps/common/components/Dropdown.tsx
@@ -0,0 +1,207 @@
+import React, {Fragment, useState} from 'react';
+import {Combobox, Transition} from '@headlessui/react';
+import {useThrottledState} from '@react-hookz/web';
+import {cl} from '@yearn-finance/web-lib/utils/cl';
+import performBatchedUpdates from '@yearn-finance/web-lib/utils/performBatchedUpdates';
+import IconChevron from '@common/icons/IconChevron';
+
+import {ImageWithFallback} from './ImageWithFallback';
+
+import type {ReactElement} from 'react';
+
+const DropdownOption = (option: TDropdownOption): ReactElement => {
+ const {label, description, icon} = option;
+ return (
+
+ {({active}): ReactElement => (
+
+ {icon && (
+
+
+
+ )}
+
+
+ {label}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ )}
+
+ );
+};
+
+const DropdownEmpty = ({isSearching}: {isSearching: boolean}): ReactElement => {
+ if(isSearching) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+export type TDropdownOption = {
+ id: string;
+ label: string;
+ description?: string;
+ icon?: string;
+}
+
+export type TDropdownProps = {
+ selected?: TDropdownOption;
+ options: TDropdownOption[];
+ onChange?: (selected: TDropdownOption) => void;
+ label?: string;
+ legend?: string;
+ isDisabled?: boolean;
+ className?: string;
+}
+
+const Dropdown = ({selected, options, onChange, label, legend, isDisabled, className}: TDropdownProps): ReactElement => {
+ const [isOpen, set_isOpen] = useThrottledState(false, 400);
+ const [search, set_search] = useState('');
+
+ const isSearching = search !== '';
+ const filteredOptions = isSearching
+ ? options.filter(({label}): boolean => label.toLowerCase().includes(search.toLowerCase()))
+ : options;
+
+ return (
+
+
+ {label &&
}
+
+ {isOpen ? (
+
{
+ e.stopPropagation();
+ e.preventDefault();
+ set_isOpen(false);
+ }} />
+ ) : null}
+
{
+ performBatchedUpdates((): void => {
+ if(onChange) {
+ onChange(option);
+ }
+ set_isOpen(false);
+ });
+ }}
+ disabled={isDisabled}
+ >
+ <>
+ set_isOpen((state: boolean): boolean => !state)}
+ className={cl(
+ 'flex h-10 w-full items-center justify-between p-2 text-base md:px-3',
+ isDisabled ? 'bg-neutral-300 text-neutral-600' : 'bg-neutral-0 text-neutral-900'
+ )}>
+
+ {selected?.icon && (
+
+
+
+ )}
+
+ option?.label ?? '-' }
+ spellCheck={false}
+ onChange={(event): void => {
+ performBatchedUpdates((): void => {
+ set_isOpen(true);
+ set_search(event.target.value);
+ });
+ }}
+ />
+
+
+
+
+
+
+ {
+ performBatchedUpdates((): void => {
+ set_isOpen(false);
+ set_search('');
+ });
+ }}>
+
+ {filteredOptions.length === 0 ? (
+
+ ) : (
+ filteredOptions.map(({id, label, description, icon}): ReactElement => (
+
+ ))
+ )}
+
+
+ >
+
+
+ {legend && (
+
+ {legend}
+
+ )}
+
+
+ );
+};
+
+export {Dropdown};
diff --git a/apps/common/components/Pagination.tsx b/apps/common/components/Pagination.tsx
new file mode 100644
index 000000000..164d68d02
--- /dev/null
+++ b/apps/common/components/Pagination.tsx
@@ -0,0 +1,63 @@
+import ReactPaginate from 'react-paginate';
+import IconPaginationArrow from '@common/icons/IconPaginationArrow';
+
+import type {ReactElement} from 'react';
+
+type TProps = {
+ range: [from: number, to: number];
+ pageCount: number;
+ numberOfItems: number;
+ onPageChange: (selectedItem: {
+ selected: number;
+ }) => void;
+}
+
+export function Pagination(props: TProps): ReactElement {
+ const {range: [from, to], pageCount, numberOfItems, onPageChange} = props;
+
+ return (
+ <>
+
+
+
+
+ {'Showing '}{from}{' to '}{to}{' of'}{' '}
+ {numberOfItems} {'results'}
+
+
+
}
+ nextLabel={
}
+ />
+
+
+ >
+ );
+}
diff --git a/apps/common/components/Table.tsx b/apps/common/components/Table.tsx
new file mode 100644
index 000000000..2fdad9d3b
--- /dev/null
+++ b/apps/common/components/Table.tsx
@@ -0,0 +1,162 @@
+/* eslint-disable tailwindcss/classnames-order */
+import {useCallback, useMemo, useState} from 'react';
+import {sort} from '@veYFI/utils';
+import {cl} from '@yearn-finance/web-lib/utils/cl';
+import {isZero} from '@yearn-finance/web-lib/utils/isZero';
+import {Pagination} from '@common/components/Pagination';
+import {usePagination} from '@common/hooks/usePagination';
+import IconChevronPlain from '@common/icons/IconChevronPlain';
+
+import type {ReactElement} from 'react';
+
+type TSortOrder = 'asc' | 'desc';
+
+type TState
= {
+ sortedBy: Extract | undefined,
+ order: TSortOrder
+}
+
+const switchOrder = (order: TSortOrder): TSortOrder => order === 'desc' ? 'asc' : 'desc';
+
+type TMetadata = {
+ key: Extract;
+ label: string;
+ className?: string;
+ sortable?: boolean;
+ fullWidth?: boolean;
+ columnSpan?: number;
+ format?: (item: T) => string | number;
+ transform?: (item: T) => ReactElement;
+}
+
+type TTableProps = {
+ metadata: TMetadata[];
+ data: T[];
+ columns?: number;
+ initialSortBy?: Extract;
+ onRowClick?: (item: T) => void;
+ itemsPerPage?: number;
+}
+
+function Table({metadata, data, columns, initialSortBy, onRowClick, itemsPerPage}: TTableProps): ReactElement {
+ const [{sortedBy, order}, set_state] = useState>({sortedBy: initialSortBy, order: 'desc'});
+
+ const sortedData = useMemo((): T[] => {
+ return sortedBy && order ? sort(data, sortedBy, order) : data;
+ }, [data, order, sortedBy]);
+
+ const {currentItems, paginationProps} = usePagination({data: sortedData, itemsPerPage: itemsPerPage || sortedData.length});
+
+ const handleSort = useCallback((key: Extract): void => {
+ const willChangeSortKey = sortedBy !== key;
+ const newOrder = switchOrder(willChangeSortKey ? 'asc' : order);
+ set_state({sortedBy: newOrder ? key : undefined, order: newOrder});
+ }, [order, sortedBy]);
+
+ const gridColsVariants = {
+ 1: 'md:grid-cols-1',
+ 2: 'md:grid-cols-2',
+ 3: 'md:grid-cols-3',
+ 4: 'md:grid-cols-4',
+ 5: 'md:grid-cols-5',
+ 6: 'md:grid-cols-6',
+ 7: 'md:grid-cols-7',
+ 8: 'md:grid-cols-8',
+ 9: 'md:grid-cols-9',
+ 10: 'md:grid-cols-10',
+ 11: 'md:grid-cols-11',
+ 12: 'md:grid-cols-12'
+ };
+
+ const numberOfColumns = Math.min(columns ?? (metadata.length), 12) as keyof typeof gridColsVariants;
+
+ const colSpanVariants = {
+ 1: 'md:col-span-1',
+ 2: 'md:col-span-2',
+ 3: 'md:col-span-3',
+ 4: 'md:col-span-4',
+ 5: 'md:col-span-5',
+ 6: 'md:col-span-6',
+ 7: 'md:col-span-7',
+ 8: 'md:col-span-8',
+ 9: 'md:col-span-9',
+ 10: 'md:col-span-10',
+ 11: 'md:col-span-11',
+ 12: 'md:col-span-12'
+ };
+
+ return (
+
+
+ {metadata.map(({key, label, sortable, className, columnSpan}): ReactElement => (
+
+ ))}
+
+
+ {currentItems.map((item, rowIndex): ReactElement => (
+
onRowClick?.(item)}
+ >
+ {metadata.map(({key, label, className, fullWidth, columnSpan, format, transform}): ReactElement => {
+ const isNumber = !isNaN(item[key] as number);
+
+ return (
+
+ {!fullWidth &&
}
+
+ {transform?.(item) ?? format?.(item).toString() ?? String(item[key])}
+
+
+ );
+ })}
+
+ ))}
+ {itemsPerPage && (
+
+ )}
+
+ );
+}
+
+export {Table};
diff --git a/apps/common/hooks/usePagination.ts b/apps/common/hooks/usePagination.ts
new file mode 100644
index 000000000..33ab2708b
--- /dev/null
+++ b/apps/common/hooks/usePagination.ts
@@ -0,0 +1,40 @@
+import {useState} from 'react';
+
+type TProps = {
+ data: T[];
+ itemsPerPage: number;
+}
+
+type TUsePaginationReturn = {
+ currentItems: T[];
+ paginationProps: {
+ range: [from: number, to: number];
+ pageCount: number;
+ numberOfItems: number;
+ onPageChange: (selectedItem: {
+ selected: number;
+ }) => void;
+ }
+}
+
+export function usePagination({data, itemsPerPage}: TProps): TUsePaginationReturn {
+ const [itemOffset, set_itemOffset] = useState(0);
+
+ const endOffset = itemOffset + itemsPerPage;
+
+ const currentItems = data.slice(itemOffset, endOffset);
+
+ const handlePageChange = ({selected}: {selected: number}): void => {
+ set_itemOffset((selected * itemsPerPage) % data.length);
+ };
+
+ return {
+ currentItems,
+ paginationProps: {
+ range: [endOffset - (itemsPerPage - 1), Math.min(endOffset, data.length)],
+ pageCount: Math.ceil(data.length / itemsPerPage),
+ numberOfItems: data.length,
+ onPageChange: handlePageChange
+ }
+ };
+}
diff --git a/apps/common/icons/IconPaginationArrow.tsx b/apps/common/icons/IconPaginationArrow.tsx
new file mode 100755
index 000000000..befd5a476
--- /dev/null
+++ b/apps/common/icons/IconPaginationArrow.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import type {ReactElement} from 'react';
+
+function IconPaginationArrow(props: React.SVGProps): ReactElement {
+ return (
+
+ );
+}
+
+export default IconPaginationArrow;
diff --git a/apps/common/types/types.ts b/apps/common/types/types.ts
index 8e66ff109..8c4c86dbf 100755
--- a/apps/common/types/types.ts
+++ b/apps/common/types/types.ts
@@ -2,6 +2,7 @@ import type {ReactElement} from 'react';
import type {TAddress, TDict} from '@yearn-finance/web-lib/types';
import type {TBalanceData} from '@yearn-finance/web-lib/types/hooks';
import type {TSolver} from '@common/schemas/yDaemonTokenListBalances';
+import type {multicall} from '@wagmi/core';
export type TClaimable = {
raw: bigint,
@@ -88,3 +89,5 @@ export type TMessariGraphData = {
}
export type TSortDirection = 'asc' | 'desc' | '';
+
+export type TMulticallContract = Parameters[0]['contracts'][0];
diff --git a/apps/common/utils/wagmiUtils.ts b/apps/common/utils/wagmiUtils.ts
new file mode 100644
index 000000000..41349b76e
--- /dev/null
+++ b/apps/common/utils/wagmiUtils.ts
@@ -0,0 +1,115 @@
+import {BaseError} from 'viem';
+import {captureException} from '@sentry/nextjs';
+import {prepareWriteContract, waitForTransaction, writeContract} from '@wagmi/core';
+import {toast} from '@yearn-finance/web-lib/components/yToast';
+import {toAddress} from '@yearn-finance/web-lib/utils/address';
+import {ZERO_ADDRESS} from '@yearn-finance/web-lib/utils/constants';
+import {toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber';
+import {isEth} from '@yearn-finance/web-lib/utils/isEth';
+import {isTAddress} from '@yearn-finance/web-lib/utils/isTAddress';
+import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction';
+import {assert} from '@common/utils/assert';
+
+import type {Abi, SimulateContractParameters} from 'viem';
+import type {Connector} from 'wagmi';
+import type {TAddress} from '@yearn-finance/web-lib/types';
+import type {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction';
+import type {GetWalletClientResult, WalletClient} from '@wagmi/core';
+
+export type TWagmiProviderContract = {
+ walletClient: GetWalletClientResult,
+ chainId: number,
+ address: TAddress,
+}
+export async function toWagmiProvider(connector: Connector | undefined): Promise {
+ assert(connector, 'Connector is not set');
+
+ const signer = await connector.getWalletClient();
+ const chainId = await connector.getChainId();
+ const {address} = signer.account;
+ return ({
+ walletClient: signer,
+ chainId,
+ address
+ });
+}
+
+export type TWriteTransaction = {
+ connector: Connector | undefined;
+ contractAddress: TAddress | undefined;
+ statusHandler?: (status: typeof defaultTxStatus) => void;
+ onTrySomethingElse?: () => Promise; //When the abi is incorrect, ex: usdt, we may need to bypass the error and try something else
+}
+
+export function assertAddress(addr: string | TAddress | undefined, name?: string): asserts addr is TAddress {
+ assert(addr, `${name || 'Address'} is not set`);
+ assert(isTAddress(addr), `${name || 'Address'} provided is invalid`);
+ assert(toAddress(addr) !== ZERO_ADDRESS, `${name || 'Address'} is 0x0`);
+ assert(!isEth(addr), `${name || 'Address'} is 0xE`);
+}
+
+export function assertAddresses(addresses: (string | TAddress | undefined)[], name?: string): asserts addresses is TAddress[] {
+ addresses.forEach((addr): void => assertAddress(addr, name));
+}
+
+type TPrepareWriteContractConfig<
+ TAbi extends Abi | readonly unknown[] = Abi,
+ TFunctionName extends string = string
+> = Omit, 'chain' | 'address'> & {
+ chainId?: number
+ walletClient?: WalletClient
+ address: TAddress | undefined
+}
+export async function handleTx<
+ TAbi extends Abi | readonly unknown[],
+ TFunctionName extends string
+>(
+ args: TWriteTransaction,
+ props: TPrepareWriteContractConfig
+): Promise {
+ args.statusHandler?.({...defaultTxStatus, pending: true});
+ const wagmiProvider = await toWagmiProvider(args.connector);
+
+ assertAddress(props.address, 'contractAddress');
+ assertAddress(wagmiProvider.address, 'userAddress');
+ try {
+ const config = await prepareWriteContract({
+ ...wagmiProvider,
+ ...props as TPrepareWriteContractConfig,
+ address: props.address,
+ value: toBigInt(props.value)
+ });
+ const {hash} = await writeContract(config.request);
+ const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash});
+ if (receipt.status === 'success') {
+ args.statusHandler?.({...defaultTxStatus, success: true});
+ } else if (receipt.status === 'reverted') {
+ args.statusHandler?.({...defaultTxStatus, error: true});
+ }
+ toast({type: 'success', content: 'Transaction successful!'});
+ return ({isSuccessful: receipt.status === 'success', receipt});
+ } catch (error) {
+ if (process.env.NODE_ENV === 'production') {
+ captureException(error);
+ }
+
+ if (!(error instanceof BaseError)) {
+ return ({isSuccessful: false, error});
+ }
+
+ if (args.onTrySomethingElse) {
+ if (error.name === 'ContractFunctionExecutionError') {
+ return await args.onTrySomethingElse();
+ }
+ }
+
+ toast({type: 'error', content: error.shortMessage});
+ args.statusHandler?.({...defaultTxStatus, error: true});
+ console.error(error);
+ return ({isSuccessful: false, error});
+ } finally {
+ setTimeout((): void => {
+ args.statusHandler?.({...defaultTxStatus});
+ }, 3000);
+ }
+}
diff --git a/apps/veyfi/Wrapper.tsx b/apps/veyfi/Wrapper.tsx
index 64dcd7512..3b866b2bc 100644
--- a/apps/veyfi/Wrapper.tsx
+++ b/apps/veyfi/Wrapper.tsx
@@ -4,6 +4,9 @@ import Meta from '@common/components/Meta';
import {useCurrentApp} from '@common/hooks/useCurrentApp';
import {variants} from '@common/utils/animations';
+import {GaugeContextApp} from './contexts/useGauge';
+import {OptionContextApp} from './contexts/useOption';
+
import type {NextRouter} from 'next/router';
import type {ReactElement} from 'react';
@@ -14,17 +17,21 @@ export default function Wrapper({children, router}: {children: ReactElement, rou
<>
-
-
- {children}
-
-
+
+
+
+
+ {children}
+
+
+
+
>
);
diff --git a/apps/veyfi/components/GaugesTab.tsx b/apps/veyfi/components/GaugesTab.tsx
new file mode 100644
index 000000000..ff02c2333
--- /dev/null
+++ b/apps/veyfi/components/GaugesTab.tsx
@@ -0,0 +1,228 @@
+import {useCallback, useState} from 'react';
+import {useGauge} from '@veYFI/contexts/useGauge';
+import * as GaugeActions from '@veYFI/utils/actions/gauge';
+import {validateNetwork} from '@veYFI/utils/validations';
+import {Button} from '@yearn-finance/web-lib/components/Button';
+import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
+import {allowanceKey, toAddress} from '@yearn-finance/web-lib/utils/address';
+import {formatBigNumberAsAmount, toBigInt, toNormalizedValue} from '@yearn-finance/web-lib/utils/format.bigNumber';
+import {formatAmount, formatPercent} from '@yearn-finance/web-lib/utils/format.number';
+import {isZero} from '@yearn-finance/web-lib/utils/isZero';
+import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction';
+import {ImageWithFallback} from '@common/components/ImageWithFallback';
+import {Table} from '@common/components/Table';
+import {useWallet} from '@common/contexts/useWallet';
+import {useYearn} from '@common/contexts/useYearn';
+
+import type {ReactElement} from 'react';
+import type {TAddress} from '@yearn-finance/web-lib/types';
+
+type TGaugeData = {
+ gaugeAddress: TAddress,
+ vaultAddress: TAddress,
+ decimals: number,
+ vaultIcon: string,
+ vaultName: string,
+ vaultApy: number,
+ vaultDeposited: bigint,
+ gaugeApy: number,
+ gaugeBoost: number,
+ gaugeStaked: bigint,
+ allowance: bigint,
+ isApproved: boolean,
+ actions: undefined
+}
+
+function GaugesTab(): ReactElement {
+ const [selectedGauge, set_selectedGauge] = useState('');
+ const [selectedAction, set_selectedAction] = useState<'stake' | 'unstake' | undefined>();
+ const {provider, address, isActive, chainID} = useWeb3();
+ const {gaugeAddresses, gaugesMap, positionsMap, allowancesMap, refresh: refreshGauges, isLoading: isLoadingGauges} = useGauge();
+ const {vaults} = useYearn();
+ const {balances, 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 gaugesData = 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 ?? '',
+ vaultApy: vault?.apy.net_apy ?? 0,
+ vaultDeposited: toBigInt(formatBigNumberAsAmount(balances[vaultAddress]?.raw)),
+ gaugeApy: 0, // TODO: gauge apy calcs
+ gaugeBoost: positionsMap[address]?.boost ?? 1,
+ gaugeStaked: toBigInt(formatBigNumberAsAmount(positionsMap[address]?.deposit.balance)),
+ allowance: toBigInt(formatBigNumberAsAmount(allowancesMap[allowanceKey(1, vaultAddress, address, userAddress)])),
+ isApproved: toBigInt(formatBigNumberAsAmount(allowancesMap[allowanceKey(1, vaultAddress, address, userAddress)])) >= toBigInt(formatBigNumberAsAmount(balances[vaultAddress]?.raw)),
+ actions: undefined
+ };
+ });
+
+ const {isValid: isValidNetwork} = validateNetwork({supportedNetwork: 1, walletNetwork: chainID});
+
+ const onApproveAndStake = useCallback(async (vaultAddress: TAddress, gaugeAddress: TAddress, amount: bigint, allowance: bigint): Promise => {
+ set_selectedGauge(gaugeAddress);
+ set_selectedAction('stake');
+ const response = await GaugeActions.approveAndStake({
+ connector: provider,
+ contractAddress: gaugeAddress,
+ vaultAddress,
+ amount,
+ allowance,
+ statusHandler: set_approveAndStakeStatus
+ });
+
+ if (response.isSuccessful) {
+ await refreshData();
+ }
+ }, [provider, refreshData]);
+
+ const onStake = useCallback(async (gaugeAddress: TAddress, amount: bigint): Promise => {
+ set_selectedGauge(gaugeAddress);
+ set_selectedAction('stake');
+
+ const response = await GaugeActions.stake({
+ connector: provider,
+ contractAddress: gaugeAddress,
+ amount,
+ statusHandler: set_stakeStatus
+ });
+
+ if (response.isSuccessful) {
+ await refreshData();
+ }
+ }, [provider, refreshData]);
+
+ const onUnstake = useCallback(async (gaugeAddress: TAddress, amount: bigint): Promise => {
+ set_selectedGauge(gaugeAddress);
+ set_selectedAction('unstake');
+
+ const response = await GaugeActions.unstake({
+ connector: provider,
+ contractAddress: gaugeAddress,
+ accountAddress: userAddress,
+ amount,
+ statusHandler: set_unstakeStatus
+ });
+
+ if (response.isSuccessful) {
+ await refreshData();
+ }
+ }, [provider, refreshData, userAddress]);
+
+ return (
+
+
(
+
+ )
+ },
+ {
+ key: 'vaultApy',
+ label: 'Vault APY',
+ sortable: true,
+ format: ({vaultApy}): string => formatPercent((vaultApy) * 100, 2, 2, 500)
+ },
+ {
+ key: 'vaultDeposited',
+ label: 'Deposited in Vault',
+ sortable: true,
+ format: ({vaultDeposited, decimals}): string => formatAmount(toNormalizedValue(vaultDeposited, decimals))
+ },
+ {
+ key: 'gaugeApy',
+ label: 'Gauge APY',
+ sortable: true
+ },
+ {
+ key: 'gaugeBoost',
+ label: 'Boost',
+ sortable: true,
+ format: ({gaugeBoost}): string => `${gaugeBoost.toFixed(2)}x`
+ },
+ {
+ key: 'gaugeStaked',
+ label: 'Staked in Gauge',
+ sortable: true,
+ format: ({gaugeStaked, decimals}): string => formatAmount(toNormalizedValue(gaugeStaked, decimals))
+ },
+ {
+ key: 'actions',
+ label: '',
+ columnSpan: 2,
+ fullWidth: true,
+ className: 'my-4 md:my-0',
+ transform: ({isApproved, vaultAddress, gaugeAddress, vaultDeposited, gaugeStaked, allowance}): ReactElement => (
+
+
+ {!isApproved && (
+
+ )}
+ {isApproved && (
+
+ )}
+
+ )
+ }
+ ]}
+ data={gaugesData}
+ columns={9}
+ initialSortBy={'gaugeApy'}
+ />
+
+ );
+}
+
+export {GaugesTab};
diff --git a/apps/veyfi/components/RedeemTab.tsx b/apps/veyfi/components/RedeemTab.tsx
new file mode 100644
index 000000000..fdacd990e
--- /dev/null
+++ b/apps/veyfi/components/RedeemTab.tsx
@@ -0,0 +1,142 @@
+import {useCallback, useState} from 'react';
+import {useAsync} from '@react-hookz/web';
+import {useOption} from '@veYFI/contexts/useOption';
+import {redeem} from '@veYFI/utils/actions/option';
+import {VEYFI_OPTIONS_ADDRESS, VEYFI_OYFI_ADDRESS} from '@veYFI/utils/constants';
+import {validateAllowance, validateAmount, validateNetwork} from '@veYFI/utils/validations';
+import {Button} from '@yearn-finance/web-lib/components/Button';
+import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
+import {useChainID} from '@yearn-finance/web-lib/hooks/useChainID';
+import {toAddress} from '@yearn-finance/web-lib/utils/address';
+import {BIG_ZERO, ETH_TOKEN_ADDRESS, YFI_ADDRESS} from '@yearn-finance/web-lib/utils/constants';
+import {formatBigNumberAsAmount, toNormalizedBN, toNormalizedValue} from '@yearn-finance/web-lib/utils/format.bigNumber';
+import {formatCounterValue} from '@yearn-finance/web-lib/utils/format.value';
+import {handleInputChangeEventValue} from '@yearn-finance/web-lib/utils/handlers/handleInputChangeEventValue';
+import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction';
+import {AmountInput} from '@common/components/AmountInput';
+import {useWallet} from '@common/contexts/useWallet';
+import {useBalance} from '@common/hooks/useBalance';
+import {useTokenPrice} from '@common/hooks/useTokenPrice';
+import {approveERC20} from '@common/utils/actions';
+
+import type {ReactElement} from 'react';
+import type {TAddress} from '@yearn-finance/web-lib/types';
+
+function RedeemTab(): ReactElement {
+ const [redeemAmount, set_redeemAmount] = useState(toNormalizedBN(0));
+ const {provider, address, isActive} = useWeb3();
+ const {safeChainID} = useChainID();
+ const {refresh: refreshBalances} = useWallet();
+ const {getRequiredEth, price: optionPrice, positions, allowances, isLoading: isLoadingOption, refresh} = useOption();
+ const clearLockAmount = (): void => set_redeemAmount(toNormalizedBN(0));
+ const refreshData = useCallback((): unknown => Promise.all([refresh(), refreshBalances()]), [refresh, refreshBalances]);
+ const onTxSuccess = useCallback((): unknown => Promise.all([refreshData(), clearLockAmount()]), [refreshData]);
+ const [{status, result}, fetchRequiredEth] = useAsync(getRequiredEth, BIG_ZERO);
+ const ethBalance = useBalance(ETH_TOKEN_ADDRESS);
+ const yfiPrice = useTokenPrice(YFI_ADDRESS);
+ const [approveRedeemStatus, set_approveRedeemStatus] = useState(defaultTxStatus);
+ const [redeemStatus, set_redeemStatus] = useState(defaultTxStatus);
+
+ const userAddress = address as TAddress;
+ const oYFIBalance = toNormalizedBN(formatBigNumberAsAmount(positions?.balance), 18);
+ const ethRequired = toNormalizedValue(result, 18);
+
+ const handleApproveRedeem = useCallback(async (): Promise => {
+ const response = await approveERC20({
+ connector: provider,
+ contractAddress: VEYFI_OYFI_ADDRESS,
+ spenderAddress: VEYFI_OPTIONS_ADDRESS,
+ statusHandler: set_approveRedeemStatus,
+ amount: redeemAmount.raw
+ });
+
+ if (response.isSuccessful) {
+ await refreshData();
+ }
+ }, [provider, redeemAmount.raw, refreshData]);
+
+ const handleRedeem = useCallback(async (): Promise => {
+ const response = await redeem({
+ connector: provider,
+ contractAddress: VEYFI_OPTIONS_ADDRESS,
+ statusHandler: set_redeemStatus,
+ accountAddress: toAddress(address),
+ amount: redeemAmount.raw,
+ ethRequired: result
+
+ });
+
+ if (response.isSuccessful) {
+ await onTxSuccess();
+ }
+ }, [address, onTxSuccess, provider, redeemAmount.raw, result]);
+
+
+ const {isValid: isApproved} = validateAllowance({
+ tokenAddress: VEYFI_OYFI_ADDRESS,
+ spenderAddress: VEYFI_OPTIONS_ADDRESS,
+ allowances,
+ amount: redeemAmount.raw,
+ ownerAddress: userAddress,
+ chainID: 1
+ });
+
+ const {isValid: isValidRedeemAmount, error: redeemAmountError} = validateAmount({
+ amount: redeemAmount.normalized,
+ balance: oYFIBalance.normalized
+ });
+
+ const {isValid: isValidNetwork} = validateNetwork({supportedNetwork: 1, walletNetwork: safeChainID});
+
+ return (
+
+
+
+
+ {'Redeem'}
+
+
+
+
+
+
{
+ const amount = handleInputChangeEventValue(value, 18);
+ set_redeemAmount(amount);
+ fetchRequiredEth.execute(amount.raw);
+ }}
+ onLegendClick={(): void => set_redeemAmount(oYFIBalance)}
+ onMaxClick={(): void => set_redeemAmount(oYFIBalance)}
+ legend={formatCounterValue(redeemAmount.normalized, yfiPrice)}
+ error={redeemAmountError}
+ />
+
+
+
+
+
+ );
+}
+
+export {RedeemTab};
diff --git a/apps/veyfi/components/RewardsTab.tsx b/apps/veyfi/components/RewardsTab.tsx
new file mode 100644
index 000000000..dd8e42022
--- /dev/null
+++ b/apps/veyfi/components/RewardsTab.tsx
@@ -0,0 +1,140 @@
+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';
+import {VEYFI_CLAIM_REWARDS_ZAP_ADDRESS} from '@veYFI/utils/constants';
+import {validateNetwork} 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 {BIG_ZERO} from '@yearn-finance/web-lib/utils/constants';
+import {formatBigNumberAsAmount, toBigInt, toNormalizedAmount} from '@yearn-finance/web-lib/utils/format.bigNumber';
+import {formatCounterValue} from '@yearn-finance/web-lib/utils/format.value';
+import {isZero} from '@yearn-finance/web-lib/utils/isZero';
+import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction';
+import {AmountInput} from '@common/components/AmountInput';
+import {Dropdown} from '@common/components/Dropdown';
+import {useYearn} from '@common/contexts/useYearn';
+
+import type {ReactElement} from 'react';
+import type {TDropdownOption} from '@common/components/Dropdown';
+
+function RewardsTab(): ReactElement {
+ const [selectedGauge, set_selectedGauge] = useState();
+ const {provider, isActive, chainID} = useWeb3();
+ const {gaugeAddresses, gaugesMap, positionsMap, refresh: refreshGauges} = useGauge();
+ const {price: optionPrice} = useOption();
+ const {vaults} = useYearn();
+ const refreshData = useCallback((): unknown => Promise.all([refreshGauges()]), [refreshGauges]);
+ const [claimStatus, set_claimStatus] = useState(defaultTxStatus);
+ const [claimAllStatus, set_claimAllStatus] = useState(defaultTxStatus);
+ const selectedGaugeAddress = toAddress(selectedGauge?.id);
+ const selectedGaugeRewards = toBigInt(formatBigNumberAsAmount(positionsMap[selectedGaugeAddress]?.reward.balance));
+
+ const onClaim = useCallback(async (): Promise => {
+ const result = await GaugeActions.claimRewards({
+ connector: provider,
+ contractAddress: selectedGaugeAddress,
+ statusHandler: set_claimStatus
+ });
+ if (result.isSuccessful) {
+ refreshData();
+ }
+ }, [provider, refreshData, selectedGaugeAddress]);
+
+ const onClaimAll = useCallback(async (): Promise => {
+ const result = await GaugeActions.claimAllRewards({
+ connector: provider,
+ contractAddress: VEYFI_CLAIM_REWARDS_ZAP_ADDRESS,
+ gaugeAddresses,
+ willLockRewards: false,
+ statusHandler: set_claimAllStatus
+ });
+ if (result.isSuccessful) {
+ refreshData();
+ }
+ }, [gaugeAddresses, provider, refreshData]);
+
+ const gaugeOptions = gaugeAddresses.filter((address): boolean => toBigInt(positionsMap[address]?.reward.balance) > 0n ?? false)
+ .map((address): TDropdownOption => {
+ const gauge = gaugesMap[address];
+ const vaultAddress = toAddress(gauge?.vaultAddress);
+ const vault = vaults[vaultAddress];
+
+ return {
+ id: address,
+ label: vault?.display_name ?? '',
+ icon: `${process.env.BASE_YEARN_ASSETS_URI}/1/${vaultAddress}/logo-128.png`
+ };
+ });
+
+ const gaugesRewards = useMemo((): bigint => {
+ return gaugeAddresses.reduce((acc, address): bigint => {
+ return acc + toBigInt(formatBigNumberAsAmount(positionsMap[address]?.reward.balance));
+ }, BIG_ZERO);
+ }, [gaugeAddresses, positionsMap]);
+
+ const {isValid: isValidNetwork} = validateNetwork({supportedNetwork: 1, walletNetwork: chainID});
+
+ return (
+
+
+
+
+ {'Claim Rewards'}
+
+
+
+
+
+
+
+
+
+ {'Claim Separately'}
+
+
+
+
+
+
+ );
+}
+
+export {RewardsTab};
diff --git a/apps/veyfi/components/VoteTab.tsx b/apps/veyfi/components/VoteTab.tsx
new file mode 100644
index 000000000..900419ca1
--- /dev/null
+++ b/apps/veyfi/components/VoteTab.tsx
@@ -0,0 +1,87 @@
+import {useCallback, useState} from 'react';
+import Link from 'next/link';
+import {delegateVote} from '@veYFI/utils/actions/votingEscrow';
+import {SNAPSHOT_DELEGATE_REGISTRY_ADDRESS} from '@veYFI/utils/constants';
+import {validateAddress, validateNetwork} 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 {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction';
+import {Input} from '@common/components/Input';
+
+import type {ReactElement} from 'react';
+import type {TAddress} from '@yearn-finance/web-lib/types';
+
+function VoteTab(): ReactElement {
+ const [delegateAddress, set_delegateAddress] = useState('');
+ const {provider, address, isActive, chainID} = useWeb3();
+
+ const [delegateVoteStatus, set_delegateVoteStatus] = useState(defaultTxStatus);
+
+ const userAddress = address as TAddress;
+
+ const {isValid: isValidNetwork} = validateNetwork({supportedNetwork: 1, walletNetwork: chainID});
+ const {isValid: isValidDelegateAddress, error: delegateAddressError} = validateAddress({address: delegateAddress});
+
+ const handleExecuteDelegateVote = useCallback(async (): Promise => {
+ if (!userAddress || !isValidDelegateAddress) {
+ return;
+ }
+
+ await delegateVote({
+ connector: provider,
+ contractAddress: SNAPSHOT_DELEGATE_REGISTRY_ADDRESS,
+ statusHandler: set_delegateVoteStatus,
+ delegateAddress: toAddress(delegateAddress)
+ });
+ }, [delegateAddress, isValidDelegateAddress, provider, userAddress]);
+
+ return (
+
+
+
+
+ {'Vote for Gauge'}
+
+
+
{'Vote to direct future YFI rewards to a particular gauge.'}
+
+
{'If you prefer your democracy on the representative side, you can delegate your vote to another address.'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export {VoteTab};
diff --git a/apps/veyfi/contexts/useGauge.tsx b/apps/veyfi/contexts/useGauge.tsx
new file mode 100644
index 000000000..a3cc2453d
--- /dev/null
+++ b/apps/veyfi/contexts/useGauge.tsx
@@ -0,0 +1,247 @@
+import React, {createContext, memo, useCallback, useContext, useEffect, useMemo} from 'react';
+import {FixedNumber} from 'ethers';
+import {useContractRead} from 'wagmi';
+import {useAsync} from '@react-hookz/web';
+import {keyBy} from '@veYFI/utils';
+import VEYFI_GAUGE_ABI from '@veYFI/utils/abi/veYFIGauge.abi';
+import VEYFI_REGISTRY_ABI from '@veYFI/utils/abi/veYFIRegistry.abi';
+import {VEYFI_REGISTRY_ADDRESS} from '@veYFI/utils/constants';
+import {erc20ABI, getContract, multicall} from '@wagmi/core';
+import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
+import {allowanceKey} from '@yearn-finance/web-lib/utils/address';
+
+import type {ReactElement} from 'react';
+import type {TAddress, TDict} from '@yearn-finance/web-lib/types';
+import type {TMulticallContract} from '@common/types/types';
+
+export type TGauge = {
+ address: TAddress,
+ vaultAddress: TAddress,
+ name: string,
+ symbol: string,
+ decimals: number,
+ totalStaked: bigint,
+ // apy?: number;
+}
+
+export type TPosition = {
+ balance: bigint,
+ underlyingBalance: bigint,
+}
+
+export type TGaugePosition = {
+ address: TAddress,
+ deposit: TPosition,
+ reward: TPosition,
+ boost: number,
+}
+
+export type TGaugeContext = {
+ gaugeAddresses: TAddress[],
+ gaugesMap: TDict,
+ positionsMap: TDict,
+ allowancesMap: TDict,
+ isLoading: boolean,
+ refresh: () => void,
+}
+const defaultProps: TGaugeContext = {
+ gaugeAddresses: [],
+ gaugesMap: {},
+ positionsMap: {},
+ allowancesMap: {},
+ isLoading: true,
+ refresh: (): void => undefined
+};
+
+const GaugeContext = createContext(defaultProps);
+export const GaugeContextApp = memo(function GaugeContextApp({children}: {children: ReactElement}): ReactElement {
+ const {address: userAddress, isActive} = useWeb3();
+ const veYFIRegistryContract = useMemo((): {address: TAddress, abi: typeof VEYFI_REGISTRY_ABI} => ({
+ address: VEYFI_REGISTRY_ADDRESS,
+ abi: VEYFI_REGISTRY_ABI
+ }), []);
+ const {data: vaultAddresses} = useContractRead({
+ ...veYFIRegistryContract,
+ functionName: 'getVaults',
+ chainId: 1
+ });
+
+ const gaugesFetcher = useCallback(async (): Promise => {
+ if (!isActive || !userAddress) {
+ return [];
+ }
+
+ const gaugeAddressCalls = vaultAddresses?.map((vaultAddress): TMulticallContract => ({
+ ...veYFIRegistryContract,
+ functionName: 'gauges',
+ args: [vaultAddress]
+ }));
+
+ const gaugeAddressesResults = await multicall({contracts: gaugeAddressCalls ?? [], chainId: 1});
+
+ const gaugeAddresses = gaugeAddressesResults.map(({result}): unknown => result) as TAddress[];
+ const gaugePromises = gaugeAddresses.map(async (address): Promise => {
+ // todo: update once abi is available
+ const veYFIGaugeContract = getContract({
+ address,
+ abi: VEYFI_GAUGE_ABI,
+ chainId: 1
+ });
+
+ // TODO: These should be migrated to wagmi
+ const calls: TMulticallContract[] = [];
+ ['asset', 'name', 'symbol', 'decimals', 'totalAssets'].forEach((functionName): void => {
+ calls.push({...veYFIGaugeContract, functionName});
+ });
+
+ const results = await multicall({
+ contracts: calls,
+ chainId: 1
+ });
+
+ const [asset, name, symbol, decimals, totalAssets] = results.map(({result}): unknown => result) as [TAddress, string, string, number, bigint];
+
+ return ({
+ address,
+ vaultAddress: asset,
+ name,
+ symbol,
+ decimals,
+ totalStaked: totalAssets
+ });
+ });
+ return Promise.all(gaugePromises);
+ }, []);
+
+ const [{result: allowancesMap, status: fetchAllowancesMapStatus}, {execute: refreshAllowances}] = useAsync(async (): Promise | undefined> => {
+ if (!gauges || !isActive) {
+ return;
+ }
+ return allowancesFetcher();
+ }, {});
+
+ const [{result: positions, status: fetchPositionsStatus}, {execute: refreshPositions}] = useAsync(async (): Promise => {
+ if (!gauges || !isActive) {
+ return;
+ }
+ return positionsFetcher();
+ }, []);
+
+ const [{result: gauges, status: fetchGaugesStatus}, {execute: refreshVotingEscrow}] = useAsync(async (): Promise => {
+ if (!isActive) {
+ return;
+ }
+ return gaugesFetcher();
+ }, []);
+
+ const refresh = useCallback((): void => {
+ refreshVotingEscrow();
+ refreshPositions();
+ refreshAllowances();
+ }, [refreshAllowances, refreshPositions, refreshVotingEscrow]);
+
+ useEffect((): void => {
+ refresh();
+ }, [refresh]);
+
+ const positionsFetcher = useCallback(async (): Promise => {
+ if (!gauges|| !isActive|| !userAddress) {
+ return [];
+ }
+
+ const positionPromises = gauges.map(async ({address}): Promise => {
+ // todo: update once abi is available
+ const veYFIGaugeContract = getContract({
+ address,
+ abi: VEYFI_GAUGE_ABI,
+ chainId: 1
+ });
+
+ const calls: TMulticallContract[] = [];
+ ['balanceOf', 'earned', 'nextBoostedBalanceOf'].forEach((functionName): void => {
+ calls.push({...veYFIGaugeContract, functionName, args: [userAddress]});
+ });
+
+ const results = await multicall({
+ contracts: calls,
+ chainId: 1
+ });
+
+ const [balance, earned, boostedBalance] = results.map(({result}): unknown => result) as bigint[];
+
+ const depositPosition: TPosition = {
+ balance,
+ underlyingBalance: balance
+ };
+
+ const rewardPosition: TPosition = {
+ balance: earned,
+ underlyingBalance: earned // TODO: convert to underlying
+ };
+
+ const boostRatio = balance > 0n
+ ? FixedNumber.from(boostedBalance).divUnsafe(FixedNumber.from(balance)).toUnsafeFloat()
+ : 0.1;
+ const boost = Math.min(1, boostRatio) * 10;
+
+ return {
+ address,
+ deposit: depositPosition,
+ reward: rewardPosition,
+ boost
+ };
+ });
+ return Promise.all(positionPromises);
+ }, [gauges, isActive, userAddress]);
+
+ const allowancesFetcher = useCallback(async (): Promise> => {
+ if (!gauges || !isActive || !userAddress) {
+ return {};
+ }
+
+ const allowanceCalls = gauges.map(({address, vaultAddress}): TMulticallContract => {
+ const erc20Contract = getContract({
+ address: vaultAddress,
+ abi: erc20ABI,
+ chainId: 1
+ });
+ return {
+ ...erc20Contract,
+ abi: erc20ABI,
+ functionName: 'allowance',
+ args: [userAddress, address]
+ };
+ });
+
+ const results = await multicall({
+ contracts: allowanceCalls,
+ chainId: 1
+ });
+ const allowances = results.map(({result}): unknown => result) as bigint[];
+
+ const allowancesMap: TDict = {};
+ gauges.forEach(({address, vaultAddress}, index): void => {
+ allowancesMap[allowanceKey(1, vaultAddress, address, userAddress)] = allowances[index];
+ });
+
+ return allowancesMap;
+ }, [gauges, isActive, userAddress]);
+
+ const contextValue = useMemo((): TGaugeContext => ({
+ gaugeAddresses: gauges?.map(({address}): TAddress => address) ?? [],
+ gaugesMap: keyBy(gauges ?? [], 'address'),
+ positionsMap: keyBy(positions ?? [], 'address'),
+ allowancesMap: allowancesMap ?? {},
+ isLoading: fetchGaugesStatus ==='loading' || fetchPositionsStatus === 'loading' || fetchAllowancesMapStatus === 'loading',
+ refresh
+ }), [allowancesMap, fetchAllowancesMapStatus, fetchGaugesStatus, fetchPositionsStatus, gauges, positions, refresh]);
+
+ return (
+
+ {children}
+
+ );
+});
+
+export const useGauge = (): TGaugeContext => useContext(GaugeContext);
+export default useGauge;
diff --git a/apps/veyfi/contexts/useOption.tsx b/apps/veyfi/contexts/useOption.tsx
new file mode 100644
index 000000000..ca037ff2e
--- /dev/null
+++ b/apps/veyfi/contexts/useOption.tsx
@@ -0,0 +1,150 @@
+import React, {createContext, memo, useCallback, useContext, useEffect, useMemo} from 'react';
+import {ethers} from 'ethers';
+import {useAsync} from '@react-hookz/web';
+import VEYFI_OPTIONS_ABI from '@veYFI/utils/abi/veYFIOptions.abi';
+import VEYFI_OYFI_ABI from '@veYFI/utils/abi/veYFIoYFI.abi';
+import {VEYFI_OPTIONS_ADDRESS, VEYFI_OYFI_ADDRESS} from '@veYFI/utils/constants';
+import {erc20ABI, readContract} from '@wagmi/core';
+import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3';
+import {allowanceKey} from '@yearn-finance/web-lib/utils/address';
+import {BIG_ZERO, ETH_TOKEN_ADDRESS, YFI_ADDRESS} from '@yearn-finance/web-lib/utils/constants';
+import {toBigInt, toNormalizedValue} from '@yearn-finance/web-lib/utils/format.bigNumber';
+import {useTokenPrice} from '@common/hooks/useTokenPrice';
+
+import type {ReactElement} from 'react';
+import type {TDict} from '@yearn-finance/web-lib/types';
+
+export type TOptionPosition = {
+ balance: bigint,
+}
+
+export type TOptionContext = {
+ getRequiredEth: (amount: bigint) => Promise,
+ price: number | undefined,
+ positions: TOptionPosition | undefined,
+ allowances: TDict,
+ isLoading: boolean,
+ refresh: () => void,
+}
+
+const defaultProps: TOptionContext = {
+ getRequiredEth: async (): Promise => BIG_ZERO,
+ price: undefined,
+ positions: undefined,
+ allowances: {},
+ isLoading: true,
+ refresh: (): void => undefined
+};
+
+const OptionContext = createContext(defaultProps);
+export const OptionContextApp = memo(function OptionContextApp({children}: {children: ReactElement}): ReactElement {
+ const {provider, address: userAddress, isActive} = useWeb3();
+ const yfiPrice = useTokenPrice(YFI_ADDRESS);
+ const ethPrice = useTokenPrice(ETH_TOKEN_ADDRESS);
+
+ const [{result: price, status: fetchPriceStatus}, {execute: refreshPrice}] = useAsync(async (): Promise => {
+ if (!isActive || provider) {
+ return;
+ }
+ return priceFetcher();
+ }, 0);
+
+ const [{result: positions, status: fetchPositionsStatus}, {execute: refreshPositions}] = useAsync(async (): Promise => {
+ if (!isActive || provider) {
+ return;
+ }
+ return positionsFetcher();
+ }, {balance: 0n});
+
+ const [{result: allowances, status: fetchAllowancesStatus}, {execute: refreshAllowances}] = useAsync(async (): Promise | undefined> => {
+ if (!isActive || provider) {
+ return;
+ }
+ return allowancesFetcher();
+ }, {});
+
+ const refresh = useCallback((): void => {
+ refreshPrice();
+ refreshPositions();
+ refreshAllowances();
+ }, [refreshPrice, refreshPositions, refreshAllowances]);
+
+ useEffect((): void => {
+ refresh();
+ }, [refresh]);
+
+ const getRequiredEth = useCallback(async (amount: bigint): Promise => {
+ // TODO: update once abi is available
+ return readContract({
+ address: VEYFI_OPTIONS_ADDRESS,
+ abi: VEYFI_OPTIONS_ABI,
+ functionName: 'eth_required',
+ args: [amount],
+ chainId: 1
+ });
+ }, []);
+
+ const priceFetcher = useCallback(async (): Promise => {
+ if(!ethPrice || !yfiPrice) {
+ return undefined;
+ }
+ const oneOption = ethers.utils.parseEther('1');
+ const requiredEthPerOption = await getRequiredEth(toBigInt(oneOption.toString()));
+ const requiredEthValuePerOption = toNormalizedValue(requiredEthPerOption, 18) * ethPrice;
+ const pricePerOption = yfiPrice - requiredEthValuePerOption;
+ return pricePerOption;
+ }, [ethPrice, yfiPrice, getRequiredEth]);
+
+ const positionsFetcher = useCallback(async (): Promise => {
+ if (!isActive || !userAddress) {
+ return undefined;
+ }
+
+ // TODO: update once abi is available
+ return {
+ balance: await readContract({
+ address: VEYFI_OYFI_ADDRESS,
+ abi: VEYFI_OYFI_ABI,
+ functionName: 'balanceOf',
+ args: [userAddress],
+ chainId: 1
+ })
+ };
+ }, [isActive, userAddress]);
+
+ const allowancesFetcher = useCallback(async (): Promise> => {
+ if (!isActive || !userAddress) {
+ return {};
+ }
+
+ const oYFIAllowanceOptions = await readContract({
+ address: VEYFI_OYFI_ADDRESS,
+ abi: erc20ABI,
+ functionName: 'allowance',
+ args: [userAddress, VEYFI_OPTIONS_ADDRESS],
+ chainId: 1
+ });
+
+ return ({
+ [allowanceKey(1, VEYFI_OYFI_ADDRESS, VEYFI_OPTIONS_ADDRESS, userAddress)]: oYFIAllowanceOptions
+ });
+ }, [isActive, userAddress]);
+
+ const contextValue = useMemo((): TOptionContext => ({
+ getRequiredEth,
+ price,
+ positions,
+ allowances: allowances ?? {},
+ isLoading: fetchPriceStatus === 'loading' || fetchPositionsStatus === 'loading' || fetchAllowancesStatus === 'loading',
+ refresh
+ }), [allowances, fetchAllowancesStatus, fetchPositionsStatus, fetchPriceStatus, getRequiredEth, positions, price, refresh]);
+
+ return (
+
+ {children}
+
+ );
+});
+
+export const useOption = (): TOptionContext => useContext(OptionContext);
+export default useOption;
diff --git a/apps/veyfi/utils/abi/SnapshotDelegateRegistry.abi.ts b/apps/veyfi/utils/abi/SnapshotDelegateRegistry.abi.ts
new file mode 100644
index 000000000..8fc5a6324
--- /dev/null
+++ b/apps/veyfi/utils/abi/SnapshotDelegateRegistry.abi.ts
@@ -0,0 +1,3 @@
+const SNAPSHOT_DELEGATE_REGISTRY_ABI = [{'anonymous':false, 'inputs':[{'indexed':true, 'internalType':'address', 'name':'delegator', 'type':'address'}, {'indexed':true, 'internalType':'bytes32', 'name':'id', 'type':'bytes32'}, {'indexed':true, 'internalType':'address', 'name':'delegate', 'type':'address'}], 'name':'ClearDelegate', 'type':'event'}, {'anonymous':false, 'inputs':[{'indexed':true, 'internalType':'address', 'name':'delegator', 'type':'address'}, {'indexed':true, 'internalType':'bytes32', 'name':'id', 'type':'bytes32'}, {'indexed':true, 'internalType':'address', 'name':'delegate', 'type':'address'}], 'name':'SetDelegate', 'type':'event'}, {'inputs':[{'internalType':'bytes32', 'name':'id', 'type':'bytes32'}], 'name':'clearDelegate', 'outputs':[], 'stateMutability':'nonpayable', 'type':'function'}, {'inputs':[{'internalType':'address', 'name':'', 'type':'address'}, {'internalType':'bytes32', 'name':'', 'type':'bytes32'}], 'name':'delegation', 'outputs':[{'internalType':'address', 'name':'', 'type':'address'}], 'stateMutability':'view', 'type':'function'}, {'inputs':[{'internalType':'bytes32', 'name':'id', 'type':'bytes32'}, {'internalType':'address', 'name':'delegate', 'type':'address'}], 'name':'setDelegate', 'outputs':[], 'stateMutability':'nonpayable', 'type':'function'}] as const;
+
+export default SNAPSHOT_DELEGATE_REGISTRY_ABI;
diff --git a/apps/veyfi/utils/abi/veYFIClaimRewardsZap.abi.ts b/apps/veyfi/utils/abi/veYFIClaimRewardsZap.abi.ts
new file mode 100644
index 000000000..ff8001a7a
--- /dev/null
+++ b/apps/veyfi/utils/abi/veYFIClaimRewardsZap.abi.ts
@@ -0,0 +1,4 @@
+// TODO: update once final version deployed
+const VEYFI_CLAIM_REWARDS_ZAP_ABI = ['function claim(address[] calldata _gauges, bool _lock, bool _claimVeYfi) external'];
+
+export default VEYFI_CLAIM_REWARDS_ZAP_ABI;
diff --git a/apps/veyfi/utils/abi/veYFIGauge.abi.ts b/apps/veyfi/utils/abi/veYFIGauge.abi.ts
new file mode 100644
index 000000000..4734f1223
--- /dev/null
+++ b/apps/veyfi/utils/abi/veYFIGauge.abi.ts
@@ -0,0 +1,1413 @@
+// TODO: update once final version deployed
+const VEYFI_GAUGE_ABI = [
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_veYfi',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_oYfi',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_veYfiOYfiPool',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'constructor'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'owner',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'value',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'Approval',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': false,
+ 'internalType': 'address',
+ 'name': 'account',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'BoostedBalanceUpdated',
+ 'type': 'event'
+ },
+ {
+ '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'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'duration',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'rewardRate',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'periodFinish',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'DurationUpdated',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'asset',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'Initialize',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': false,
+ 'internalType': 'uint8',
+ 'name': 'version',
+ 'type': 'uint8'
+ }
+ ],
+ 'name': 'Initialized',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'previousOwner',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'newOwner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'OwnershipTransferred',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'account',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'recipient',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'RecipientUpdated',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'user',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'reward',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'RewardPaid',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'currentRewards',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'lastUpdateTime',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'periodFinish',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'rewardRate',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'historicalRewards',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'RewardsAdded',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'from',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'RewardsQueued',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'token',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'Sweep',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'from',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'to',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'value',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'Transfer',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'account',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'transfered',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'TransferredPenalty',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'account',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'rewardPerTokenStored',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'lastUpdateTime',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'rewards',
+ 'type': 'uint256'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'userRewardPerTokenPaid',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'UpdatedRewards',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'caller',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'receiver',
+ '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': 'Withdraw',
+ 'type': 'event'
+ },
+ {
+ 'inputs': [],
+ 'name': 'BOOSTING_FACTOR',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'BOOST_DENOMINATOR',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'PRECISION_FACTOR',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'REWARD_TOKEN',
+ 'outputs': [
+ {
+ 'internalType': 'contract IERC20',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'VEYFI',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'VE_YFI_POOL',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'owner',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'allowance',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'approve',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'asset',
+ 'outputs': [
+ {
+ 'internalType': 'contract IERC20',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'account',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'balanceOf',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_account',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'boostedBalanceOf',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_shares',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'convertToAssets',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_assets',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'convertToShares',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'currentRewards',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'decimals',
+ 'outputs': [
+ {
+ 'internalType': 'uint8',
+ 'name': '',
+ 'type': 'uint8'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'subtractedValue',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'decreaseAllowance',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_assets',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'deposit',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'duration',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_account',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'earned',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'getReward',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'historicalRewards',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'addedValue',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'increaseAllowance',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_asset',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'initialize',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address[]',
+ 'name': '_accounts',
+ 'type': 'address[]'
+ }
+ ],
+ 'name': 'kick',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'lastTimeRewardApplicable',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'lastUpdateTime',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'maxDeposit',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'maxMint',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'maxRedeem',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'maxWithdraw',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_shares',
+ 'type': 'uint256'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_receiver',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'mint',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'name',
+ 'outputs': [
+ {
+ 'internalType': 'string',
+ 'name': '',
+ 'type': 'string'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_account',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'nextBoostedBalanceOf',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'owner',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'periodFinish',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_assets',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'previewDeposit',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_shares',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'previewMint',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_assets',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'previewRedeem',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_assets',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'previewWithdraw',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'queueNewRewards',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'queuedRewards',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'recipients',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_assets',
+ 'type': 'uint256'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_receiver',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'redeem',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'renounceOwnership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'rewardPerToken',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'rewardPerTokenStored',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'rewardRate',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'rewards',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_newDuration',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'setDuration',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_recipient',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'setRecipient',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_token',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'sweep',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'symbol',
+ 'outputs': [
+ {
+ 'internalType': 'string',
+ 'name': '',
+ 'type': 'string'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'totalAssets',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'totalSupply',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'to',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'transfer',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'from',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': 'to',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'transferFrom',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'newOwner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'transferOwnership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'userRewardPerTokenPaid',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_assets',
+ 'type': 'uint256'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_receiver',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_owner',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'bool',
+ 'name': '_claim',
+ 'type': 'bool'
+ }
+ ],
+ 'name': 'withdraw',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ }
+] as const;
+
+export default VEYFI_GAUGE_ABI;
diff --git a/apps/veyfi/utils/abi/veYFIOptions.abi.ts b/apps/veyfi/utils/abi/veYFIOptions.abi.ts
new file mode 100644
index 000000000..9e547e1b0
--- /dev/null
+++ b/apps/veyfi/utils/abi/veYFIOptions.abi.ts
@@ -0,0 +1,262 @@
+const VEYFI_OPTIONS_ABI = [
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': false,
+ 'name': 'yfi_recovered',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'Killed',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': false,
+ 'name': 'token',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'Sweep',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'name': 'previous_owner',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'name': 'new_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'OwnershipTransferStarted',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'name': 'previous_owner',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'name': 'new_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'OwnershipTransferred',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'name': 'payee',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'SetPayee',
+ 'type': 'event'
+ },
+ {
+ 'inputs': [
+ {
+ 'name': 'yfi',
+ 'type': 'address'
+ },
+ {
+ 'name': 'o_yfi',
+ 'type': 'address'
+ },
+ {
+ 'name': 've_yfi',
+ 'type': 'address'
+ },
+ {
+ 'name': 'owner',
+ 'type': 'address'
+ },
+ {
+ 'name': 'price_feed',
+ 'type': 'address'
+ },
+ {
+ 'name': 'curve_pool',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'constructor'
+ },
+ {
+ 'inputs': [
+ {
+ 'name': 'amount',
+ 'type': 'uint256'
+ },
+ {
+ 'name': 'recipient',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'exercise',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'payable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'eth_required',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'get_latest_price',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'name': 'new_payee',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'set_payee',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'kill',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'name': 'token',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'sweep',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'name': 'new_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'transfer_ownership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'accept_ownership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'owner',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'pending_owner',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'killed',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'payee',
+ 'outputs': [
+ {
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ }
+] as const;
+
+export default VEYFI_OPTIONS_ABI;
diff --git a/apps/veyfi/utils/abi/veYFIRegistry.abi.ts b/apps/veyfi/utils/abi/veYFIRegistry.abi.ts
new file mode 100644
index 000000000..d51490f31
--- /dev/null
+++ b/apps/veyfi/utils/abi/veYFIRegistry.abi.ts
@@ -0,0 +1,294 @@
+// TODO: update once final version deployed
+const VEYFI_REGISTRY_ABI = [
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_ve',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_yfi',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_gaugefactory',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_veYfiRewardPool',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'constructor'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'previousOwner',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'newOwner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'OwnershipTransferred',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 've',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'UpdatedVeToken',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'vault',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'VaultAdded',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'vault',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'VaultRemoved',
+ 'type': 'event'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_vault',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': '_owner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'addVaultToRewards',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'gaugefactory',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'gauges',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'getVaults',
+ 'outputs': [
+ {
+ 'internalType': 'address[]',
+ 'name': '',
+ 'type': 'address[]'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'isGauge',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'owner',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_vault',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'removeVaultFromRewards',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'renounceOwnership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_veToken',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'setVe',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'newOwner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'transferOwnership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'vaultForGauge',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'veToken',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'veYfiRewardPool',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'yfi',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ }
+] as const;
+
+export default VEYFI_REGISTRY_ABI;
diff --git a/apps/veyfi/utils/abi/veYFIoYFI.abi.ts b/apps/veyfi/utils/abi/veYFIoYFI.abi.ts
new file mode 100644
index 000000000..889de9c26
--- /dev/null
+++ b/apps/veyfi/utils/abi/veYFIoYFI.abi.ts
@@ -0,0 +1,380 @@
+const VEYFI_OYFI_ABI = [
+ {
+ 'inputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'constructor'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'owner',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'value',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'Approval',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'previousOwner',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'newOwner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'OwnershipTransferred',
+ 'type': 'event'
+ },
+ {
+ 'anonymous': false,
+ 'inputs': [
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'from',
+ 'type': 'address'
+ },
+ {
+ 'indexed': true,
+ 'internalType': 'address',
+ 'name': 'to',
+ 'type': 'address'
+ },
+ {
+ 'indexed': false,
+ 'internalType': 'uint256',
+ 'name': 'value',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'Transfer',
+ 'type': 'event'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'owner',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'allowance',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'approve',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'account',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'balanceOf',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '_amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'burn',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_owner',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': '_amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'burn',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'decimals',
+ 'outputs': [
+ {
+ 'internalType': 'uint8',
+ 'name': '',
+ 'type': 'uint8'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'subtractedValue',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'decreaseAllowance',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'spender',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'addedValue',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'increaseAllowance',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': '_to',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': '_amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'mint',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'name',
+ 'outputs': [
+ {
+ 'internalType': 'string',
+ 'name': '',
+ 'type': 'string'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'owner',
+ 'outputs': [
+ {
+ 'internalType': 'address',
+ 'name': '',
+ 'type': 'address'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'renounceOwnership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'symbol',
+ 'outputs': [
+ {
+ 'internalType': 'string',
+ 'name': '',
+ 'type': 'string'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [],
+ 'name': 'totalSupply',
+ 'outputs': [
+ {
+ 'internalType': 'uint256',
+ 'name': '',
+ 'type': 'uint256'
+ }
+ ],
+ 'stateMutability': 'view',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'to',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'transfer',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'from',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'address',
+ 'name': 'to',
+ 'type': 'address'
+ },
+ {
+ 'internalType': 'uint256',
+ 'name': 'amount',
+ 'type': 'uint256'
+ }
+ ],
+ 'name': 'transferFrom',
+ 'outputs': [
+ {
+ 'internalType': 'bool',
+ 'name': '',
+ 'type': 'bool'
+ }
+ ],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ },
+ {
+ 'inputs': [
+ {
+ 'internalType': 'address',
+ 'name': 'newOwner',
+ 'type': 'address'
+ }
+ ],
+ 'name': 'transferOwnership',
+ 'outputs': [],
+ 'stateMutability': 'nonpayable',
+ 'type': 'function'
+ }
+] as const;
+
+export default VEYFI_OYFI_ABI;
diff --git a/apps/veyfi/utils/actions/gauge.ts b/apps/veyfi/utils/actions/gauge.ts
new file mode 100644
index 000000000..354275cbc
--- /dev/null
+++ b/apps/veyfi/utils/actions/gauge.ts
@@ -0,0 +1,98 @@
+import {assert} from '@common/utils/assert';
+import {assertAddress, assertAddresses, handleTx as handleTxWagmi} from '@common/utils/wagmiUtils';
+
+import VEYFI_CLAIM_REWARDS_ZAP_ABI from '../abi/veYFIClaimRewardsZap.abi';
+import VEYFI_GAUGE_ABI from '../abi/veYFIGauge.abi';
+
+import type {TAddress} from '@yearn-finance/web-lib/types';
+import type {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction';
+import type {TWriteTransaction} from '@common/utils/wagmiUtils';
+
+type TApproveAndStake = TWriteTransaction & {
+ vaultAddress: TAddress;
+ allowance: bigint;
+ amount: bigint;
+};
+export async function approveAndStake(props: TApproveAndStake): Promise {
+ assertAddress(props.contractAddress);
+ assertAddress(props.vaultAddress);
+ assert(props.amount > 0n, 'Amount is 0');
+
+ if(!(props.allowance >= props.amount)) {
+ await handleTxWagmi(props, {
+ address: props.vaultAddress,
+ abi: ['function approve(address _spender, uint256 _value) external'],
+ functionName: 'approve',
+ args: [props.contractAddress, props.amount]
+ });
+ }
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_GAUGE_ABI,
+ functionName: 'deposit',
+ args: [props.amount]
+ });
+}
+
+type TStake = TWriteTransaction & {
+ amount: bigint;
+};
+export async function stake(props: TStake): Promise {
+ assertAddress(props.contractAddress);
+ assert(props.amount > 0n, 'Amount is 0');
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_GAUGE_ABI,
+ functionName: 'deposit',
+ args: [props.amount]
+ });
+}
+
+type TUnstake = TWriteTransaction & {
+ accountAddress: TAddress;
+ amount: bigint;
+};
+export async function unstake(props: TUnstake): Promise {
+ assertAddress(props.contractAddress);
+ assertAddress(props.accountAddress);
+ assert(props.amount > 0n, 'Amount is 0');
+
+ const willClaim = false;
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_GAUGE_ABI,
+ functionName: 'withdraw',
+ args: [props.amount, props.accountAddress, props.accountAddress, willClaim]
+ });
+}
+
+type TClaimRewards = TWriteTransaction;
+export async function claimRewards(props: TClaimRewards): Promise {
+ assertAddress(props.contractAddress);
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_GAUGE_ABI,
+ functionName: 'getReward'
+ });
+}
+
+type TClaimAllRewards = TWriteTransaction & {
+ gaugeAddresses: TAddress[];
+ willLockRewards: boolean;
+ claimVotingEscrow?: boolean;
+};
+export async function claimAllRewards(props: TClaimAllRewards): Promise {
+ assertAddress(props.contractAddress);
+ assertAddresses(props.gaugeAddresses);
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_CLAIM_REWARDS_ZAP_ABI,
+ functionName: 'claim',
+ args: [props.gaugeAddresses, props.willLockRewards, props.claimVotingEscrow]
+ });
+}
diff --git a/apps/veyfi/utils/actions/option.ts b/apps/veyfi/utils/actions/option.ts
new file mode 100644
index 000000000..90f7627f7
--- /dev/null
+++ b/apps/veyfi/utils/actions/option.ts
@@ -0,0 +1,27 @@
+import VEYFI_OPTIONS_ABI from '@veYFI/utils/abi/veYFIOptions.abi';
+import {assert} from '@common/utils/assert';
+import {assertAddress, handleTx as handleTxWagmi} from '@common/utils/wagmiUtils';
+
+import type {TAddress} from '@yearn-finance/web-lib/types';
+import type {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction';
+import type {TWriteTransaction} from '@common/utils/wagmiUtils';
+
+type TRedeem = TWriteTransaction & {
+ accountAddress: TAddress;
+ amount: bigint;
+ ethRequired: bigint;
+};
+export async function redeem(props: TRedeem): Promise {
+ assertAddress(props.contractAddress);
+ assertAddress(props.accountAddress);
+ assert(props.amount > 0n, 'amount is zero');
+ assert(props.ethRequired > 0n, 'ethRequired is zero');
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_OPTIONS_ABI,
+ functionName: 'exercise',
+ value: props.ethRequired,
+ args: [props.amount, props.accountAddress]
+ });
+}
diff --git a/apps/veyfi/utils/actions/votingEscrow.ts b/apps/veyfi/utils/actions/votingEscrow.ts
new file mode 100644
index 000000000..f8dd50fdd
--- /dev/null
+++ b/apps/veyfi/utils/actions/votingEscrow.ts
@@ -0,0 +1,114 @@
+import {stringToHex} from 'viem';
+import {prepareWriteContract} from '@wagmi/core';
+import {MAX_UINT_256} from '@yearn-finance/web-lib/utils/constants';
+import {toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber';
+import {assert} from '@common/utils/assert';
+import {assertAddresses, handleTx as handleTxWagmi} from '@common/utils/wagmiUtils';
+
+import SNAPSHOT_DELEGATE_REGISTRY_ABI from '../abi/SnapshotDelegateRegistry.abi';
+import VEYFI_ABI from '../abi/veYFI.abi';
+import {YEARN_SNAPSHOT_SPACE} from '../constants';
+
+import type {TAddress} from '@yearn-finance/web-lib/types';
+import type {TSeconds} from '@yearn-finance/web-lib/utils/time';
+import type {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction';
+import type {TWriteTransaction} from '@common/utils/wagmiUtils';
+
+type TApproveLock = TWriteTransaction & {votingEscrowAddress: TAddress; amount: bigint;};
+export async function approveLock(props: TApproveLock): Promise {
+ const {votingEscrowAddress, amount = MAX_UINT_256, contractAddress} = props;
+
+ assertAddresses([votingEscrowAddress, contractAddress]);
+ assert(amount > 0n, 'Amount is 0');
+
+ return await handleTxWagmi(props, {
+ address: contractAddress,
+ abi: ['function approve(address _spender, uint256 _value) external'],
+ functionName: 'approve',
+ args: [votingEscrowAddress, amount]
+ });
+}
+
+type TLock = TWriteTransaction & {votingEscrowAddress: TAddress; accountAddress: TAddress; amount: bigint; time: TSeconds};
+export async function lock(props: TLock): Promise {
+ assertAddresses([props.votingEscrowAddress, props.accountAddress, props.contractAddress]);
+ assert(props.amount > 0n, 'Amount is 0');
+ assert(props.time > 0, 'Time is 0');
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_ABI,
+ functionName: 'modify_lock',
+ args: [props.amount, toBigInt(props.time), props.accountAddress]
+ });
+}
+
+type TIncreaseLockAmount = TWriteTransaction & {votingEscrowAddress: TAddress; accountAddress: TAddress; amount: bigint};
+export async function increaseLockAmount(props: TIncreaseLockAmount): Promise {
+ assertAddresses([props.votingEscrowAddress, props.accountAddress, props.contractAddress]);
+ assert(props.amount > 0n, 'Amount is 0');
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_ABI,
+ functionName: 'modify_lock',
+ args: [props.amount, 0n, props.accountAddress]
+ });
+}
+
+type TExtendLockTime = TWriteTransaction & {votingEscrowAddress: TAddress; accountAddress: TAddress; time: TSeconds};
+export async function extendLockTime(props: TExtendLockTime): Promise {
+ assertAddresses([props.votingEscrowAddress, props.accountAddress, props.contractAddress]);
+ assert(props.time > 0, 'Time is 0');
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_ABI,
+ functionName: 'modify_lock',
+ args: [0n, toBigInt(props.time), props.accountAddress]
+ });
+}
+
+type TWithdrawUnlocked = TWriteTransaction & {votingEscrowAddress: TAddress};
+export async function withdrawUnlocked(props: TWithdrawUnlocked): Promise {
+ assertAddresses([props.votingEscrowAddress, props.contractAddress]);
+
+ const {result} = await prepareWriteContract({
+ address: props.votingEscrowAddress,
+ abi: VEYFI_ABI,
+ functionName: 'withdraw'
+ });
+
+ if (result.penalty > 0n) {
+ throw new Error('Tokens are not yet unlocked');
+ }
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_ABI,
+ functionName: 'withdraw'
+ });
+}
+
+type TWithdrawLocked = TWriteTransaction & {votingEscrowAddress: TAddress};
+export async function withdrawLocked(props: TWithdrawLocked): Promise {
+ assertAddresses([props.votingEscrowAddress, props.contractAddress]);
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: VEYFI_ABI,
+ functionName: 'withdraw'
+ });
+}
+
+type TDelegateVote = TWriteTransaction & {delegateAddress: TAddress};
+export async function delegateVote(props: TDelegateVote): Promise {
+ assertAddresses([props.delegateAddress, props.contractAddress]);
+
+ return await handleTxWagmi(props, {
+ address: props.contractAddress,
+ abi: SNAPSHOT_DELEGATE_REGISTRY_ABI,
+ functionName: 'setDelegate',
+ args: [stringToHex(YEARN_SNAPSHOT_SPACE, {size: 32}), props.delegateAddress]
+ });
+}
diff --git a/apps/veyfi/utils/constants.ts b/apps/veyfi/utils/constants.ts
index 6dceb85e7..e5e2a1e0d 100644
--- a/apps/veyfi/utils/constants.ts
+++ b/apps/veyfi/utils/constants.ts
@@ -1,6 +1,17 @@
+import {toAddress} from '@yearn-finance/web-lib/utils/address';
+
+import type {TAddress} from '@yearn-finance/web-lib/types';
import type {TWeeks} from '@yearn-finance/web-lib/utils/time';
+export const VEYFI_REGISTRY_ADDRESS: TAddress = toAddress(''); // TODO: update once deployed
+export const VEYFI_CLAIM_REWARDS_ZAP_ADDRESS: TAddress = toAddress(''); // TODO: update once deployed
+export const VEYFI_OPTIONS_ADDRESS = toAddress(''); // TODO: update once deployed
+export const VEYFI_OYFI_ADDRESS = toAddress(''); // TODO: update once deployed
+
export const MAX_LOCK_TIME: TWeeks = 208;
export const MIN_LOCK_TIME: TWeeks = 1;
export const MIN_LOCK_AMOUNT: TWeeks = 1;
+
+export const YEARN_SNAPSHOT_SPACE = 'veyfi.eth';
+export const SNAPSHOT_DELEGATE_REGISTRY_ADDRESS = toAddress('0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446');
diff --git a/apps/veyfi/utils/index.ts b/apps/veyfi/utils/index.ts
index e77832445..6f7d28cd8 100644
--- a/apps/veyfi/utils/index.ts
+++ b/apps/veyfi/utils/index.ts
@@ -1,6 +1,7 @@
import {toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {roundToWeek, toSeconds, YEAR} from '@yearn-finance/web-lib/utils/time';
+import type {TDict} from '@yearn-finance/web-lib/types';
import type {TMilliseconds, TSeconds} from '@yearn-finance/web-lib/utils/time';
const MAX_LOCK: TSeconds = toSeconds(roundToWeek(YEAR * 4));
@@ -15,3 +16,26 @@ export function getVotingPower(lockAmount: bigint, unlockTime: TMilliseconds): b
}
return lockAmount / toBigInt(MAX_LOCK) * toBigInt(duration);
}
+
+export const keyBy = (array: T1[], key: T2): TDict =>
+ (array || []).reduce((r, x): TDict => ({...r, [x[key] as string]: x}), {});
+
+export const isNumberable = (value: unknown): boolean => !isNaN(value as number);
+
+export const isString = (value: unknown): value is string => typeof value === 'string';
+
+export const sort = (data: T[], by: Extract, order?: 'asc' | 'desc'): T[] => {
+ const compare = (a: T, b: T): number => {
+ const elementA = a[by];
+ const elementB = b[by];
+ if (isNumberable(elementA) && isNumberable(elementB)) {
+ return order === 'desc' ? Number(elementA) - Number(elementB) : Number(elementB) - Number(elementA);
+ }
+ if (isString(elementA) && isString(elementB)) {
+ return order === 'desc' ? elementA.toLowerCase().localeCompare(elementB.toLowerCase()) : elementB.toLowerCase().localeCompare(elementA.toLowerCase());
+ }
+ return 0;
+ };
+
+ return [...data].sort(compare);
+};
diff --git a/apps/veyfi/utils/validations.ts b/apps/veyfi/utils/validations.ts
index 63f933d78..d68f3fc3f 100644
--- a/apps/veyfi/utils/validations.ts
+++ b/apps/veyfi/utils/validations.ts
@@ -1,3 +1,4 @@
+import {isAddress} from 'viem';
import {allowanceKey} from '@yearn-finance/web-lib/utils/address';
import {isZero} from '@yearn-finance/web-lib/utils/isZero';
@@ -84,3 +85,21 @@ export function validateNetwork(props: TValidateNetworkProps): TValidationRespon
return {isValid: true};
}
+
+export type TValidateAddressProps = {
+ address?: string;
+}
+
+export function validateAddress(props: TValidateAddressProps): TValidationResponse {
+ const {address} = props;
+
+ if(!address) {
+ return {isValid: false};
+ }
+
+ if (!isAddress(address)) {
+ return {isValid: false, error: 'Invalid Address'};
+ }
+
+ return {isValid: true};
+}
diff --git a/package.json b/package.json
index b92f86a01..e0987d5e7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "yearnfi",
- "version": "0.1.24",
+ "version": "0.1.25",
"scripts": {
"dev": "next",
"inspect": "NODE_OPTIONS='--inspect' next",
@@ -75,9 +75,9 @@
"postcss-nesting": "^12.0.0",
"sass": "^1.63.3",
"sharp": "^0.32.1",
+ "vitest": "^0.32.2",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
- "vitest": "^0.32.0",
"webpack": "^5.88.2"
},
"resolutions": {
diff --git a/pages/veyfi/index.tsx b/pages/veyfi/index.tsx
index 48dceec81..1d346c84a 100755
--- a/pages/veyfi/index.tsx
+++ b/pages/veyfi/index.tsx
@@ -1,6 +1,10 @@
import {ClaimTab} from '@veYFI/components/ClaimTab';
+import {GaugesTab} from '@veYFI/components/GaugesTab';
import {LockTab} from '@veYFI/components/LockTab';
import {ManageLockTab} from '@veYFI/components/ManageLockTab';
+import {RedeemTab} from '@veYFI/components/RedeemTab';
+import {RewardsTab} from '@veYFI/components/RewardsTab';
+import {VoteTab} from '@veYFI/components/VoteTab';
import {useVotingEscrow} from '@veYFI/contexts/useVotingEscrow';
import Wrapper from '@veYFI/Wrapper';
import {formatToNormalizedValue, toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber';
@@ -8,6 +12,7 @@ import {formatAmount} 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';
+import {useFeatureFlag} from '@common/hooks/useFeatureFlag';
import {formatDateShort} from '@common/utils';
import type {NextRouter} from 'next/router';
@@ -15,6 +20,7 @@ import type {ReactElement} from 'react';
function Index(): ReactElement {
const {votingEscrow, positions, isLoading} = useVotingEscrow();
+ const [isGaugeVotingFeatureEnabled] = useFeatureFlag('gauge-voting');
const totalLockedYFI = formatToNormalizedValue(toBigInt(votingEscrow?.supply), 18);
const yourLockedYFI = formatToNormalizedValue(toBigInt(positions?.deposit?.underlyingBalance), 18);
@@ -22,12 +28,18 @@ function Index(): ReactElement {
const tabs = [
{id: 'lock', label: 'Lock YFI', content: },
{id: 'manage', label: 'Manage lock', content: },
- {id: 'claim', label: 'Claim', content: }
- ];
+ {id: 'claim', label: 'Claim', content: },
+ ...isGaugeVotingFeatureEnabled ? [
+ {id: 'gauges', label: 'Stake / Unstake', content: },
+ {id: 'rewards', label: 'Rewards', content: },
+ {id: 'redeem', label: 'Redeem oYFI', content: },
+ {id: 'vote', label: 'Vote for Gauge', content: }
+ ] : []
+ ].filter(Boolean);
return (
<>
-
+
{'veYFI'}
diff --git a/yarn.lock b/yarn.lock
index 8555355a7..a9b383b40 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2786,13 +2786,13 @@
"@babel/plugin-transform-react-jsx-source" "^7.22.5"
react-refresh "^0.14.0"
-"@vitest/expect@0.32.4":
- version "0.32.4"
- resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.32.4.tgz#4aa4eec78112cdbe299834b965420d4fb3afa91d"
- integrity sha512-m7EPUqmGIwIeoU763N+ivkFjTzbaBn0n9evsTOcde03ugy2avPs3kZbYmw3DkcH1j5mxhMhdamJkLQ6dM1bk/A==
+"@vitest/expect@0.32.2":
+ version "0.32.2"
+ resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.32.2.tgz#8111f6ab1ff3b203efbe3a25e8bb2d160ce4b720"
+ integrity sha512-6q5yzweLnyEv5Zz1fqK5u5E83LU+gOMVBDuxBl2d2Jfx1BAp5M+rZgc5mlyqdnxquyoiOXpXmFNkcGcfFnFH3Q==
dependencies:
- "@vitest/spy" "0.32.4"
- "@vitest/utils" "0.32.4"
+ "@vitest/spy" "0.32.2"
+ "@vitest/utils" "0.32.2"
chai "^4.3.7"
"@vitest/expect@0.33.0":
@@ -2804,14 +2804,15 @@
"@vitest/utils" "0.33.0"
chai "^4.3.7"
-"@vitest/runner@0.32.4":
- version "0.32.4"
- resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.32.4.tgz#2872c697994745f1b70e2bd6568236ad2d9eade6"
- integrity sha512-cHOVCkiRazobgdKLnczmz2oaKK9GJOw6ZyRcaPdssO1ej+wzHVIkWiCiNacb3TTYPdzMddYkCgMjZ4r8C0JFCw==
+"@vitest/runner@0.32.2":
+ version "0.32.2"
+ resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.32.2.tgz#18dd979ce4e8766bcc90948d11b4c8ae6ed90b89"
+ integrity sha512-06vEL0C1pomOEktGoLjzZw+1Fb+7RBRhmw/06WkDrd1akkT9i12su0ku+R/0QM69dfkIL/rAIDTG+CSuQVDcKw==
dependencies:
- "@vitest/utils" "0.32.4"
+ "@vitest/utils" "0.32.2"
+ concordance "^5.0.4"
p-limit "^4.0.0"
- pathe "^1.1.1"
+ pathe "^1.1.0"
"@vitest/runner@0.33.0":
version "0.33.0"
@@ -2822,14 +2823,14 @@
p-limit "^4.0.0"
pathe "^1.1.1"
-"@vitest/snapshot@0.32.4":
- version "0.32.4"
- resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.32.4.tgz#75166b1c772d018278a7f0e79f43f3eae813f5ae"
- integrity sha512-IRpyqn9t14uqsFlVI2d7DFMImGMs1Q9218of40bdQQgMePwVdmix33yMNnebXcTzDU5eiV3eUsoxxH5v0x/IQA==
+"@vitest/snapshot@0.32.2":
+ version "0.32.2"
+ resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.32.2.tgz#500b6453e88e4c50a0aded39839352c16b519b9e"
+ integrity sha512-JwhpeH/PPc7GJX38vEfCy9LtRzf9F4er7i4OsAJyV7sjPwjj+AIR8cUgpMTWK4S3TiamzopcTyLsZDMuldoi5A==
dependencies:
magic-string "^0.30.0"
- pathe "^1.1.1"
- pretty-format "^29.5.0"
+ pathe "^1.1.0"
+ pretty-format "^27.5.1"
"@vitest/snapshot@0.33.0":
version "0.33.0"
@@ -2840,12 +2841,12 @@
pathe "^1.1.1"
pretty-format "^29.5.0"
-"@vitest/spy@0.32.4":
- version "0.32.4"
- resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.32.4.tgz#c3212bc60c1430c3b5c39d6a384a75458b8f1e80"
- integrity sha512-oA7rCOqVOOpE6rEoXuCOADX7Lla1LIa4hljI2MSccbpec54q+oifhziZIJXxlE/CvI2E+ElhBHzVu0VEvJGQKQ==
+"@vitest/spy@0.32.2":
+ version "0.32.2"
+ resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.32.2.tgz#f3ef7afe0d34e863b90df7c959fa5af540a6aaf9"
+ integrity sha512-Q/ZNILJ4ca/VzQbRM8ur3Si5Sardsh1HofatG9wsJY1RfEaw0XKP8IVax2lI1qnrk9YPuG9LA2LkZ0EI/3d4ug==
dependencies:
- tinyspy "^2.1.1"
+ tinyspy "^2.1.0"
"@vitest/spy@0.33.0":
version "0.33.0"
@@ -2854,14 +2855,14 @@
dependencies:
tinyspy "^2.1.1"
-"@vitest/utils@0.32.4":
- version "0.32.4"
- resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.32.4.tgz#36283e3aa3f3b1a378e19493c7b3b9107dc4ea71"
- integrity sha512-Gwnl8dhd1uJ+HXrYyV0eRqfmk9ek1ASE/LWfTCuWMw+d07ogHqp4hEAV28NiecimK6UY9DpSEPh+pXBA5gtTBg==
+"@vitest/utils@0.32.2":
+ version "0.32.2"
+ resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.32.2.tgz#809c720cafbf4b35ce651deb8570d57785e77819"
+ integrity sha512-lnJ0T5i03j0IJaeW73hxe2AuVnZ/y1BhhCOuIcl9LIzXnbpXJT9Lrt6brwKHXLOiA7MZ6N5hSJjt0xE1dGNCzQ==
dependencies:
diff-sequences "^29.4.3"
loupe "^2.3.6"
- pretty-format "^29.5.0"
+ pretty-format "^27.5.1"
"@vitest/utils@0.33.0":
version "0.33.0"
@@ -3889,6 +3890,11 @@ bl@^4.0.3:
inherits "^2.0.4"
readable-stream "^3.4.0"
+blueimp-md5@^2.10.0:
+ version "2.19.0"
+ resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0"
+ integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==
+
bn.js@5.2.1, bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0, bn.js@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
@@ -4282,6 +4288,20 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+concordance@^5.0.4:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/concordance/-/concordance-5.0.4.tgz#9896073261adced72f88d60e4d56f8efc4bbbbd2"
+ integrity sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==
+ dependencies:
+ date-time "^3.1.0"
+ esutils "^2.0.3"
+ fast-diff "^1.2.0"
+ js-string-escape "^1.0.1"
+ lodash "^4.17.15"
+ md5-hex "^3.0.1"
+ semver "^7.3.2"
+ well-known-symbols "^2.0.0"
+
convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
@@ -4448,6 +4468,13 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
+date-time@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/date-time/-/date-time-3.1.0.tgz#0d1e934d170579f481ed8df1e2b8ff70ee845e1e"
+ integrity sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==
+ dependencies:
+ time-zone "^1.0.0"
+
dayjs@^1.11.8, dayjs@^1.11.9:
version "1.11.9"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
@@ -5150,7 +5177,7 @@ estree-walker@^2.0.2:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
-esutils@^2.0.2:
+esutils@^2.0.2, esutils@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
@@ -5333,6 +5360,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+fast-diff@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
+ integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
+
fast-equals@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d"
@@ -6361,6 +6393,11 @@ js-sha3@0.8.0:
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
+js-string-escape@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
+ integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -6646,7 +6683,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
-lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.21:
+lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -6722,6 +6759,13 @@ make-error@1.x:
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+md5-hex@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c"
+ integrity sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==
+ dependencies:
+ blueimp-md5 "^2.10.0"
+
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -7500,7 +7544,7 @@ pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
-pretty-format@^27.0.2:
+pretty-format@^27.0.2, pretty-format@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
@@ -8088,7 +8132,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4:
+semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4:
version "7.5.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
@@ -8271,11 +8315,16 @@ stacktrace-parser@^0.1.10:
dependencies:
type-fest "^0.7.1"
-std-env@^3.3.3:
+std-env@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.3.tgz#a54f06eb245fdcfef53d56f3c0251f1d5c3d01fe"
integrity sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==
+std-env@^3.3.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910"
+ integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==
+
stop-iteration-iterator@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4"
@@ -8675,6 +8724,11 @@ thread-stream@^0.15.1:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
+time-zone@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d"
+ integrity sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==
+
tinybench@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.5.0.tgz#4711c99bbf6f3e986f67eb722fed9cddb3a68ba5"
@@ -8690,7 +8744,7 @@ tinypool@^0.6.0:
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.6.0.tgz#c3640b851940abe2168497bb6e43b49fafb3ba7b"
integrity sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==
-tinyspy@^2.1.1:
+tinyspy@^2.1.0, tinyspy@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.1.1.tgz#9e6371b00c259e5c5b301917ca18c01d40ae558c"
integrity sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==
@@ -9091,15 +9145,15 @@ viem@^1.5.3:
isomorphic-ws "5.0.0"
ws "8.12.0"
-vite-node@0.32.4:
- version "0.32.4"
- resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.32.4.tgz#7b3f94af5a87c631fbc380ba662914bafbd04d80"
- integrity sha512-L2gIw+dCxO0LK14QnUMoqSYpa9XRGnTTTDjW2h19Mr+GR0EFj4vx52W41gFXfMLqpA00eK4ZjOVYo1Xk//LFEw==
+vite-node@0.32.2:
+ version "0.32.2"
+ resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.32.2.tgz#bfccdfeb708b2309ea9e5fe424951c75bb9c0096"
+ integrity sha512-dTQ1DCLwl2aEseov7cfQ+kDMNJpM1ebpyMMMwWzBvLbis8Nla/6c9WQcqpPssTwS6Rp/+U6KwlIj8Eapw4bLdA==
dependencies:
cac "^6.7.14"
debug "^4.3.4"
- mlly "^1.4.0"
- pathe "^1.1.1"
+ mlly "^1.2.0"
+ pathe "^1.1.0"
picocolors "^1.0.0"
vite "^3.0.0 || ^4.0.0"
@@ -9126,34 +9180,35 @@ vite-node@0.33.0:
optionalDependencies:
fsevents "~2.3.2"
-vitest@^0.32.0:
- version "0.32.4"
- resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.32.4.tgz#a0558ae44c2ccdc254eece0365f16c4ffc5231bb"
- integrity sha512-3czFm8RnrsWwIzVDu/Ca48Y/M+qh3vOnF16czJm98Q/AN1y3B6PBsyV8Re91Ty5s7txKNjEhpgtGPcfdbh2MZg==
+vitest@^0.32.2:
+ version "0.32.2"
+ resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.32.2.tgz#758ce2220f609e240ac054eca7ad11a5140679ab"
+ integrity sha512-hU8GNNuQfwuQmqTLfiKcqEhZY72Zxb7nnN07koCUNmntNxbKQnVbeIS6sqUgR3eXSlbOpit8+/gr1KpqoMgWCQ==
dependencies:
"@types/chai" "^4.3.5"
"@types/chai-subset" "^1.3.3"
"@types/node" "*"
- "@vitest/expect" "0.32.4"
- "@vitest/runner" "0.32.4"
- "@vitest/snapshot" "0.32.4"
- "@vitest/spy" "0.32.4"
- "@vitest/utils" "0.32.4"
- acorn "^8.9.0"
+ "@vitest/expect" "0.32.2"
+ "@vitest/runner" "0.32.2"
+ "@vitest/snapshot" "0.32.2"
+ "@vitest/spy" "0.32.2"
+ "@vitest/utils" "0.32.2"
+ acorn "^8.8.2"
acorn-walk "^8.2.0"
cac "^6.7.14"
chai "^4.3.7"
+ concordance "^5.0.4"
debug "^4.3.4"
local-pkg "^0.4.3"
magic-string "^0.30.0"
- pathe "^1.1.1"
+ pathe "^1.1.0"
picocolors "^1.0.0"
- std-env "^3.3.3"
+ std-env "^3.3.2"
strip-literal "^1.0.1"
tinybench "^2.5.0"
tinypool "^0.5.0"
vite "^3.0.0 || ^4.0.0"
- vite-node "0.32.4"
+ vite-node "0.32.2"
why-is-node-running "^2.2.2"
vitest@^0.33.0:
@@ -9281,6 +9336,11 @@ webpack@^5.88.2:
watchpack "^2.4.0"
webpack-sources "^3.2.3"
+well-known-symbols@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-2.0.0.tgz#e9c7c07dbd132b7b84212c8174391ec1f9871ba5"
+ integrity sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==
+
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"