From 7bb9c343ca5d441f349f78834fadae8cf7606117 Mon Sep 17 00:00:00 2001 From: Major Date: Fri, 2 Jun 2023 09:46:33 +0200 Subject: [PATCH 01/10] feat: wagmi handleTX init --- apps/common/utils/actions.tsx | 230 +++++--------------------- apps/common/utils/toWagmiProvider.tsx | 62 ++++++- apps/ycrv/utils/actions.ts | 49 +----- 3 files changed, 112 insertions(+), 229 deletions(-) diff --git a/apps/common/utils/actions.tsx b/apps/common/utils/actions.tsx index 8cd579b23..36f9be916 100755 --- a/apps/common/utils/actions.tsx +++ b/apps/common/utils/actions.tsx @@ -2,7 +2,6 @@ import {captureException} from '@sentry/nextjs'; import {getEthZapperContract} from '@vaults/utils'; import VAULT_MIGRATOR_ABI from '@vaults/utils/abi/vaultMigrator.abi'; import {erc20ABI, prepareWriteContract, readContract, waitForTransaction, writeContract} from '@wagmi/core'; -import {toast} from '@yearn-finance/web-lib/components/yToast'; import PARTNER_VAULT_ABI from '@yearn-finance/web-lib/utils/abi/partner.vault.abi'; import VAULT_ABI from '@yearn-finance/web-lib/utils/abi/vault.abi'; import ZAP_ETH_TO_YVETH_ABI from '@yearn-finance/web-lib/utils/abi/zapEthToYvEth.abi'; @@ -11,7 +10,7 @@ import {toAddress} from '@yearn-finance/web-lib/utils/address'; import {MAX_UINT_256} from '@yearn-finance/web-lib/utils/constants'; import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; import {assert} from '@common/utils/assert'; -import {assertAddress, toWagmiProvider} from '@common/utils/toWagmiProvider'; +import {assertAddress, handleTx, toWagmiProvider} from '@common/utils/toWagmiProvider'; import type {BaseError, ContractFunctionExecutionError, Hex} from 'viem'; import type {Connector} from 'wagmi'; @@ -74,60 +73,27 @@ type TApproveERC20 = TWriteTransaction & { amount: bigint; }; export async function approveERC20(props: TApproveERC20): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.spenderAddress, 'spenderAddress'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - try { - const wagmiProvider = await toWagmiProvider(props.connector); - try { - const config = await prepareWriteContract({ - ...wagmiProvider, + try { + return await handleTx(props, { + address: props.contractAddress, + abi: erc20ABI, + functionName: 'approve', + args: [props.spenderAddress, props.amount] + }); + } catch (error) { + const err = error as ContractFunctionExecutionError; + if (err.name === 'ContractFunctionExecutionError') { + return await handleTx(props, { address: props.contractAddress, - abi: erc20ABI, + abi: ALTERNATE_ERC20_APPROVE_ABI, functionName: 'approve', args: [props.spenderAddress, props.amount] }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - const err = error as ContractFunctionExecutionError; - if (err.name === 'ContractFunctionExecutionError') { - const alternateConfig = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: ALTERNATE_ERC20_APPROVE_ABI, - functionName: 'approve', - args: [props.spenderAddress, props.amount] - }); - const {hash} = await writeContract(alternateConfig.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } - return ({isSuccessful: false, error: err || ''}); } - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); } + return ({isSuccessful: false, error: new Error('Unknown error')}); } /* 🔵 - Yearn Finance ********************************************************** @@ -141,39 +107,14 @@ type TDeposit = TWriteTransaction & { amount: bigint; }; export async function deposit(props: TDeposit): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assert(props.amount > 0n, 'Amount is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: VAULT_ABI, - functionName: 'deposit', - args: [props.amount] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: VAULT_ABI, + functionName: 'deposit', + args: [props.amount] + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -252,44 +193,19 @@ type TDepositViaPartner = TWriteTransaction & { amount: bigint; }; export async function depositViaPartner(props: TDepositViaPartner): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.vaultAddress, 'vaultAddress'); assert(props.amount > 0n, 'Amount is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: PARTNER_VAULT_ABI, - functionName: 'deposit', - args: [ - props.vaultAddress, - props.partnerAddress || toAddress(process.env.PARTNER_ID_ADDRESS), - props.amount - ] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: PARTNER_VAULT_ABI, + functionName: 'deposit', + args: [ + props.vaultAddress, + props.partnerAddress || toAddress(process.env.PARTNER_ID_ADDRESS), + props.amount + ] + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -362,41 +278,18 @@ export async function withdrawETH(props: TWithdrawEth): Promise { ** @param amount - The amount of ETH to withdraw. ******************************************************************************/ type TWithdrawShares = TWriteTransaction & { - amount: bigint; + amount: bigint | undefined; }; export async function withdrawShares(props: TWithdrawShares): Promise { - assertAddress(props.contractAddress, 'contractAddress'); + assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: VAULT_ABI, - functionName: 'withdraw', - args: [props.amount] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: VAULT_ABI, + functionName: 'withdraw', + args: [props.amount] + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -412,48 +305,13 @@ type TMigrateShares = TWriteTransaction & { toVault: TAddress | undefined; }; export async function migrateShares(props: TMigrateShares): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.fromVault, 'fromVault'); assertAddress(props.toVault, 'toVault'); - const wagmiProvider = await toWagmiProvider(props.connector); - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: VAULT_MIGRATOR_ABI, - functionName: 'migrateAll', - args: [props.fromVault, props.toVault] - }); - - const gas = config?.request?.gas || 0n; - const estimateGas = new Intl.NumberFormat([navigator.language || 'fr-FR', 'en-US']).format(gas); - const safeGas = new Intl.NumberFormat([navigator.language || 'fr-FR', 'en-US']).format(gas * 13n / 10n); - toast({ - type: 'info', - content: `Gas estimate for migration is ${estimateGas}. We'll use ${safeGas} to give some margin and reduce the risk of transaction failure.`, - duration: 10000 - }); - console.info(`Gas estimate for migration is ${estimateGas}. We'll use ${safeGas} to give some margin and reduce the risk of transaction failure.`); - config.request.gas = gas * 13n / 10n; - - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: VAULT_MIGRATOR_ABI, + functionName: 'migrateAll', + args: [props.fromVault, props.toVault] + }); } diff --git a/apps/common/utils/toWagmiProvider.tsx b/apps/common/utils/toWagmiProvider.tsx index 1ee716b4d..3e22548f2 100644 --- a/apps/common/utils/toWagmiProvider.tsx +++ b/apps/common/utils/toWagmiProvider.tsx @@ -1,12 +1,16 @@ +import {captureException} from '@sentry/nextjs'; +import {prepareWriteContract, waitForTransaction, writeContract} from '@wagmi/core'; import {toAddress} from '@yearn-finance/web-lib/utils/address'; import {ETH_TOKEN_ADDRESS, ZERO_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; 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, BaseError, SimulateContractParameters} from 'viem'; import type {Connector} from 'wagmi'; import type {TAddress} from '@yearn-finance/web-lib/types'; -import type {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; -import type {GetWalletClientResult} from '@wagmi/core'; +import type {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction'; +import type {GetWalletClientResult, WalletClient} from '@wagmi/core'; export type TWagmiProviderContract = { walletClient: GetWalletClientResult, @@ -38,3 +42,57 @@ export function assertAddress(addr: string | TAddress | undefined, name?: string assert(toAddress(addr) !== ZERO_ADDRESS, `${name || 'Address'} is 0x0`); assert(toAddress(addr) !== ETH_TOKEN_ADDRESS, `${name || 'Address'} is 0xE`); } + +type TPrepareWriteContractConfig< + TAbi extends Abi | readonly unknown[] = Abi, + TFunctionName extends string = string, + TChainId extends number = number, + TWalletClient extends WalletClient = WalletClient +> = Omit, 'chain' | 'address'> & { + chainId?: TChainId | number + walletClient?: TWalletClient | null + address: TAddress | undefined +} +export async function handleTx< + TAbi extends Abi | readonly unknown[], + TFunctionName extends string, + TChainId extends number, + TWalletClient extends WalletClient = WalletClient +>( + args: TWriteTransaction, + props: TPrepareWriteContractConfig +): Promise { + args.statusHandler?.({...defaultTxStatus, pending: true}); + const wagmiProvider = await toWagmiProvider(args.connector); + + //Some extra assertions + assertAddress(props.address, 'contractAddress'); + assertAddress(wagmiProvider.address, 'userAddress'); + try { + const config = await prepareWriteContract({ + ...wagmiProvider, + ...props as TPrepareWriteContractConfig, + address: props.address + }); + 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}); + } + return ({isSuccessful: receipt.status === 'success', receipt}); + } catch (error) { + console.error(error); + const errorAsBaseError = error as BaseError; + if (process.env.NODE_ENV === 'production') { + captureException(errorAsBaseError); + } + args.statusHandler?.({...defaultTxStatus, error: true}); + return ({isSuccessful: false, error: errorAsBaseError || ''}); + } finally { + setTimeout((): void => { + args.statusHandler?.({...defaultTxStatus}); + }, 3000); + } +} diff --git a/apps/ycrv/utils/actions.ts b/apps/ycrv/utils/actions.ts index 997d0c1a4..7f9dfb55f 100644 --- a/apps/ycrv/utils/actions.ts +++ b/apps/ycrv/utils/actions.ts @@ -1,12 +1,8 @@ -import {captureException} from '@sentry/nextjs'; -import {prepareWriteContract, waitForTransaction, writeContract} from '@wagmi/core'; import {ZAP_YEARN_VE_CRV_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; -import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; import {assert} from '@common/utils/assert'; -import {assertAddress, toWagmiProvider} from '@common/utils/toWagmiProvider'; +import {assertAddress, handleTx} from '@common/utils/toWagmiProvider'; import ZAP_CRV_ABI from '@yCRV/utils/abi/zapCRV.abi'; -import type {BaseError} from 'viem'; 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/toWagmiProvider'; @@ -30,46 +26,17 @@ type TZapYCRV = TWriteTransaction & { slippage: bigint; }; export async function zapCRV(props: TZapYCRV): Promise { + const minAmountWithSlippage = props.minAmount * (1n - (props.slippage / 100n)); assertAddress(ZAP_YEARN_VE_CRV_ADDRESS, 'ZAP_YEARN_VE_CRV_ADDRESS'); - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.inputToken, 'inputToken'); assertAddress(props.outputToken, 'outputToken'); assert(props.amount > 0n, 'Amount must be greater than 0'); - assert(props.minAmount > 0n, 'Min amount must be greater than 0'); - assert(props.minAmount <= props.amount, 'Min amount must be less than amount'); - - const minAmountWithSlippage = props.minAmount * (1n - (props.slippage / 100n)); assert(props.amount >= minAmountWithSlippage, 'Amount must be greater or equal to min amount with slippage'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: ZAP_YEARN_VE_CRV_ADDRESS, - abi: ZAP_CRV_ABI, - functionName: 'zap', - args: [props.inputToken, props.outputToken, props.amount, minAmountWithSlippage] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } - + return await handleTx(props, { + address: ZAP_YEARN_VE_CRV_ADDRESS, + abi: ZAP_CRV_ABI, + functionName: 'zap', + args: [props.inputToken, props.outputToken, props.amount, minAmountWithSlippage] + }); } From ce178efab8b64a5b844b662e409efc60514c933c Mon Sep 17 00:00:00 2001 From: Major Date: Fri, 2 Jun 2023 09:56:00 +0200 Subject: [PATCH 02/10] fix: remove some level of complexity --- apps/common/utils/actions.tsx | 2 +- apps/common/utils/toWagmiProvider.tsx | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/common/utils/actions.tsx b/apps/common/utils/actions.tsx index 36f9be916..896f99db0 100755 --- a/apps/common/utils/actions.tsx +++ b/apps/common/utils/actions.tsx @@ -19,7 +19,7 @@ import type {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction'; import type {TWriteTransaction} from '@common/utils/toWagmiProvider'; //Because USDT do not return a boolean on approve, we need to use this ABI -const ALTERNATE_ERC20_APPROVE_ABI = [{'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}]; +const ALTERNATE_ERC20_APPROVE_ABI = [{'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}] as const; /* 🔵 - Yearn Finance ********************************************************** ** isApprovedERC20 is a _VIEW_ function that checks if a token is approved for diff --git a/apps/common/utils/toWagmiProvider.tsx b/apps/common/utils/toWagmiProvider.tsx index 3e22548f2..5dfa37b20 100644 --- a/apps/common/utils/toWagmiProvider.tsx +++ b/apps/common/utils/toWagmiProvider.tsx @@ -45,22 +45,18 @@ export function assertAddress(addr: string | TAddress | undefined, name?: string type TPrepareWriteContractConfig< TAbi extends Abi | readonly unknown[] = Abi, - TFunctionName extends string = string, - TChainId extends number = number, - TWalletClient extends WalletClient = WalletClient + TFunctionName extends string = string > = Omit, 'chain' | 'address'> & { - chainId?: TChainId | number - walletClient?: TWalletClient | null + chainId?: number + walletClient?: WalletClient address: TAddress | undefined } export async function handleTx< TAbi extends Abi | readonly unknown[], - TFunctionName extends string, - TChainId extends number, - TWalletClient extends WalletClient = WalletClient + TFunctionName extends string >( args: TWriteTransaction, - props: TPrepareWriteContractConfig + props: TPrepareWriteContractConfig ): Promise { args.statusHandler?.({...defaultTxStatus, pending: true}); const wagmiProvider = await toWagmiProvider(args.connector); From 027b05ac1391b95792f2ff2d07fad238a118a262 Mon Sep 17 00:00:00 2001 From: Major Date: Fri, 2 Jun 2023 10:06:12 +0200 Subject: [PATCH 03/10] feat: migrate the common actions --- apps/common/utils/actions.tsx | 110 ++++++++++------------------------ 1 file changed, 30 insertions(+), 80 deletions(-) diff --git a/apps/common/utils/actions.tsx b/apps/common/utils/actions.tsx index 896f99db0..b7ae6b3a7 100755 --- a/apps/common/utils/actions.tsx +++ b/apps/common/utils/actions.tsx @@ -1,18 +1,16 @@ -import {captureException} from '@sentry/nextjs'; import {getEthZapperContract} from '@vaults/utils'; import VAULT_MIGRATOR_ABI from '@vaults/utils/abi/vaultMigrator.abi'; -import {erc20ABI, prepareWriteContract, readContract, waitForTransaction, writeContract} from '@wagmi/core'; +import {erc20ABI, readContract} from '@wagmi/core'; import PARTNER_VAULT_ABI from '@yearn-finance/web-lib/utils/abi/partner.vault.abi'; import VAULT_ABI from '@yearn-finance/web-lib/utils/abi/vault.abi'; import ZAP_ETH_TO_YVETH_ABI from '@yearn-finance/web-lib/utils/abi/zapEthToYvEth.abi'; import ZAP_FTM_TO_YVFTM_ABI from '@yearn-finance/web-lib/utils/abi/zapFtmToYvFTM.abi'; import {toAddress} from '@yearn-finance/web-lib/utils/address'; import {MAX_UINT_256} from '@yearn-finance/web-lib/utils/constants'; -import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; import {assert} from '@common/utils/assert'; import {assertAddress, handleTx, toWagmiProvider} from '@common/utils/toWagmiProvider'; -import type {BaseError, ContractFunctionExecutionError, Hex} from 'viem'; +import type {ContractFunctionExecutionError} 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'; @@ -129,53 +127,29 @@ type TDepositEth = TWriteTransaction & { amount: bigint; }; export async function depositETH(props: TDepositEth): Promise { - const wagmiProvider = await toWagmiProvider(props.connector); - const destAddress = getEthZapperContract(wagmiProvider.chainId); - assertAddress(props.contractAddress, 'contractAddress'); - assertAddress(destAddress, 'destAddress'); + assert(props.connector, 'No connector'); assert(props.amount > 0n, 'Amount is 0'); - - props.statusHandler?.({...defaultTxStatus, pending: true}); - try { - let txHash: Hex; - if (wagmiProvider.chainId === 250) { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: destAddress, - abi: ZAP_FTM_TO_YVFTM_ABI, + const chainID = await props.connector.getChainId(); + switch (chainID) { + case 1: { + return await handleTx(props, { + address: getEthZapperContract(1), + abi: ZAP_ETH_TO_YVETH_ABI, functionName: 'deposit', value: props.amount }); - const {hash} = await writeContract(config.request); - txHash = hash; - } else { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: destAddress, - abi: ZAP_ETH_TO_YVETH_ABI, + } + case 250: { + return await handleTx(props, { + address: getEthZapperContract(250), + abi: ZAP_FTM_TO_YVFTM_ABI, functionName: 'deposit', value: props.amount }); - const {hash} = await writeContract(config.request); - txHash = hash; } - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash: txHash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); + default: { + throw new Error('Invalid chainId'); } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); } } @@ -220,53 +194,29 @@ type TWithdrawEth = TWriteTransaction & { amount: bigint; }; export async function withdrawETH(props: TWithdrawEth): Promise { - const wagmiProvider = await toWagmiProvider(props.connector); - const destAddress = getEthZapperContract(wagmiProvider.chainId); - assertAddress(props.contractAddress, 'contractAddress'); - assertAddress(destAddress, 'destAddress'); + assert(props.connector, 'No connector'); assert(props.amount > 0n, 'Amount is 0'); - - props.statusHandler?.({...defaultTxStatus, pending: true}); - try { - let txHash: Hex; - if (wagmiProvider.chainId === 250) { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: destAddress, - abi: ZAP_FTM_TO_YVFTM_ABI, + const chainID = await props.connector.getChainId(); + switch (chainID) { + case 1: { + return await handleTx(props, { + address: getEthZapperContract(1), + abi: ZAP_ETH_TO_YVETH_ABI, functionName: 'withdraw', args: [props.amount] }); - const {hash} = await writeContract(config.request); - txHash = hash; - } else { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: destAddress, - abi: ZAP_ETH_TO_YVETH_ABI, + } + case 250: { + return await handleTx(props, { + address: getEthZapperContract(250), + abi: ZAP_FTM_TO_YVFTM_ABI, functionName: 'withdraw', args: [props.amount] }); - const {hash} = await writeContract(config.request); - txHash = hash; } - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash: txHash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); + default: { + throw new Error('Invalid chainId'); } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); } } From 2edaae5937794422fc1bc09c0f29445f0bff6c46 Mon Sep 17 00:00:00 2001 From: Major Date: Fri, 2 Jun 2023 10:08:03 +0200 Subject: [PATCH 04/10] feat: suggestion to make bigInt |undefined to be sure to check stuff --- apps/common/utils/actions.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/common/utils/actions.tsx b/apps/common/utils/actions.tsx index b7ae6b3a7..d8348a039 100755 --- a/apps/common/utils/actions.tsx +++ b/apps/common/utils/actions.tsx @@ -68,9 +68,10 @@ export async function approvedERC20Amount( ******************************************************************************/ type TApproveERC20 = TWriteTransaction & { spenderAddress: TAddress | undefined; - amount: bigint; + amount: bigint | undefined; }; export async function approveERC20(props: TApproveERC20): Promise { + assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assertAddress(props.spenderAddress, 'spenderAddress'); try { @@ -102,9 +103,10 @@ export async function approveERC20(props: TApproveERC20): Promise { ** @param amount - The amount of ETH to deposit. ******************************************************************************/ type TDeposit = TWriteTransaction & { - amount: bigint; + amount: bigint | undefined; }; export async function deposit(props: TDeposit): Promise { + assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); return await handleTx(props, { @@ -124,10 +126,11 @@ export async function deposit(props: TDeposit): Promise { ** @param amount - The amount of collateral to deposit. ******************************************************************************/ type TDepositEth = TWriteTransaction & { - amount: bigint; + amount: bigint | undefined; }; export async function depositETH(props: TDepositEth): Promise { assert(props.connector, 'No connector'); + assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); const chainID = await props.connector.getChainId(); switch (chainID) { @@ -164,10 +167,11 @@ export async function depositETH(props: TDepositEth): Promise { type TDepositViaPartner = TWriteTransaction & { vaultAddress: TAddress | undefined; partnerAddress: TAddress | undefined; - amount: bigint; + amount: bigint | undefined; }; export async function depositViaPartner(props: TDepositViaPartner): Promise { assertAddress(props.vaultAddress, 'vaultAddress'); + assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); return await handleTx(props, { @@ -191,10 +195,11 @@ export async function depositViaPartner(props: TDepositViaPartner): Promise { assert(props.connector, 'No connector'); + assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); const chainID = await props.connector.getChainId(); switch (chainID) { From 23275d2088716afa4a00bd08df27640bba498c43 Mon Sep 17 00:00:00 2001 From: Major Date: Fri, 2 Jun 2023 10:08:26 +0200 Subject: [PATCH 05/10] fix: remove stupid suggestion --- apps/common/utils/actions.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/common/utils/actions.tsx b/apps/common/utils/actions.tsx index d8348a039..33f12587a 100755 --- a/apps/common/utils/actions.tsx +++ b/apps/common/utils/actions.tsx @@ -68,10 +68,9 @@ export async function approvedERC20Amount( ******************************************************************************/ type TApproveERC20 = TWriteTransaction & { spenderAddress: TAddress | undefined; - amount: bigint | undefined; + amount: bigint; }; export async function approveERC20(props: TApproveERC20): Promise { - assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assertAddress(props.spenderAddress, 'spenderAddress'); try { @@ -103,10 +102,9 @@ export async function approveERC20(props: TApproveERC20): Promise { ** @param amount - The amount of ETH to deposit. ******************************************************************************/ type TDeposit = TWriteTransaction & { - amount: bigint | undefined; + amount: bigint; }; export async function deposit(props: TDeposit): Promise { - assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); return await handleTx(props, { @@ -126,11 +124,10 @@ export async function deposit(props: TDeposit): Promise { ** @param amount - The amount of collateral to deposit. ******************************************************************************/ type TDepositEth = TWriteTransaction & { - amount: bigint | undefined; + amount: bigint; }; export async function depositETH(props: TDepositEth): Promise { assert(props.connector, 'No connector'); - assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); const chainID = await props.connector.getChainId(); switch (chainID) { @@ -167,11 +164,10 @@ export async function depositETH(props: TDepositEth): Promise { type TDepositViaPartner = TWriteTransaction & { vaultAddress: TAddress | undefined; partnerAddress: TAddress | undefined; - amount: bigint | undefined; + amount: bigint; }; export async function depositViaPartner(props: TDepositViaPartner): Promise { assertAddress(props.vaultAddress, 'vaultAddress'); - assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); return await handleTx(props, { @@ -195,11 +191,10 @@ export async function depositViaPartner(props: TDepositViaPartner): Promise { assert(props.connector, 'No connector'); - assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); const chainID = await props.connector.getChainId(); switch (chainID) { @@ -233,10 +228,9 @@ export async function withdrawETH(props: TWithdrawEth): Promise { ** @param amount - The amount of ETH to withdraw. ******************************************************************************/ type TWithdrawShares = TWriteTransaction & { - amount: bigint | undefined; + amount: bigint; }; export async function withdrawShares(props: TWithdrawShares): Promise { - assert(typeof props.amount === 'bigint', 'Amount is not a bigint'); assert(props.amount > 0n, 'Amount is 0'); return await handleTx(props, { From 72c40f54b61a31eb9d59db6c8ec47132e5cce6a9 Mon Sep 17 00:00:00 2001 From: Major Date: Fri, 2 Jun 2023 10:29:30 +0200 Subject: [PATCH 06/10] feat: migrate actions --- apps/vaults/utils/actions.ts | 241 +++++------------------- apps/veyfi/components/ClaimTab.tsx | 4 +- apps/veyfi/components/LockTab.tsx | 6 +- apps/veyfi/components/ManageLockTab.tsx | 6 +- apps/veyfi/utils/actions.ts | 240 ++++++++--------------- apps/ybal/utils/actions.ts | 71 +++---- apps/ybribe/utils/actions.ts | 128 ++----------- 7 files changed, 167 insertions(+), 529 deletions(-) diff --git a/apps/vaults/utils/actions.ts b/apps/vaults/utils/actions.ts index 6d656dcf8..b0bddf859 100644 --- a/apps/vaults/utils/actions.ts +++ b/apps/vaults/utils/actions.ts @@ -1,17 +1,14 @@ -import {captureException} from '@sentry/nextjs'; import STAKING_REWARDS_ABI from '@vaults/utils/abi/stakingRewards.abi'; import STAKING_REWARDS_ZAP_ABI from '@vaults/utils/abi/stakingRewardsZap.abi'; import VAULT_FACTORY_ABI from '@vaults/utils/abi/vaultFactory.abi'; import ZAP_VE_CRV_ABI from '@vaults/utils/abi/zapVeCRV.abi'; -import {prepareWriteContract, waitForTransaction, writeContract} from '@wagmi/core'; +import {prepareWriteContract} from '@wagmi/core'; import {} from '@yearn-finance/web-lib/utils/address'; import {STAKING_REWARDS_ZAP_ADDRESS, VAULT_FACTORY_ADDRESS, ZAP_YEARN_VE_CRV_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; import {toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber'; -import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; import {assert} from '@common/utils/assert'; -import {assertAddress, toWagmiProvider} from '@common/utils/toWagmiProvider'; +import {assertAddress, handleTx, toWagmiProvider} from '@common/utils/toWagmiProvider'; -import type {BaseError} from 'viem'; 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/toWagmiProvider'; @@ -29,41 +26,16 @@ type TDepositAndStake = TWriteTransaction & { amount: bigint; }; export async function depositAndStake(props: TDepositAndStake): Promise { - assertAddress(props.contractAddress, 'contractAddress'); + assertAddress(STAKING_REWARDS_ZAP_ADDRESS, 'STAKING_REWARDS_ZAP_ADDRESS'); assertAddress(props.vaultAddress, 'vaultAddress'); assert(props.amount > 0n, 'Amount is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: STAKING_REWARDS_ZAP_ADDRESS, - abi: STAKING_REWARDS_ZAP_ABI, - functionName: 'zapIn', - args: [props.vaultAddress, props.amount] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } - + return await handleTx(props, { + address: STAKING_REWARDS_ZAP_ADDRESS, + abi: STAKING_REWARDS_ZAP_ABI, + functionName: 'zapIn', + args: [props.vaultAddress, props.amount] + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -77,39 +49,14 @@ type TStake = TWriteTransaction & { amount: bigint; }; export async function stake(props: TStake): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assert(props.amount > 0n, 'Amount is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: STAKING_REWARDS_ABI, - functionName: 'stake', - args: [props.amount] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: STAKING_REWARDS_ABI, + functionName: 'stake', + args: [props.amount] + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -120,37 +67,11 @@ export async function stake(props: TStake): Promise { ******************************************************************************/ type TUnstake = TWriteTransaction; export async function unstake(props: TUnstake): Promise { - assertAddress(props.contractAddress, 'contractAddress'); - - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: STAKING_REWARDS_ABI, - functionName: 'exit' - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: STAKING_REWARDS_ABI, + functionName: 'exit' + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -161,37 +82,11 @@ export async function unstake(props: TUnstake): Promise { ******************************************************************************/ type TClaim = TWriteTransaction; export async function claim(props: TClaim): Promise { - assertAddress(props.contractAddress, 'contractAddress'); - - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: STAKING_REWARDS_ABI, - functionName: 'getReward' - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: STAKING_REWARDS_ABI, + functionName: 'getReward' + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -209,41 +104,17 @@ type TVeCRVZap = TWriteTransaction & { amount: bigint; }; export async function veCRVzap(props: TVeCRVZap): Promise { - assertAddress(props.contractAddress, 'contractAddress'); + assertAddress(ZAP_YEARN_VE_CRV_ADDRESS, 'ZAP_YEARN_VE_CRV_ADDRESS'); assertAddress(props.inputToken, 'inputToken'); assertAddress(props.outputToken, 'outputToken'); assert(props.amount > 0n, 'Amount must be greater than 0n'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: ZAP_YEARN_VE_CRV_ADDRESS, - abi: ZAP_VE_CRV_ABI, - functionName: 'zap', - args: [props.inputToken, props.outputToken, props.amount] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: ZAP_YEARN_VE_CRV_ADDRESS, + abi: ZAP_VE_CRV_ABI, + functionName: 'zap', + args: [props.inputToken, props.outputToken, props.amount] + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -257,54 +128,30 @@ type TCreateNewVaultsAndStrategies = TWriteTransaction & { gaugeAddress: TAddress | undefined; }; export async function createNewVaultsAndStrategies(props: TCreateNewVaultsAndStrategies): Promise { - assertAddress(props.contractAddress, 'contractAddress'); + assertAddress(VAULT_FACTORY_ADDRESS, 'VAULT_FACTORY_ADDRESS'); assertAddress(props.gaugeAddress, 'gaugeAddress'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: VAULT_FACTORY_ADDRESS, - abi: VAULT_FACTORY_ABI, - functionName: 'createNewVaultsAndStrategies', - args: [props.gaugeAddress] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: VAULT_FACTORY_ADDRESS, + abi: VAULT_FACTORY_ABI, + functionName: 'createNewVaultsAndStrategies', + args: [props.gaugeAddress] + }); } /* 🔵 - Yearn Finance ********************************************************** -** gasOfCreateNewVaultsAndStrategies is a _READ function that estimate the gas +** gasOfCreateNewVaultsAndStrategies is a _READ_ function that estimate the gas ** of the createNewVaultsAndStrategies function. ** ** @app - Vaults (veCRV) ** @param gaugeAddress - the base gauge address ******************************************************************************/ export async function gasOfCreateNewVaultsAndStrategies(props: TCreateNewVaultsAndStrategies): Promise { - assertAddress(props.contractAddress, 'contractAddress'); - assertAddress(props.gaugeAddress, 'gaugeAddress'); - - const wagmiProvider = await toWagmiProvider(props.connector); try { + assertAddress(props.contractAddress, 'contractAddress'); + assertAddress(props.gaugeAddress, 'gaugeAddress'); + + const wagmiProvider = await toWagmiProvider(props.connector); const config = await prepareWriteContract({ ...wagmiProvider, address: VAULT_FACTORY_ADDRESS, diff --git a/apps/veyfi/components/ClaimTab.tsx b/apps/veyfi/components/ClaimTab.tsx index 599684d2e..0b77a0031 100644 --- a/apps/veyfi/components/ClaimTab.tsx +++ b/apps/veyfi/components/ClaimTab.tsx @@ -1,7 +1,7 @@ import {useCallback, useState} from 'react'; import {formatUnits} from 'viem'; import {useVotingEscrow} from '@veYFI/contexts/useVotingEscrow'; -import {withdrawUnlocked} from '@veYFI/utils/actions'; +import {withdrawUnlockedVeYFI} from '@veYFI/utils/actions'; import {validateNetwork} from '@veYFI/utils/validations'; import {Button} from '@yearn-finance/web-lib/components/Button'; import {useWeb3} from '@yearn-finance/web-lib/contexts/useWeb3'; @@ -31,7 +31,7 @@ function ClaimTab(): ReactElement { }, [refreshVotingEscrow, refreshBalances]); const onWithdrawUnlocked = useCallback(async (): Promise => { - const result = await withdrawUnlocked({ + const result = await withdrawUnlockedVeYFI({ connector: provider, contractAddress: votingEscrow?.address, statusHandler: set_withdrawUnlockedStatus diff --git a/apps/veyfi/components/LockTab.tsx b/apps/veyfi/components/LockTab.tsx index 36336de73..b10367c33 100644 --- a/apps/veyfi/components/LockTab.tsx +++ b/apps/veyfi/components/LockTab.tsx @@ -2,7 +2,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react'; import {formatUnits} from 'viem'; import {useVotingEscrow} from '@veYFI/contexts/useVotingEscrow'; import {getVotingPower} from '@veYFI/utils'; -import {increaseLockAmount, lock} from '@veYFI/utils/actions'; +import {increaseVeYFILockAmount, lockVeYFI} from '@veYFI/utils/actions'; import {MAX_LOCK_TIME, MIN_LOCK_AMOUNT, MIN_LOCK_TIME} from '@veYFI/utils/constants'; import {validateAllowance, validateAmount, validateNetwork} from '@veYFI/utils/validations'; import {Button} from '@yearn-finance/web-lib/components/Button'; @@ -65,7 +65,7 @@ function LockTab(): ReactElement { }, [lockAmount.raw, provider, refreshData, votingEscrow?.address, votingEscrow?.token]); const onLock = useCallback(async (): Promise => { - const result = await lock({ + const result = await lockVeYFI({ connector: provider, contractAddress: votingEscrow?.address, amount: lockAmount.raw, @@ -78,7 +78,7 @@ function LockTab(): ReactElement { }, [provider, votingEscrow?.address, lockAmount.raw, unlockTime, onTxSuccess]); const onIncreaseLockAmount = useCallback(async (): Promise => { - const result = await increaseLockAmount({ + const result = await increaseVeYFILockAmount({ connector: provider, contractAddress: votingEscrow?.address, amount: lockAmount.raw, diff --git a/apps/veyfi/components/ManageLockTab.tsx b/apps/veyfi/components/ManageLockTab.tsx index 55ebd3d06..726ba5563 100644 --- a/apps/veyfi/components/ManageLockTab.tsx +++ b/apps/veyfi/components/ManageLockTab.tsx @@ -2,7 +2,7 @@ import {useCallback, useMemo, useState} from 'react'; import {formatUnits} from 'viem'; import {useVotingEscrow} from '@veYFI/contexts/useVotingEscrow'; import {getVotingPower} from '@veYFI/utils'; -import {extendLockTime, withdrawLocked} from '@veYFI/utils/actions'; +import {extendVeYFILockTime, withdrawLockedVeYFI} from '@veYFI/utils/actions'; import {MAX_LOCK_TIME, MIN_LOCK_TIME} from '@veYFI/utils/constants'; import {validateAmount, validateNetwork} from '@veYFI/utils/validations'; import {Button} from '@yearn-finance/web-lib/components/Button'; @@ -36,7 +36,7 @@ function ManageLockTab(): ReactElement { }, [refreshBalances, refreshVotingEscrow]); const onExtendLockTime = useCallback(async (): Promise => { - const result = await extendLockTime({ + const result = await extendVeYFILockTime({ connector: provider, contractAddress: votingEscrow?.address, time: toBigInt(toSeconds(newUnlockTime)), @@ -48,7 +48,7 @@ function ManageLockTab(): ReactElement { }, [newUnlockTime, onTxSuccess, provider, votingEscrow?.address]); const onWithdrawLocked = useCallback(async (): Promise => { - const result = await withdrawLocked({ + const result = await withdrawLockedVeYFI({ connector: provider, contractAddress: votingEscrow?.address, statusHandler: set_withdrawLockedStatus diff --git a/apps/veyfi/utils/actions.ts b/apps/veyfi/utils/actions.ts index dcd880670..a6c7c732b 100644 --- a/apps/veyfi/utils/actions.ts +++ b/apps/veyfi/utils/actions.ts @@ -1,238 +1,148 @@ -import {captureException} from '@sentry/nextjs'; import VEYFI_ABI from '@veYFI/utils/abi/veYFI.abi'; -import {prepareWriteContract, waitForTransaction, writeContract} from '@wagmi/core'; +import {prepareWriteContract} from '@wagmi/core'; +import {toAddress} from '@yearn-finance/web-lib/utils/address'; import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; import {assert} from '@common/utils/assert'; -import {assertAddress, toWagmiProvider} from '@common/utils/toWagmiProvider'; +import {assertAddress, handleTx} from '@common/utils/toWagmiProvider'; -import type {BaseError} from 'viem'; import type {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction'; import type {TWriteTransaction} from '@common/utils/toWagmiProvider'; /* 🔵 - Yearn Finance ********************************************************** -** lock is a _WRITE_ function that locks funds in the veYFI contract in +** lockVeYFI is a _WRITE_ function that locks funds in the veYFI contract in ** exchange of some voting power. ** ** @app - veYFI ** @param amount - The amount of the underlying asset to deposit. ** @param time - The amount of time to lock the funds for. ******************************************************************************/ -type TLock = TWriteTransaction & { +type TLockVeYFI = TWriteTransaction & { amount: bigint; time: bigint }; -export async function lock(props: TLock): Promise { - assertAddress(props.contractAddress, 'contractAddress'); +export async function lockVeYFI(props: TLockVeYFI): Promise { + assert(props.connector, 'No connector'); assert(props.time > 0n, 'Time is 0'); assert(props.amount > 0n, 'Amount is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: VEYFI_ABI, - functionName: 'modify_lock', - args: [props.amount, props.time, wagmiProvider.address] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + const signerAddress = await props.connector.getAccount(); + assertAddress(signerAddress, 'signerAddress'); + return await handleTx(props, { + address: props.contractAddress, + abi: VEYFI_ABI, + functionName: 'modify_lock', + args: [props.amount, props.time, signerAddress] + }); } /* 🔵 - Yearn Finance ********************************************************** -** increaseLockAmount is a _WRITE_ function that increases the amount of funds -** locked in the veYFI contract in exchange of some voting power. +** increaseVeYFILockAmount is a _WRITE_ function that increases the amount of +** funds locked in the veYFI contract in exchange of some voting power. ** ** @app - veYFI ** @param amount - The amount of the underlying asset to deposit. ******************************************************************************/ -type TIncreaseLockAmount = TWriteTransaction & { +type TIncreaseVeYFILockAmount = TWriteTransaction & { amount: bigint; }; -export async function increaseLockAmount(props: TIncreaseLockAmount): Promise { - assertAddress(props.contractAddress, 'contractAddress'); +export async function increaseVeYFILockAmount(props: TIncreaseVeYFILockAmount): Promise { + assert(props.connector, 'No connector'); assert(props.amount > 0n, 'Amount is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); + const signerAddress = await props.connector.getAccount(); + assertAddress(signerAddress, 'signerAddress'); - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: VEYFI_ABI, - functionName: 'modify_lock', - args: [props.amount, 0n, wagmiProvider.address] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: props.contractAddress, + abi: VEYFI_ABI, + functionName: 'modify_lock', + args: [props.amount, 0n, signerAddress] + }); } /* 🔵 - Yearn Finance ********************************************************** -** extendLockTime is a _WRITE_ function that increases the amount of time funds +** extendVeYFILockTime is a _WRITE_ function that increases the amount of time funds ** are locked in the veYFI contract in exchange of some voting power. ** ** @app - veYFI ** @param time - The amount of time to lock the funds for. ******************************************************************************/ -type TExtendLockTime = TWriteTransaction & { +type TExtendVeYFILockTime = TWriteTransaction & { time: bigint; }; -export async function extendLockTime(props: TExtendLockTime): Promise { - assertAddress(props.contractAddress, 'contractAddress'); +export async function extendVeYFILockTime(props: TExtendVeYFILockTime): Promise { + assert(props.connector, 'No connector'); assert(props.time > 0n, 'Time is 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); + const signerAddress = await props.connector.getAccount(); + assertAddress(signerAddress, 'signerAddress'); + + return await handleTx(props, { + address: props.contractAddress, + abi: VEYFI_ABI, + functionName: 'modify_lock', + args: [0n, props.time, signerAddress] + }); +} +/* 🔵 - Yearn Finance ********************************************************** +** getVeYFIWithdrawPenalty is a _READ_ function that simulates a withdrawal from +** the veYFI contract and returns the penalty to be paid. +** +** @app - veYFI +******************************************************************************/ +type TGetVeYFIWithdrawPenalty = TWriteTransaction; +export async function getVeYFIWithdrawPenalty(props: TGetVeYFIWithdrawPenalty): Promise { try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, + const {result} = await prepareWriteContract({ + address: toAddress(props.contractAddress), abi: VEYFI_ABI, - functionName: 'modify_lock', - args: [0n, props.time, wagmiProvider.address] + functionName: 'withdraw' }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); + return result.penalty; } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); + return 0n; } } /* 🔵 - Yearn Finance ********************************************************** -** withdrawUnlocked is a _WRITE_ function that withdraws unlocked funds from +** withdrawUnlockedVeYFI is a _WRITE_ function that withdraws unlocked funds from ** the veYFI contract. ** Note: will fail if there is a penalty to be paid. ** ** @app - veYFI ******************************************************************************/ -type TWithdrawUnlocked = TWriteTransaction; -export async function withdrawUnlocked(props: TWithdrawUnlocked): Promise { - assertAddress(props.contractAddress, 'contractAddress'); - +type TWithdrawUnlockedVeYFI = TWriteTransaction; +export async function withdrawUnlockedVeYFI(props: TWithdrawUnlockedVeYFI): Promise { props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: VEYFI_ABI, - functionName: 'withdraw' - }); - if (config.result.penalty > 0n) { - throw new Error('Tokens are not yet unlocked'); - } - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); + const penalty = await getVeYFIWithdrawPenalty(props); + if (penalty > 0n) { props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { setTimeout((): void => { props.statusHandler?.({...defaultTxStatus}); }, 3000); + return ({isSuccessful: false, error: new Error('Tokens are not yet unlocked')}); } + + return await handleTx(props, { + address: props.contractAddress, + abi: VEYFI_ABI, + functionName: 'withdraw' + }); } /* 🔵 - Yearn Finance ********************************************************** -** withdrawLocked is a _WRITE_ function that withdraws locked funds from the +** withdrawLockedVeYFI is a _WRITE_ function that withdraws locked funds from the ** veYFI contract. ** ** @app - veYFI ******************************************************************************/ -type TWithdrawLocked = TWriteTransaction; -export async function withdrawLocked(props: TWithdrawLocked): Promise { - assertAddress(props.contractAddress, 'contractAddress'); - - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: props.contractAddress, - abi: VEYFI_ABI, - functionName: 'withdraw' - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } +type TWithdrawLockedVeYFI = TWriteTransaction; +export async function withdrawLockedVeYFI(props: TWithdrawLockedVeYFI): Promise { + return await handleTx(props, { + address: props.contractAddress, + abi: VEYFI_ABI, + functionName: 'withdraw' + }); } diff --git a/apps/ybal/utils/actions.ts b/apps/ybal/utils/actions.ts index cb270e8d9..c66914b8e 100644 --- a/apps/ybal/utils/actions.ts +++ b/apps/ybal/utils/actions.ts @@ -1,18 +1,15 @@ -import {captureException} from '@sentry/nextjs'; -import {prepareWriteContract, readContract, waitForTransaction, writeContract} from '@wagmi/core'; +import {prepareWriteContract, readContract} from '@wagmi/core'; import {toAddress} from '@yearn-finance/web-lib/utils/address'; import {LPYBAL_TOKEN_ADDRESS, STYBAL_TOKEN_ADDRESS, YBAL_TOKEN_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; import {toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber'; -import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; import {assert} from '@common/utils/assert'; -import {assertAddress, toWagmiProvider} from '@common/utils/toWagmiProvider'; +import {assertAddress, handleTx, toWagmiProvider} from '@common/utils/toWagmiProvider'; -import type {BaseError} from 'viem'; 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/toWagmiProvider'; -const ZAP_ABI = [{'inputs':[{'name':'_input_token', 'type':'address'}, {'name':'_output_token', 'type':'address'}, {'name':'_amount_in', 'type':'uint256'}, {'name':'_min_out', 'type':'uint256'}, {'name':'_recipient', 'type':'address'}, {'name':'_mint', 'type':'bool'}], 'name':'zap', 'outputs':[{'name':'', 'type':'uint256'}], 'stateMutability':'nonpayable', 'type':'function'}, {'inputs':[], 'name':'mint_buffer', 'outputs':[{'name':'', 'type':'uint256'}], 'stateMutability':'view', 'type':'function'}, {'inputs':[{'name':'_input_token', 'type':'address'}, {'name':'_output_token', 'type':'address'}, {'name':'_amount_in', 'type':'uint256'}, {'name':'_mint', 'type':'bool'}], 'name':'queryZapOutput', 'outputs':[{'name':'', 'type':'uint256'}], 'stateMutability':'nonpayable', 'type':'function'}] as const; +const ZAP_BAL_ABI = [{'inputs':[{'name':'_input_token', 'type':'address'}, {'name':'_output_token', 'type':'address'}, {'name':'_amount_in', 'type':'uint256'}, {'name':'_min_out', 'type':'uint256'}, {'name':'_recipient', 'type':'address'}, {'name':'_mint', 'type':'bool'}], 'name':'zap', 'outputs':[{'name':'', 'type':'uint256'}], 'stateMutability':'nonpayable', 'type':'function'}, {'inputs':[], 'name':'mint_buffer', 'outputs':[{'name':'', 'type':'uint256'}], 'stateMutability':'view', 'type':'function'}, {'inputs':[{'name':'_input_token', 'type':'address'}, {'name':'_output_token', 'type':'address'}, {'name':'_amount_in', 'type':'uint256'}, {'name':'_mint', 'type':'bool'}], 'name':'queryZapOutput', 'outputs':[{'name':'', 'type':'uint256'}], 'stateMutability':'nonpayable', 'type':'function'}] as const; const LOCAL_ZAP_YEARN_YBAL_ADDRESS = toAddress('0x43cA9bAe8dF108684E5EAaA720C25e1b32B0A075'); const OUTPUT_TOKENS = [YBAL_TOKEN_ADDRESS, STYBAL_TOKEN_ADDRESS, LPYBAL_TOKEN_ADDRESS]; @@ -29,7 +26,7 @@ export async function simulateZapForMinOut(props: TSimulateZapForMinOut): Promis try { const wagmiProvider = await toWagmiProvider(props.connector); - const baseContract = {...wagmiProvider, address: LOCAL_ZAP_YEARN_YBAL_ADDRESS, abi: ZAP_ABI}; + const baseContract = {...wagmiProvider, address: LOCAL_ZAP_YEARN_YBAL_ADDRESS, abi: ZAP_BAL_ABI}; const {result: expectedAmountMint} = await prepareWriteContract({ ...baseContract, functionName: 'queryZapOutput', @@ -89,52 +86,28 @@ type TZapYBal = TWriteTransaction & { shouldMint: boolean; }; export async function zapBal(props: TZapYBal): Promise { + const minAmountWithSlippage = props.minAmount * (1n - (props.slippage / 100n)); + assert(props.connector, 'No connector'); assertAddress(LOCAL_ZAP_YEARN_YBAL_ADDRESS, 'LOCAL_ZAP_YEARN_YBAL_ADDRESS'); - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.inputToken, 'inputToken'); assertAddress(props.outputToken, 'outputToken'); assert(props.amount > 0n, 'Amount must be greater than 0'); assert(props.minAmount > 0n, 'Min amount must be greater than 0'); - assert(props.minAmount <= props.amount, 'Min amount must be less than amount'); - - const minAmountWithSlippage = props.minAmount * (1n - (props.slippage / 100n)); - assert(props.amount >= minAmountWithSlippage, 'Amount must be greater than min amount with slippage'); + assert(props.amount >= minAmountWithSlippage, 'Amount must be greater or equal to min amount with slippage'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: LOCAL_ZAP_YEARN_YBAL_ADDRESS, - abi: ZAP_ABI, - functionName: 'zap', - args: [ - props.inputToken, - props.outputToken, - props.amount, - minAmountWithSlippage, - wagmiProvider.address, - props.shouldMint - ] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + const userAddress = await props.connector.getAccount(); + assertAddress(userAddress, 'userAddress'); + return await handleTx(props, { + address: LOCAL_ZAP_YEARN_YBAL_ADDRESS, + abi: ZAP_BAL_ABI, + functionName: 'zap', + args: [ + props.inputToken, + props.outputToken, + props.amount, + minAmountWithSlippage, + userAddress, + props.shouldMint + ] + }); } diff --git a/apps/ybribe/utils/actions.ts b/apps/ybribe/utils/actions.ts index a8bb34ca6..ba981e42d 100644 --- a/apps/ybribe/utils/actions.ts +++ b/apps/ybribe/utils/actions.ts @@ -1,14 +1,8 @@ -import {captureException} from '@sentry/nextjs'; -import {prepareWriteContract, waitForTransaction, writeContract} from '@wagmi/core'; -import {CURVE_BRIBE_V2_ADDRESS, CURVE_BRIBE_V3_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; -import {defaultTxStatus} from '@yearn-finance/web-lib/utils/web3/transaction'; +import {CURVE_BRIBE_V3_ADDRESS} from '@yearn-finance/web-lib/utils/constants'; import {assert} from '@common/utils/assert'; -import {assertAddress, toWagmiProvider} from '@common/utils/toWagmiProvider'; -import CURVE_BRIBE_V2_ABI from '@yBribe/utils/abi/curveBribeV2.abi'; +import {assertAddress, handleTx} from '@common/utils/toWagmiProvider'; +import CURVE_BRIBE_V3_ABI from '@yBribe/utils/abi/curveBribeV3.abi'; -import CURVE_BRIBE_V3_ABI from './abi/curveBribeV3.abi'; - -import type {BaseError} from 'viem'; 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/toWagmiProvider'; @@ -16,7 +10,6 @@ import type {TWriteTransaction} from '@common/utils/toWagmiProvider'; /* 🔵 - Yearn Finance ********************************************************** ** claimReward is a _WRITE_ function that claims the rewards from the yBribe ** contract. -** The correct function for V2 or V3 should be used. ** ** @app - yBribe ** @param gaugeAddress - The address of the gauge to claim rewards from. @@ -26,80 +19,21 @@ type TClaimReward = TWriteTransaction & { gaugeAddress: TAddress | undefined; tokenAddress: TAddress | undefined; }; -export async function claimRewardV2(props: TClaimReward): Promise { - assertAddress(CURVE_BRIBE_V2_ADDRESS, 'CURVE_BRIBE_V2_ADDRESS'); - assertAddress(props.contractAddress, 'contractAddress'); - assertAddress(props.gaugeAddress, 'gaugeAddress'); - assertAddress(props.tokenAddress, 'tokenAddress'); - - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: CURVE_BRIBE_V2_ADDRESS, - abi: CURVE_BRIBE_V2_ABI, - functionName: 'claim_reward', - args: [wagmiProvider.address, props.gaugeAddress, props.tokenAddress] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } -} - export async function claimRewardV3(props: TClaimReward): Promise { + assert(props.connector, 'No connector'); assertAddress(CURVE_BRIBE_V3_ADDRESS, 'CURVE_BRIBE_V3_ADDRESS'); - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.gaugeAddress, 'gaugeAddress'); assertAddress(props.tokenAddress, 'tokenAddress'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); + const signerAddress = await props.connector.getAccount(); + assertAddress(signerAddress, 'signerAddress'); - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: CURVE_BRIBE_V3_ADDRESS, - abi: CURVE_BRIBE_V3_ABI, - functionName: 'claim_reward_for', - args: [wagmiProvider.address, props.gaugeAddress, props.tokenAddress] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } + return await handleTx(props, { + address: CURVE_BRIBE_V3_ADDRESS, + abi: CURVE_BRIBE_V3_ABI, + functionName: 'claim_reward_for', + args: [signerAddress, props.gaugeAddress, props.tokenAddress] + }); } @@ -119,40 +53,14 @@ type TAddReward = TWriteTransaction & { }; export async function addReward(props: TAddReward): Promise { assertAddress(CURVE_BRIBE_V3_ADDRESS, 'CURVE_BRIBE_V3_ADDRESS'); - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.gaugeAddress, 'gaugeAddress'); assertAddress(props.tokenAddress, 'tokenAddress'); assert(props.amount > 0n, 'Amount must be greater than 0'); - props.statusHandler?.({...defaultTxStatus, pending: true}); - const wagmiProvider = await toWagmiProvider(props.connector); - - try { - const config = await prepareWriteContract({ - ...wagmiProvider, - address: CURVE_BRIBE_V3_ADDRESS, - abi: CURVE_BRIBE_V3_ABI, - functionName: 'add_reward_amount', - args: [props.gaugeAddress, props.tokenAddress, props.amount] - }); - const {hash} = await writeContract(config.request); - const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); - if (receipt.status === 'success') { - props.statusHandler?.({...defaultTxStatus, success: true}); - } else if (receipt.status === 'reverted') { - props.statusHandler?.({...defaultTxStatus, error: true}); - } - return ({isSuccessful: receipt.status === 'success', receipt}); - } catch (error) { - console.error(error); - const errorAsBaseError = error as BaseError; - captureException(errorAsBaseError); - props.statusHandler?.({...defaultTxStatus, error: true}); - return ({isSuccessful: false, error: errorAsBaseError || ''}); - } finally { - setTimeout((): void => { - props.statusHandler?.({...defaultTxStatus}); - }, 3000); - } - + return await handleTx(props, { + address: CURVE_BRIBE_V3_ADDRESS, + abi: CURVE_BRIBE_V3_ABI, + functionName: 'add_reward_amount', + args: [props.gaugeAddress, props.tokenAddress, props.amount] + }); } From 00c3d2242b0dcae4e176fba9c99eb98d598c0895 Mon Sep 17 00:00:00 2001 From: Karelian Pie Date: Fri, 2 Jun 2023 12:39:08 +0300 Subject: [PATCH 07/10] fix: Types --- apps/common/utils/toWagmiProvider.tsx | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/apps/common/utils/toWagmiProvider.tsx b/apps/common/utils/toWagmiProvider.tsx index 5dfa37b20..9bcc3d616 100644 --- a/apps/common/utils/toWagmiProvider.tsx +++ b/apps/common/utils/toWagmiProvider.tsx @@ -6,11 +6,11 @@ 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, BaseError, SimulateContractParameters} from 'viem'; +import type {BaseError} 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'; +import type {GetWalletClientResult} from '@wagmi/core'; export type TWagmiProviderContract = { walletClient: GetWalletClientResult, @@ -43,20 +43,11 @@ export function assertAddress(addr: string | TAddress | undefined, name?: string assert(toAddress(addr) !== ETH_TOKEN_ADDRESS, `${name || 'Address'} is 0xE`); } -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 ->( +type TPrepareWriteContractProp = Parameters[0]; + +export async function handleTx( args: TWriteTransaction, - props: TPrepareWriteContractConfig + props: TPrepareWriteContractProp ): Promise { args.statusHandler?.({...defaultTxStatus, pending: true}); const wagmiProvider = await toWagmiProvider(args.connector); @@ -65,12 +56,12 @@ export async function handleTx< assertAddress(props.address, 'contractAddress'); assertAddress(wagmiProvider.address, 'userAddress'); try { - const config = await prepareWriteContract({ + const {request} = await prepareWriteContract({ ...wagmiProvider, - ...props as TPrepareWriteContractConfig, + ...props, address: props.address }); - const {hash} = await writeContract(config.request); + const {hash} = await writeContract(request); const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); if (receipt.status === 'success') { args.statusHandler?.({...defaultTxStatus, success: true}); From e6894fedf4ccecfa33e1234d89baa29e07c289c6 Mon Sep 17 00:00:00 2001 From: Karelian Pie Date: Fri, 2 Jun 2023 13:03:38 +0300 Subject: [PATCH 08/10] fix: Cannot set bigint to type undefined --- apps/common/utils/actions.tsx | 5 +++++ apps/common/utils/toWagmiProvider.tsx | 5 +++-- apps/vaults/utils/actions.ts | 5 +++++ apps/veyfi/utils/actions.ts | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/common/utils/actions.tsx b/apps/common/utils/actions.tsx index 33f12587a..62c46b68e 100755 --- a/apps/common/utils/actions.tsx +++ b/apps/common/utils/actions.tsx @@ -72,6 +72,7 @@ type TApproveERC20 = TWriteTransaction & { }; export async function approveERC20(props: TApproveERC20): Promise { assertAddress(props.spenderAddress, 'spenderAddress'); + assertAddress(props.contractAddress); try { return await handleTx(props, { @@ -106,6 +107,7 @@ type TDeposit = TWriteTransaction & { }; export async function deposit(props: TDeposit): Promise { assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); return await handleTx(props, { address: props.contractAddress, @@ -169,6 +171,7 @@ type TDepositViaPartner = TWriteTransaction & { export async function depositViaPartner(props: TDepositViaPartner): Promise { assertAddress(props.vaultAddress, 'vaultAddress'); assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); return await handleTx(props, { address: props.contractAddress, @@ -232,6 +235,7 @@ type TWithdrawShares = TWriteTransaction & { }; export async function withdrawShares(props: TWithdrawShares): Promise { assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); return await handleTx(props, { address: props.contractAddress, @@ -256,6 +260,7 @@ type TMigrateShares = TWriteTransaction & { export async function migrateShares(props: TMigrateShares): Promise { assertAddress(props.fromVault, 'fromVault'); assertAddress(props.toVault, 'toVault'); + assertAddress(props.contractAddress); return await handleTx(props, { address: props.contractAddress, diff --git a/apps/common/utils/toWagmiProvider.tsx b/apps/common/utils/toWagmiProvider.tsx index 9bcc3d616..981b92135 100644 --- a/apps/common/utils/toWagmiProvider.tsx +++ b/apps/common/utils/toWagmiProvider.tsx @@ -47,7 +47,7 @@ type TPrepareWriteContractProp = Parameters[0]; export async function handleTx( args: TWriteTransaction, - props: TPrepareWriteContractProp + props: Omit & { value?: bigint; } ): Promise { args.statusHandler?.({...defaultTxStatus, pending: true}); const wagmiProvider = await toWagmiProvider(args.connector); @@ -59,7 +59,8 @@ export async function handleTx( const {request} = await prepareWriteContract({ ...wagmiProvider, ...props, - address: props.address + address: props.address, + value: undefined }); const {hash} = await writeContract(request); const receipt = await waitForTransaction({chainId: wagmiProvider.chainId, hash}); diff --git a/apps/vaults/utils/actions.ts b/apps/vaults/utils/actions.ts index b0bddf859..4611019f4 100644 --- a/apps/vaults/utils/actions.ts +++ b/apps/vaults/utils/actions.ts @@ -50,6 +50,7 @@ type TStake = TWriteTransaction & { }; export async function stake(props: TStake): Promise { assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); return await handleTx(props, { address: props.contractAddress, @@ -67,6 +68,8 @@ export async function stake(props: TStake): Promise { ******************************************************************************/ type TUnstake = TWriteTransaction; export async function unstake(props: TUnstake): Promise { + assertAddress(props.contractAddress); + return await handleTx(props, { address: props.contractAddress, abi: STAKING_REWARDS_ABI, @@ -82,6 +85,8 @@ export async function unstake(props: TUnstake): Promise { ******************************************************************************/ type TClaim = TWriteTransaction; export async function claim(props: TClaim): Promise { + assertAddress(props.contractAddress); + return await handleTx(props, { address: props.contractAddress, abi: STAKING_REWARDS_ABI, diff --git a/apps/veyfi/utils/actions.ts b/apps/veyfi/utils/actions.ts index a6c7c732b..f8ae06ba6 100644 --- a/apps/veyfi/utils/actions.ts +++ b/apps/veyfi/utils/actions.ts @@ -24,6 +24,7 @@ export async function lockVeYFI(props: TLockVeYFI): Promise { assert(props.connector, 'No connector'); assert(props.time > 0n, 'Time is 0'); assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); const signerAddress = await props.connector.getAccount(); assertAddress(signerAddress, 'signerAddress'); @@ -49,6 +50,7 @@ type TIncreaseVeYFILockAmount = TWriteTransaction & { export async function increaseVeYFILockAmount(props: TIncreaseVeYFILockAmount): Promise { assert(props.connector, 'No connector'); assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); const signerAddress = await props.connector.getAccount(); assertAddress(signerAddress, 'signerAddress'); @@ -74,6 +76,7 @@ type TExtendVeYFILockTime = TWriteTransaction & { export async function extendVeYFILockTime(props: TExtendVeYFILockTime): Promise { assert(props.connector, 'No connector'); assert(props.time > 0n, 'Time is 0'); + assertAddress(props.contractAddress); const signerAddress = await props.connector.getAccount(); assertAddress(signerAddress, 'signerAddress'); @@ -115,6 +118,8 @@ export async function getVeYFIWithdrawPenalty(props: TGetVeYFIWithdrawPenalty): ******************************************************************************/ type TWithdrawUnlockedVeYFI = TWriteTransaction; export async function withdrawUnlockedVeYFI(props: TWithdrawUnlockedVeYFI): Promise { + assertAddress(props.contractAddress); + props.statusHandler?.({...defaultTxStatus, pending: true}); const penalty = await getVeYFIWithdrawPenalty(props); if (penalty > 0n) { @@ -140,6 +145,7 @@ export async function withdrawUnlockedVeYFI(props: TWithdrawUnlockedVeYFI): Prom ******************************************************************************/ type TWithdrawLockedVeYFI = TWriteTransaction; export async function withdrawLockedVeYFI(props: TWithdrawLockedVeYFI): Promise { + assertAddress(props.contractAddress); return await handleTx(props, { address: props.contractAddress, abi: VEYFI_ABI, From 61e4a2b956dffdad7d4a68de0d6a2bb9c0dcbd3a Mon Sep 17 00:00:00 2001 From: Major <90963895+Majorfi@users.noreply.github.com> Date: Fri, 2 Jun 2023 12:20:38 +0200 Subject: [PATCH 09/10] Update apps/common/utils/toWagmiProvider.tsx --- apps/common/utils/toWagmiProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/common/utils/toWagmiProvider.tsx b/apps/common/utils/toWagmiProvider.tsx index 981b92135..d4a8be929 100644 --- a/apps/common/utils/toWagmiProvider.tsx +++ b/apps/common/utils/toWagmiProvider.tsx @@ -52,7 +52,6 @@ export async function handleTx( args.statusHandler?.({...defaultTxStatus, pending: true}); const wagmiProvider = await toWagmiProvider(args.connector); - //Some extra assertions assertAddress(props.address, 'contractAddress'); assertAddress(wagmiProvider.address, 'userAddress'); try { From 2f90128141b9a0c1427f1d1d9a036c329ee95122 Mon Sep 17 00:00:00 2001 From: Major <90963895+Majorfi@users.noreply.github.com> Date: Fri, 2 Jun 2023 14:25:31 +0200 Subject: [PATCH 10/10] fix: remove useless toString() (#223) --- apps/vaults/contexts/useSolver.tsx | 2 +- apps/vaults/hooks/useSolverCowswap.ts | 8 ++++---- apps/vaults/hooks/useSolverPortals.ts | 8 ++++---- apps/veyfi/components/ManageLockTab.tsx | 2 +- pages/vaults/index.tsx | 4 ++-- pages/veyfi/index.tsx | 4 ++-- pages/ybal/index.tsx | 4 ++-- pages/ycrv/index.tsx | 18 +++++++++--------- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/apps/vaults/contexts/useSolver.tsx b/apps/vaults/contexts/useSolver.tsx index 6603c2565..182257cc4 100644 --- a/apps/vaults/contexts/useSolver.tsx +++ b/apps/vaults/contexts/useSolver.tsx @@ -89,7 +89,7 @@ function WithSolverContextApp({children}: { children: React.ReactElement }): Rea if (currentNonce !== executionNonce.current) { return; } - const requestHash = await hash(serialize({...request, solver, expectedOut: quote.value.raw.toString()})); + const requestHash = await hash(serialize({...request, solver, expectedOut: quote.value.raw})); performBatchedUpdates((): void => { set_currentSolverState({...ctx, quote: quote.value, hash: requestHash}); set_isLoading(false); diff --git a/apps/vaults/hooks/useSolverCowswap.ts b/apps/vaults/hooks/useSolverCowswap.ts index d2a72a5b0..f3622ce57 100644 --- a/apps/vaults/hooks/useSolverCowswap.ts +++ b/apps/vaults/hooks/useSolverCowswap.ts @@ -35,7 +35,7 @@ async function getQuote( kind: OrderQuoteSide.kind.SELL, // always sell partiallyFillable: false, // always false validTo: 0, - sellAmountBeforeFee: toBigInt(request?.inputAmount || 0).toString() // amount to sell, in wei + sellAmountBeforeFee: toBigInt(request?.inputAmount).toString() // amount to sell, in wei }; if (isZeroAddress(quoteRequest.from)) { @@ -253,7 +253,7 @@ export function useSolverCowswap(): TSolverContext { } return ( toNormalizedBN( - toBigInt(latestQuote?.current?.quote?.buyAmount.toString()), + toBigInt(latestQuote?.current?.quote?.buyAmount), request?.current?.outputToken?.decimals || 18 ) ); @@ -308,7 +308,7 @@ export function useSolverCowswap(): TSolverContext { provider, request.current.inputToken.value, //token to approve SOLVER_COW_VAULT_RELAYER_ADDRESS, //Cowswap relayer - toBigInt(amount.toString()) + toBigInt(amount) ); if (isApproved) { return onSuccess(); @@ -317,7 +317,7 @@ export function useSolverCowswap(): TSolverContext { connector: provider, contractAddress: request.current.inputToken.value, spenderAddress: SOLVER_COW_VAULT_RELAYER_ADDRESS, - amount: toBigInt(amount.toString()), + amount: toBigInt(amount), statusHandler: txStatusSetter }); if (result.isSuccessful) { diff --git a/apps/vaults/hooks/useSolverPortals.ts b/apps/vaults/hooks/useSolverPortals.ts index bb4138e8e..9f41d3f64 100644 --- a/apps/vaults/hooks/useSolverPortals.ts +++ b/apps/vaults/hooks/useSolverPortals.ts @@ -35,7 +35,7 @@ async function getQuote( ): Promise<{data: TPortalEstimate | undefined, error: Error | undefined}> { const params = { sellToken: toAddress(request.inputToken.value), - sellAmount: toBigInt(request.inputAmount || 0).toString(), + sellAmount: toBigInt(request.inputAmount).toString(), buyToken: toAddress(request.outputToken.value), slippagePercentage: String(zapSlippage / 100) }; @@ -152,7 +152,7 @@ export function useSolverPortals(): TSolverContext { params: { takerAddress: toAddress(address), sellToken: toAddress(request.current.inputToken.value), - sellAmount: toBigInt(request.current.inputAmount || 0).toString(), + sellAmount: toBigInt(request.current.inputAmount).toString(), buyToken: toAddress(request.current.outputToken.value), slippagePercentage: String(zapSlippage / 100), validate: true @@ -220,7 +220,7 @@ export function useSolverPortals(): TSolverContext { params: { takerAddress: toAddress(request.current.from), sellToken: toAddress(request.current.inputToken.value), - sellAmount:toBigInt(request.current.inputAmount || 0).toString(), + sellAmount:toBigInt(request.current.inputAmount).toString(), buyToken: toAddress(request.current.outputToken.value) } }); @@ -261,7 +261,7 @@ export function useSolverPortals(): TSolverContext { params: { takerAddress: toAddress(request.current.from), sellToken: toAddress(request.current.inputToken.value), - sellAmount:toBigInt(request.current.inputAmount || 0).toString(), + sellAmount:toBigInt(request.current.inputAmount).toString(), buyToken: toAddress(request.current.outputToken.value) } }); diff --git a/apps/veyfi/components/ManageLockTab.tsx b/apps/veyfi/components/ManageLockTab.tsx index 726ba5563..0a8de5222 100644 --- a/apps/veyfi/components/ManageLockTab.tsx +++ b/apps/veyfi/components/ManageLockTab.tsx @@ -136,7 +136,7 @@ function ManageLockTab(): ReactElement {