diff --git a/apps/common/utils/actions.tsx b/apps/common/utils/actions.tsx index 8cd579b23..62c46b68e 100755 --- a/apps/common/utils/actions.tsx +++ b/apps/common/utils/actions.tsx @@ -1,26 +1,23 @@ -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 {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, toWagmiProvider} from '@common/utils/toWagmiProvider'; +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'; 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 @@ -74,60 +71,28 @@ type TApproveERC20 = TWriteTransaction & { amount: bigint; }; export async function approveERC20(props: TApproveERC20): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assertAddress(props.spenderAddress, 'spenderAddress'); + assertAddress(props.contractAddress); - 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 +106,15 @@ type TDeposit = TWriteTransaction & { amount: bigint; }; export async function deposit(props: TDeposit): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); - 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 ********************************************************** @@ -188,53 +129,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); } } @@ -252,44 +169,20 @@ 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'); + assertAddress(props.contractAddress); - 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 ********************************************************** @@ -304,53 +197,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); } } @@ -365,38 +234,15 @@ type TWithdrawShares = TWriteTransaction & { amount: bigint; }; export async function withdrawShares(props: TWithdrawShares): Promise { - assertAddress(props.contractAddress, 'contractAddress'); assert(props.amount > 0n, 'Amount is 0'); + assertAddress(props.contractAddress); - 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 +258,14 @@ 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'); + assertAddress(props.contractAddress); - 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..d4a8be929 100644 --- a/apps/common/utils/toWagmiProvider.tsx +++ b/apps/common/utils/toWagmiProvider.tsx @@ -1,11 +1,15 @@ +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 {BaseError} 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 {TTxResponse} from '@yearn-finance/web-lib/utils/web3/transaction'; import type {GetWalletClientResult} from '@wagmi/core'; export type TWagmiProviderContract = { @@ -38,3 +42,44 @@ 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 TPrepareWriteContractProp = Parameters[0]; + +export async function handleTx( + args: TWriteTransaction, + props: Omit & { value?: bigint; } +): Promise { + args.statusHandler?.({...defaultTxStatus, pending: true}); + const wagmiProvider = await toWagmiProvider(args.connector); + + assertAddress(props.address, 'contractAddress'); + assertAddress(wagmiProvider.address, 'userAddress'); + try { + const {request} = await prepareWriteContract({ + ...wagmiProvider, + ...props, + address: props.address, + value: undefined + }); + const {hash} = await writeContract(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/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/vaults/utils/actions.ts b/apps/vaults/utils/actions.ts index 6d656dcf8..4611019f4 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,15 @@ 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); - } + assertAddress(props.contractAddress); + + return await handleTx(props, { + address: props.contractAddress, + abi: STAKING_REWARDS_ABI, + functionName: 'stake', + args: [props.amount] + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -120,37 +68,13 @@ 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); + assertAddress(props.contractAddress); - 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 +85,13 @@ 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); - } + assertAddress(props.contractAddress); + + return await handleTx(props, { + address: props.contractAddress, + abi: STAKING_REWARDS_ABI, + functionName: 'getReward' + }); } /* 🔵 - Yearn Finance ********************************************************** @@ -209,41 +109,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 +133,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..0a8de5222 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 @@ -136,7 +136,7 @@ function ManageLockTab(): ReactElement {