From 53bd43e4e964cd95becc283cedae8f5540675e0c Mon Sep 17 00:00:00 2001 From: toniocodo Date: Wed, 4 Oct 2023 12:24:10 +0200 Subject: [PATCH] feat: update operation flow, add notifs and activities --- libs/oeth/redeem/src/hooks.tsx | 80 +++++--- libs/oeth/swap/src/actions/mintVault.ts | 146 +++++--------- libs/oeth/swap/src/actions/swapCurve/index.ts | 106 +++------- libs/oeth/swap/src/actions/swapCurveEth.ts | 70 +++---- libs/oeth/swap/src/actions/swapZapperEth.ts | 108 +++------- .../swap/src/actions/swapZapperSfrxeth.ts | 87 ++------ libs/oeth/swap/src/actions/unwrapWOETH.ts | 36 ++-- libs/oeth/swap/src/actions/wrapOETH.ts | 96 +++------ libs/oeth/swap/src/{hooks.ts => hooks.tsx} | 190 ++++++++++++------ libs/oeth/swap/src/state.ts | 48 +++-- libs/oeth/swap/src/types.ts | 22 +- 11 files changed, 421 insertions(+), 568 deletions(-) rename libs/oeth/swap/src/{hooks.ts => hooks.tsx} (69%) diff --git a/libs/oeth/redeem/src/hooks.tsx b/libs/oeth/redeem/src/hooks.tsx index 4890c9249..99081d424 100644 --- a/libs/oeth/redeem/src/hooks.tsx +++ b/libs/oeth/redeem/src/hooks.tsx @@ -1,12 +1,13 @@ import { useCallback } from 'react'; -import { usePushActivity, useUpdateActivity } from '@origin/oeth/shared'; -import { contracts, tokens } from '@origin/shared/contracts'; import { - BlockExplorerLink, - usePushNotification, - useSlippage, -} from '@origin/shared/providers'; + RedeemNotification, + useDeleteActivity, + usePushActivity, + useUpdateActivity, +} from '@origin/oeth/shared'; +import { contracts, tokens } from '@origin/shared/contracts'; +import { usePushNotification, useSlippage } from '@origin/shared/providers'; import { isNilOrEmpty } from '@origin/shared/utils'; import { prepareWriteContract, @@ -43,6 +44,7 @@ export const useHandleRedeem = () => { const pushNotification = usePushNotification(); const pushActivity = usePushActivity(); const updateActivity = useUpdateActivity(); + const deleteActivity = useDeleteActivity(); const { address } = useAccount(); const [{ amountIn, amountOut }, setRedeemState] = useRedeemState(); const wagmiClient = useQueryClient(); @@ -52,6 +54,14 @@ export const useHandleRedeem = () => { return; } + const minAmountOut = parseUnits( + ( + +formatUnits(amountOut, MIX_TOKEN.decimals) - + +formatUnits(amountOut, MIX_TOKEN.decimals) * slippage + ).toString(), + MIX_TOKEN.decimals, + ); + const activity = pushActivity({ type: 'redeem', status: 'pending', @@ -68,14 +78,6 @@ export const useHandleRedeem = () => { ); try { - const minAmountOut = parseUnits( - ( - +formatUnits(amountOut, MIX_TOKEN.decimals) - - +formatUnits(amountOut, MIX_TOKEN.decimals) * slippage - ).toString(), - MIX_TOKEN.decimals, - ); - const { request } = await prepareWriteContract({ address: contracts.mainnet.OETHVaultCore.address, abi: contracts.mainnet.OETHVaultCore.abi, @@ -83,37 +85,55 @@ export const useHandleRedeem = () => { args: [amountIn, minAmountOut], }); const { hash } = await writeContract(request); + setRedeemState( + produce((draft) => { + draft.isRedeemLoading = false; + }), + ); const txReceipt = await waitForTransaction({ hash }); - - console.log('redeem vault done!'); wagmiClient.invalidateQueries({ queryKey: ['redeem_balance'] }); updateActivity({ ...activity, status: 'success', txReceipt }); pushNotification({ - title: intl.formatMessage({ defaultMessage: 'Redeem complete' }), - severity: 'success', - content: , + content: ( + + ), }); - } catch (e) { - console.error(`redeem vault error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED') { + } catch (error) { + if (error.cause.name === 'UserRejectedRequestError') { + deleteActivity(activity.id); pushNotification({ - title: intl.formatMessage({ defaultMessage: 'Redeem vault' }), + title: intl.formatMessage({ defaultMessage: 'Redeem Cancelled' }), + message: intl.formatMessage({ + defaultMessage: 'User rejected operation', + }), severity: 'info', }); } else { - updateActivity({ ...activity, status: 'error', error: e.short }); + updateActivity({ + ...activity, + status: 'error', + error: error.shortMessage, + }); + pushNotification({ + content: ( + + ), + }); } } - - setRedeemState( - produce((draft) => { - draft.isRedeemLoading = false; - }), - ); }, [ address, amountIn, amountOut, + deleteActivity, intl, pushActivity, pushNotification, diff --git a/libs/oeth/swap/src/actions/mintVault.ts b/libs/oeth/swap/src/actions/mintVault.ts index 331cae331..7b25cd10f 100644 --- a/libs/oeth/swap/src/actions/mintVault.ts +++ b/libs/oeth/swap/src/actions/mintVault.ts @@ -8,7 +8,6 @@ import { prepareWriteContract, readContract, readContracts, - waitForTransaction, writeContract, } from '@wagmi/core'; import { formatUnits, parseUnits } from 'viem'; @@ -84,39 +83,37 @@ const estimateGas: EstimateGas = async ({ return gasEstimate; } catch {} - try { - const [rebaseThreshold, autoAllocateThreshold] = - await queryClient.fetchQuery({ - queryKey: ['vault-info', tokenOut.address], - queryFn: () => - readContracts({ - contracts: [ - { - address: contracts.mainnet.OETHVaultCore.address, - abi: contracts.mainnet.OETHVaultCore.abi, - functionName: 'rebaseThreshold', - }, - { - address: contracts.mainnet.OETHVaultCore.address, - abi: contracts.mainnet.OETHVaultCore.abi, - functionName: 'autoAllocateThreshold', - }, - ], - }), - staleTime: Infinity, - }); - - // TODO check validity - gasEstimate = 220000n; - if (amountIn > autoAllocateThreshold?.result) { - gasEstimate = 2900000n; - } else if (amountIn > rebaseThreshold?.result) { - gasEstimate = 510000n; - } - } catch (e) { - console.error(`mint vault gas estimate error!\n${e.message}`); + const [rebaseThreshold, autoAllocateThreshold] = await queryClient.fetchQuery( + { + queryKey: ['vault-info', tokenOut.address], + queryFn: () => + readContracts({ + contracts: [ + { + address: contracts.mainnet.OETHVaultCore.address, + abi: contracts.mainnet.OETHVaultCore.abi, + functionName: 'rebaseThreshold', + }, + { + address: contracts.mainnet.OETHVaultCore.address, + abi: contracts.mainnet.OETHVaultCore.abi, + functionName: 'autoAllocateThreshold', + }, + ], + }), + staleTime: Infinity, + }, + ); + + gasEstimate = 220000n; + if (amountIn > autoAllocateThreshold?.result) { + gasEstimate = 2900000n; + } else if (amountIn > rebaseThreshold?.result) { + gasEstimate = 510000n; } + console.log(`Mint vault uses fix gas estimate: ${gasEstimate}`); + return gasEstimate; }; @@ -158,7 +155,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({ args: [contracts.mainnet.OETHVaultCore.address, amountIn], account: address, }); - } catch {} + } catch { + console.log(`Mint vault uses fix approval gas estimate: 0`); + } return approvalEstimate; }; @@ -206,35 +205,16 @@ const estimateRoute: EstimateRoute = async ({ }; }; -const approve: Approve = async ({ - tokenIn, - amountIn, - onSuccess, - onError, - onReject, -}) => { - try { - const { request } = await prepareWriteContract({ - address: tokenIn.address, - abi: erc20ABI, - functionName: 'approve', - args: [contracts.mainnet.OETHVaultCore.address, amountIn], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); - - console.log(`mint vault approval done!`); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`mint vault approval error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Mint vault approval'); - } else if (onError) { - await onError('Mint vault approval'); - } - } +const approve: Approve = async ({ tokenIn, amountIn }) => { + const { request } = await prepareWriteContract({ + address: tokenIn.address, + abi: erc20ABI, + functionName: 'approve', + args: [contracts.mainnet.OETHVaultCore.address, amountIn], + }); + const { hash } = await writeContract(request); + + return hash; }; const swap: Swap = async ({ @@ -243,24 +223,17 @@ const swap: Swap = async ({ amountIn, slippage, amountOut, - onSuccess, - onError, - onReject, }) => { const { address } = getAccount(); if (amountIn === 0n || isNilOrEmpty(address)) { - return; + return null; } const approved = await allowance({ tokenIn, tokenOut }); if (approved < amountIn) { - console.error(`mint vault is not approved`); - if (onError) { - await onError('Mint vault is not approved'); - } - return; + throw new Error(`Mint vault is not approved`); } const minAmountOut = parseUnits( @@ -271,28 +244,15 @@ const swap: Swap = async ({ tokenOut.decimals, ); - try { - const { request } = await prepareWriteContract({ - address: contracts.mainnet.OETHVaultCore.address, - abi: contracts.mainnet.OETHVaultCore.abi, - functionName: 'mint', - args: [tokenIn.address, amountIn, minAmountOut], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); - - console.log('mint vault done!'); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`mint vault error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Mint vault swap'); - } else if (onError) { - await onError('Mint vault swap'); - } - } + const { request } = await prepareWriteContract({ + address: contracts.mainnet.OETHVaultCore.address, + abi: contracts.mainnet.OETHVaultCore.abi, + functionName: 'mint', + args: [tokenIn.address, amountIn, minAmountOut], + }); + const { hash } = await writeContract(request); + + return hash; }; export default { diff --git a/libs/oeth/swap/src/actions/swapCurve/index.ts b/libs/oeth/swap/src/actions/swapCurve/index.ts index 680619264..256f53ba1 100644 --- a/libs/oeth/swap/src/actions/swapCurve/index.ts +++ b/libs/oeth/swap/src/actions/swapCurve/index.ts @@ -5,7 +5,6 @@ import { getPublicClient, prepareWriteContract, readContract, - waitForTransaction, writeContract, } from '@wagmi/core'; import { formatUnits, maxUint256, parseUnits } from 'viem'; @@ -35,7 +34,7 @@ const estimateAmount: EstimateAmount = async ({ const curveConfig = curveRoutes[tokenIn.symbol]?.[tokenOut.symbol]; if (isNilOrEmpty(curveConfig)) { - console.error( + throw new Error( `No curve route found, verify exchange mapping ${tokenIn.symbol} -> ${tokenOut.symbol}`, ); } @@ -78,11 +77,9 @@ const estimateGas: EstimateGas = async ({ const curveConfig = curveRoutes[tokenIn.symbol]?.[tokenOut.symbol]; if (isNilOrEmpty(curveConfig)) { - console.error( + throw new Error( `No curve route found, verify exchange mapping ${tokenIn.symbol} -> ${tokenOut.symbol}`, ); - - return gasEstimate; } try { @@ -100,9 +97,7 @@ const estimateGas: EstimateGas = async ({ ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }), }); } catch (e) { - console.error( - `swap curve exchange multiple gas estimate error, returning fix estimate! \n${e.message}`, - ); + console.log(`Swap curve uses fix gas estimate: 350000`); gasEstimate = 350000n; } @@ -152,7 +147,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({ args: [curve.CurveRegistryExchange.address, amountIn], account: address, }); - } catch {} + } catch { + console.log(`Swap curve uses fix approval gas estimate: 0`); + } return approvalEstimate; }; @@ -207,36 +204,16 @@ const estimateRoute: EstimateRoute = async ({ }; }; -const approve: Approve = async ({ - tokenIn, - amountIn, - curve, - onSuccess, - onError, - onReject, -}) => { - try { - const { request } = await prepareWriteContract({ - address: tokenIn.address, - abi: erc20ABI, - functionName: 'approve', - args: [curve.CurveRegistryExchange.address, amountIn], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); +const approve: Approve = async ({ tokenIn, amountIn, curve }) => { + const { request } = await prepareWriteContract({ + address: tokenIn.address, + abi: erc20ABI, + functionName: 'approve', + args: [curve.CurveRegistryExchange.address, amountIn], + }); + const { hash } = await writeContract(request); - console.log(`swap curve exchange multiple approval done!`); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`swap curve exchange multiple approval error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Swap Curve approval'); - } else if (onError) { - await onError('Swap Curve approval'); - } - } + return hash; }; const swap: Swap = async ({ @@ -246,24 +223,17 @@ const swap: Swap = async ({ amountOut, slippage, curve, - onSuccess, - onError, - onReject, }) => { const { address } = getAccount(); if (amountIn === 0n || isNilOrEmpty(address)) { - return; + return null; } const approved = await allowance({ tokenIn, tokenOut, curve }); if (approved < amountIn) { - console.error(`swap curve exchange multiple is not approved`); - if (onError) { - await onError('swap curve exchange multiple is not approved'); - } - return; + throw new Error(`Swap curve is not approved`); } const minAmountOut = parseUnits( @@ -277,43 +247,21 @@ const swap: Swap = async ({ const curveConfig = curveRoutes[tokenIn.symbol]?.[tokenOut?.symbol]; if (isNilOrEmpty(curveConfig)) { - console.error( + throw new Error( `No curve route found, verify exchange mapping ${tokenIn.symbol} -> ${tokenOut.symbol}`, ); - if (onError) { - await onError('No curve route found'); - } - return; } - try { - const { request } = await prepareWriteContract({ - address: curve.CurveRegistryExchange.address, - abi: curve.CurveRegistryExchange.abi, - functionName: 'exchange_multiple', - args: [ - curveConfig.routes, - curveConfig.swapParams, - amountIn, - minAmountOut, - ], - ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }), - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); + const { request } = await prepareWriteContract({ + address: curve.CurveRegistryExchange.address, + abi: curve.CurveRegistryExchange.abi, + functionName: 'exchange_multiple', + args: [curveConfig.routes, curveConfig.swapParams, amountIn, minAmountOut], + ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }), + }); + const { hash } = await writeContract(request); - console.log('swap curve exchange multiple done!'); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`swap curve exchange multiple error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Swap Curve exchange multiple'); - } else if (onError) { - await onError('Swap Curve exchange multiple'); - } - } + return hash; }; export default { diff --git a/libs/oeth/swap/src/actions/swapCurveEth.ts b/libs/oeth/swap/src/actions/swapCurveEth.ts index c38e8fe64..b69776272 100644 --- a/libs/oeth/swap/src/actions/swapCurveEth.ts +++ b/libs/oeth/swap/src/actions/swapCurveEth.ts @@ -5,7 +5,6 @@ import { getPublicClient, prepareWriteContract, readContract, - waitForTransaction, writeContract, } from '@wagmi/core'; import { formatUnits, isAddressEqual, maxUint256, parseUnits } from 'viem'; @@ -93,9 +92,7 @@ const estimateGas: EstimateGas = async ({ account: address ?? ETH_ADDRESS_CURVE, }); } catch (e) { - console.error( - `swap curve OETHPool gas estimate error, returning fix estimate!\n${e.message}`, - ); + console.log(`Swap curve OETH Pool uses fix gas estimate: 180000`); gasEstimate = 180000n; } @@ -110,6 +107,7 @@ const allowance: Allowance = async () => { const estimateApprovalGas: EstimateApprovalGas = async () => { // ETH doesn't need approval + console.log(`Swap curve OETH Pool uses fix approval gas estimate: 0`); return 0n; }; @@ -163,11 +161,9 @@ const estimateRoute: EstimateRoute = async ({ }; }; -const approve: Approve = async ({ onSuccess }) => { +const approve: Approve = async () => { // ETH doesn't need approval - if (onSuccess) { - await onSuccess(null); - } + return null; }; const swap: Swap = async ({ @@ -177,12 +173,9 @@ const swap: Swap = async ({ amountOut, slippage, curve, - onSuccess, - onError, - onReject, }) => { if (amountIn === 0n) { - return; + return null; } const minAmountOut = parseUnits( @@ -193,42 +186,29 @@ const swap: Swap = async ({ tokenOut.decimals, ); - try { - const { request } = await prepareWriteContract({ - address: contracts.mainnet.curveOethPool.address, - abi: contracts.mainnet.curveOethPool.abi, - functionName: 'exchange', - args: [ - BigInt( - curve.OethPoolUnderlyings.findIndex((t) => - isAddressEqual(t, tokenIn.address ?? ETH_ADDRESS_CURVE), - ), + const { request } = await prepareWriteContract({ + address: contracts.mainnet.curveOethPool.address, + abi: contracts.mainnet.curveOethPool.abi, + functionName: 'exchange', + args: [ + BigInt( + curve.OethPoolUnderlyings.findIndex((t) => + isAddressEqual(t, tokenIn.address ?? ETH_ADDRESS_CURVE), ), - BigInt( - curve.OethPoolUnderlyings.findIndex((t) => - isAddressEqual(t, tokenOut.address ?? ETH_ADDRESS_CURVE), - ), + ), + BigInt( + curve.OethPoolUnderlyings.findIndex((t) => + isAddressEqual(t, tokenOut.address ?? ETH_ADDRESS_CURVE), ), - amountIn, - minAmountOut, - ], - ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }), - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); + ), + amountIn, + minAmountOut, + ], + ...(isNilOrEmpty(tokenIn.address) && { value: amountIn }), + }); + const { hash } = await writeContract(request); - console.log('swap curve OETHPool done!'); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`swap curve OETHPool error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Swap Curve exchange'); - } else if (onError) { - await onError('Swap Curve exchange'); - } - } + return hash; }; export default { diff --git a/libs/oeth/swap/src/actions/swapZapperEth.ts b/libs/oeth/swap/src/actions/swapZapperEth.ts index d3799be32..b2499059d 100644 --- a/libs/oeth/swap/src/actions/swapZapperEth.ts +++ b/libs/oeth/swap/src/actions/swapZapperEth.ts @@ -6,7 +6,6 @@ import { getPublicClient, prepareWriteContract, readContract, - waitForTransaction, writeContract, } from '@wagmi/core'; import { formatUnits, maxUint256 } from 'viem'; @@ -26,7 +25,7 @@ const estimateAmount: EstimateAmount = async ({ amountIn }) => { }; const estimateGas: EstimateGas = async ({ amountIn }) => { - let gasEstimate = 200000n; + let gasEstimate = 0n; const { address } = getAccount(); @@ -44,7 +43,10 @@ const estimateGas: EstimateGas = async ({ amountIn }) => { value: amountIn, account: address, }); - } catch {} + } catch { + console.log(`Swap zapper uses fix gas estimate: 200000`); + gasEstimate = 200000n; + } return gasEstimate; }; @@ -97,7 +99,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({ args: [contracts.mainnet.OETHZapper.address, amountIn], account: address, }); - } catch {} + } catch { + console.log(`Swap zapper uses fix approval gas estimate: 0`); + } return approvalEstimate; }; @@ -140,92 +144,44 @@ const estimateRoute: EstimateRoute = async ({ }; }; -const approve: Approve = async ({ - tokenIn, - tokenOut, - amountIn, - onSuccess, - onError, - onReject, -}) => { - if ( - (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) && - onSuccess - ) { - console.log(`swap eth does not require approval!`); - onSuccess(null); +const approve: Approve = async ({ tokenIn, tokenOut, amountIn }) => { + if (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) { + return null; } - try { - const { request } = await prepareWriteContract({ - address: tokenIn.address, - abi: erc20ABI, - functionName: 'approve', - args: [contracts.mainnet.OETHZapper.address, amountIn], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); - - console.log(`swap zapper eth approval done!`); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`swap zapper eth approval error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Swap Zapper ETH approval'); - } else if (onError) { - await onError('Swap Zapper ETH approval'); - } - } + const { request } = await prepareWriteContract({ + address: tokenIn.address, + abi: erc20ABI, + functionName: 'approve', + args: [contracts.mainnet.OETHZapper.address, amountIn], + }); + const { hash } = await writeContract(request); + + return hash; }; -const swap: Swap = async ({ - tokenIn, - tokenOut, - amountIn, - onSuccess, - onError, - onReject, -}) => { +const swap: Swap = async ({ tokenIn, tokenOut, amountIn }) => { const { address } = getAccount(); if (amountIn === 0n || isNilOrEmpty(address)) { - return; + return null; } const approved = await allowance({ tokenIn, tokenOut }); if (approved < amountIn) { - console.error(`swap zapper eth is not approved`); - if (onError) { - await onError('Swap Zapper Eth is not approved'); - } - return; + throw new Error(`Swap zapper is not approved`); } - try { - const { request } = await prepareWriteContract({ - address: contracts.mainnet.OETHZapper.address, - abi: contracts.mainnet.OETHZapper.abi, - functionName: 'deposit', - value: amountIn, - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); - - console.log('swap zapper eth done!'); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`swap zapper eth error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Swap Zapper Eth'); - } else if (onError) { - await onError('Swap Zapper Eth'); - } - } + const { request } = await prepareWriteContract({ + address: contracts.mainnet.OETHZapper.address, + abi: contracts.mainnet.OETHZapper.abi, + functionName: 'deposit', + value: amountIn, + }); + const { hash } = await writeContract(request); + + return hash; }; export default { diff --git a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts index 5d855e988..225b7de42 100644 --- a/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts +++ b/libs/oeth/swap/src/actions/swapZapperSfrxeth.ts @@ -7,7 +7,6 @@ import { prepareWriteContract, readContract, readContracts, - waitForTransaction, writeContract, } from '@wagmi/core'; import { formatUnits, maxUint256, parseUnits } from 'viem'; @@ -54,6 +53,7 @@ const estimateAmount: EstimateAmount = async ({ tokenOut, amountIn }) => { }; const estimateGas: EstimateGas = async () => { + console.log(`Swap zapper sfrxETH uses fix gas estimate: 90000`); return 90000n; }; @@ -106,6 +106,7 @@ const estimateApprovalGas: EstimateApprovalGas = async ({ account: address, }); } catch { + console.log(`Swap zapper sfrxETH uses fix approval gas estimate: 64000`); approvalEstimate = 64000n; } @@ -150,44 +151,20 @@ const estimateRoute: EstimateRoute = async ({ }; }; -const approve: Approve = async ({ - tokenIn, - tokenOut, - amountIn, - onSuccess, - onError, - onReject, -}) => { - if ( - (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) && - onSuccess - ) { - console.log(`swap zapper does not require approval!`); - onSuccess(null); +const approve: Approve = async ({ tokenIn, tokenOut, amountIn }) => { + if (isNilOrEmpty(tokenIn.address) || isNilOrEmpty(tokenOut.address)) { + return null; } - try { - const { request } = await prepareWriteContract({ - address: tokenIn.address, - abi: erc20ABI, - functionName: 'approve', - args: [contracts.mainnet.OETHZapper.address, amountIn], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); + const { request } = await prepareWriteContract({ + address: tokenIn.address, + abi: erc20ABI, + functionName: 'approve', + args: [contracts.mainnet.OETHZapper.address, amountIn], + }); + const { hash } = await writeContract(request); - console.log(`swap zapper approval done!`); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`swap zapper approval error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Swap Zapper approval'); - } else if (onError) { - await onError('Swap Zapper approval'); - } - } + return hash; }; const swap: Swap = async ({ @@ -196,9 +173,6 @@ const swap: Swap = async ({ amountIn, slippage, amountOut, - onSuccess, - onError, - onReject, }) => { const { address } = getAccount(); @@ -209,11 +183,7 @@ const swap: Swap = async ({ const approved = await allowance({ tokenIn, tokenOut }); if (approved < amountIn) { - console.error(`swap zapper is not approved`); - if (onError) { - await onError('Swap Zapper is not approved'); - } - return; + throw new Error(`Swap zapper sfrxETH is not approved`); } const minAmountOut = parseUnits( @@ -224,28 +194,15 @@ const swap: Swap = async ({ tokenOut.decimals, ); - try { - const { request } = await prepareWriteContract({ - address: contracts.mainnet.OETHZapper.address, - abi: contracts.mainnet.OETHZapper.abi, - functionName: 'depositSFRXETH', - args: [amountIn, minAmountOut], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); + const { request } = await prepareWriteContract({ + address: contracts.mainnet.OETHZapper.address, + abi: contracts.mainnet.OETHZapper.abi, + functionName: 'depositSFRXETH', + args: [amountIn, minAmountOut], + }); + const { hash } = await writeContract(request); - console.log('swap zapper sfrxEth done!'); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`swap zapper sfrxEth error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Swap Zapper'); - } else if (onError) { - await onError('Swap Zapper'); - } - } + return hash; }; export default { diff --git a/libs/oeth/swap/src/actions/unwrapWOETH.ts b/libs/oeth/swap/src/actions/unwrapWOETH.ts index 13bfdea9c..863efa020 100644 --- a/libs/oeth/swap/src/actions/unwrapWOETH.ts +++ b/libs/oeth/swap/src/actions/unwrapWOETH.ts @@ -5,7 +5,6 @@ import { getPublicClient, prepareWriteContract, readContract, - waitForTransaction, writeContract, } from '@wagmi/core'; import { formatUnits, maxUint256 } from 'viem'; @@ -68,7 +67,9 @@ const estimateGas: EstimateGas = async ({ amountIn }) => { args: [amountIn, whales.mainnet.WOETH, whales.mainnet.WOETH], account: whales.mainnet.WOETH, }); - } catch {} + } catch { + console.log(`Unwrap WOETH uses fix gas estimate: 0`); + } return gasEstimate; }; @@ -80,6 +81,7 @@ const allowance: Allowance = async () => { const estimateApprovalGas: EstimateApprovalGas = async () => { // Unwrap WOETH does not require approval + console.log(`Unwrap WOETH uses fix gas estimate: 0`); return 0n; }; @@ -121,11 +123,9 @@ const estimateRoute: EstimateRoute = async ({ }; }; -const approve: Approve = async ({ onSuccess }) => { +const approve: Approve = async () => { // Unwrap WOETH does not require approval - if (onSuccess) { - await onSuccess(null); - } + return null; }; const swap: Swap = async ({ amountIn }) => { @@ -135,21 +135,15 @@ const swap: Swap = async ({ amountIn }) => { return; } - try { - const { request } = await prepareWriteContract({ - address: contracts.mainnet.WOETH.address, - abi: contracts.mainnet.WOETH.abi, - functionName: 'redeem', - args: [amountIn, address, address], - }); - const { hash } = await writeContract(request); - await waitForTransaction({ hash }); - // TODO trigger notification - console.log('unwrap woeth done!'); - } catch (e) { - // TODO trigger notification - console.log(`unwrap woeth error!\n${e.message}`); - } + const { request } = await prepareWriteContract({ + address: contracts.mainnet.WOETH.address, + abi: contracts.mainnet.WOETH.abi, + functionName: 'redeem', + args: [amountIn, address, address], + }); + const { hash } = await writeContract(request); + + return hash; }; export default { diff --git a/libs/oeth/swap/src/actions/wrapOETH.ts b/libs/oeth/swap/src/actions/wrapOETH.ts index 075f0e696..869e97e46 100644 --- a/libs/oeth/swap/src/actions/wrapOETH.ts +++ b/libs/oeth/swap/src/actions/wrapOETH.ts @@ -6,7 +6,6 @@ import { getPublicClient, prepareWriteContract, readContract, - waitForTransaction, writeContract, } from '@wagmi/core'; import { formatUnits } from 'viem'; @@ -69,7 +68,9 @@ const estimateGas: EstimateGas = async ({ amountIn }) => { args: [amountIn, whales.mainnet.OETH], account: whales.mainnet.OETH, }); - } catch {} + } catch { + console.log(`Wrap OETH uses fix gas estimate: 0`); + } return gasEstimate; }; @@ -112,7 +113,9 @@ const estimateApprovalGas: EstimateApprovalGas = async ({ args: [contracts.mainnet.WOETH.address, amountIn], account: address, }); - } catch {} + } catch { + console.log(`Wrap OETH uses fix approval gas estimate: 0`); + } return approvalEstimate; }; @@ -155,83 +158,40 @@ const estimateRoute: EstimateRoute = async ({ }; }; -const approve: Approve = async ({ - tokenIn, - amountIn, - onSuccess, - onError, - onReject, -}) => { - try { - const { request } = await prepareWriteContract({ - address: tokenIn.address, - abi: erc20ABI, - functionName: 'approve', - args: [contracts.mainnet.WOETH.address, amountIn], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); - - console.log(`wrap oeth approval done!`); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`wrap oeth approval error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Wrap OETH approval'); - } else if (onError) { - await onError('Wrap OETH approval'); - } - } +const approve: Approve = async ({ tokenIn, amountIn }) => { + const { request } = await prepareWriteContract({ + address: tokenIn.address, + abi: erc20ABI, + functionName: 'approve', + args: [contracts.mainnet.WOETH.address, amountIn], + }); + const { hash } = await writeContract(request); + + return hash; }; -const swap: Swap = async ({ - tokenIn, - tokenOut, - amountIn, - onSuccess, - onError, - onReject, -}) => { +const swap: Swap = async ({ tokenIn, tokenOut, amountIn }) => { const { address } = getAccount(); if (amountIn === 0n || isNilOrEmpty(address)) { - return; + return null; } const approved = await allowance({ tokenIn, tokenOut }); if (approved < amountIn) { - console.error(`wrap oeth is not approved`); - if (onError) { - await onError('Wrap OETH is not approved'); - } - return; + throw new Error(`Wrap OETH is not approved`); } - try { - const { request } = await prepareWriteContract({ - address: contracts.mainnet.WOETH.address, - abi: contracts.mainnet.WOETH.abi, - functionName: 'deposit', - args: [amountIn, address], - }); - const { hash } = await writeContract(request); - const txReceipt = await waitForTransaction({ hash }); - - console.log('wrap oeth done!'); - if (onSuccess) { - await onSuccess(txReceipt); - } - } catch (e) { - console.error(`wrap oeth error!\n${e.message}`); - if (e?.code === 'ACTION_REJECTED' && onReject) { - await onReject('Wrap OETH'); - } else if (onError) { - await onError('Wrap OETH'); - } - } + const { request } = await prepareWriteContract({ + address: contracts.mainnet.WOETH.address, + abi: contracts.mainnet.WOETH.abi, + functionName: 'deposit', + args: [amountIn, address], + }); + const { hash } = await writeContract(request); + + return hash; }; export default { diff --git a/libs/oeth/swap/src/hooks.ts b/libs/oeth/swap/src/hooks.tsx similarity index 69% rename from libs/oeth/swap/src/hooks.ts rename to libs/oeth/swap/src/hooks.tsx index 3cf41aa7b..24985d31a 100644 --- a/libs/oeth/swap/src/hooks.ts +++ b/libs/oeth/swap/src/hooks.tsx @@ -1,6 +1,12 @@ import { useCallback, useMemo } from 'react'; -import { usePushActivity, useUpdateActivity } from '@origin/oeth/shared'; +import { + ApprovalNotification, + SwapNotification, + useDeleteActivity, + usePushActivity, + useUpdateActivity, +} from '@origin/oeth/shared'; import { useCurve, usePushNotification, @@ -8,6 +14,7 @@ import { } from '@origin/shared/providers'; import { isNilOrEmpty } from '@origin/shared/utils'; import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { waitForTransaction } from '@wagmi/core'; import { produce } from 'immer'; import { useIntl } from 'react-intl'; import { useAccount, useQueryClient as useWagmiClient } from 'wagmi'; @@ -162,12 +169,18 @@ export const useSwapRouteAllowance = (route: SwapRoute) => { route?.tokenOut.symbol, route?.action, ], - queryFn: () => - swapActions[route.action].allowance({ - tokenIn: route.tokenIn, - tokenOut: route.tokenOut, - curve, - }), + queryFn: async () => { + let res = 0n; + try { + res = await swapActions[route.action].allowance({ + tokenIn: route.tokenIn, + tokenOut: route.tokenOut, + curve, + }); + } catch {} + + return res; + }, enabled: !isNilOrEmpty(route), placeholderData: 0n, }); @@ -182,7 +195,7 @@ export const useHandleApprove = () => { const pushNotification = usePushNotification(); const pushActivity = usePushActivity(); const updateActivity = useUpdateActivity(); - + const deleteActivity = useDeleteActivity(); const [ { amountIn, amountOut, selectedSwapRoute, tokenIn, tokenOut }, setSwapState, @@ -206,12 +219,20 @@ export const useHandleApprove = () => { amountIn, amountOut, }); - await swapActions[selectedSwapRoute.action].approve({ - tokenIn, - tokenOut, - amountIn, - curve, - onSuccess: (txReceipt) => { + try { + const hash = await swapActions[selectedSwapRoute.action].approve({ + tokenIn, + tokenOut, + amountIn, + curve, + }); + setSwapState( + produce((draft) => { + draft.isApprovalLoading = false; + }), + ); + if (!isNilOrEmpty(hash)) { + const txReceipt = await waitForTransaction({ hash }); wagmiClient.invalidateQueries({ queryKey: ['swap_balance'], }); @@ -219,37 +240,54 @@ export const useHandleApprove = () => { queryKey: ['swap_allowance'], }); updateActivity({ ...activity, status: 'success', txReceipt }); - setSwapState( - produce((draft) => { - draft.isApprovalLoading = false; - }), - ); - }, - onError: (error: string) => { - updateActivity({ ...activity, status: 'error', error }); - setSwapState( - produce((draft) => { - draft.isApprovalLoading = false; - }), - ); - }, - onReject: () => { + pushNotification({ + content: ( + + ), + }); + } + } catch (error) { + setSwapState( + produce((draft) => { + draft.isApprovalLoading = false; + }), + ); + if (error.cause.name === 'UserRejectedRequestError') { + deleteActivity(activity.id); pushNotification({ title: intl.formatMessage({ defaultMessage: 'Approval Cancelled' }), + message: intl.formatMessage({ + defaultMessage: 'User rejected operation', + }), severity: 'info', }); - setSwapState( - produce((draft) => { - draft.isApprovalLoading = false; - }), - ); - }, - }); + } else { + updateActivity({ + ...activity, + status: 'error', + error: error.shortMessage, + }); + pushNotification({ + content: ( + + ), + }); + } + } }, [ address, amountIn, amountOut, curve, + deleteActivity, intl, pushActivity, pushNotification, @@ -273,6 +311,7 @@ export const useHandleSwap = () => { const pushNotification = usePushNotification(); const pushActivity = usePushActivity(); const updateActivity = useUpdateActivity(); + const deleteActivity = useDeleteActivity(); const [ { amountIn, amountOut, selectedSwapRoute, tokenIn, tokenOut }, setSwapState, @@ -296,43 +335,78 @@ export const useHandleSwap = () => { draft.isSwapLoading = true; }), ); - await swapActions[selectedSwapRoute.action].swap({ - tokenIn, - tokenOut, - amountIn, - estimatedRoute: selectedSwapRoute, - slippage, - amountOut, - curve, - onSuccess: (txReceipt) => { + try { + const hash = await swapActions[selectedSwapRoute.action].swap({ + tokenIn, + tokenOut, + amountIn, + estimatedRoute: selectedSwapRoute, + slippage, + amountOut, + curve, + }); + setSwapState( + produce((draft) => { + draft.isSwapLoading = false; + }), + ); + if (!isNilOrEmpty(hash)) { + const txReceipt = await waitForTransaction({ hash }); wagmiClient.invalidateQueries({ queryKey: ['swap_balance'], }); queryClient.invalidateQueries({ queryKey: ['swap_allowance'], }); + pushNotification({ + content: ( + + ), + }); updateActivity({ ...activity, status: 'success', txReceipt }); - }, - onError: (error: string) => { - updateActivity({ ...activity, status: 'error', error }); - }, - onReject: () => { + } + } catch (error) { + setSwapState( + produce((draft) => { + draft.isSwapLoading = false; + }), + ); + if (error.cause.name === 'UserRejectedRequestError') { + deleteActivity(activity.id); pushNotification({ - title: intl.formatMessage({ defaultMessage: 'Swap Cancelled' }), + title: intl.formatMessage({ defaultMessage: 'Operation Cancelled' }), + message: intl.formatMessage({ + defaultMessage: 'User rejected operation', + }), severity: 'info', }); - }, - }); - setSwapState( - produce((draft) => { - draft.isSwapLoading = false; - }), - ); + } else { + updateActivity({ + ...activity, + status: 'error', + error: error.shortMessage, + }); + pushNotification({ + content: ( + + ), + }); + } + } }, [ address, amountIn, amountOut, curve, + deleteActivity, intl, pushActivity, pushNotification, diff --git a/libs/oeth/swap/src/state.ts b/libs/oeth/swap/src/state.ts index 0e60a175f..dfd1b3893 100644 --- a/libs/oeth/swap/src/state.ts +++ b/libs/oeth/swap/src/state.ts @@ -10,7 +10,7 @@ import { createContainer } from 'react-tracked'; import { swapActions } from './actions'; import { getAvailableRoutes } from './utils'; -import type { SwapState } from './types'; +import type { EstimatedSwapRoute, SwapState } from './types'; export const { Provider: SwapProvider, useTracked: useSwapState } = createContainer(() => { @@ -58,19 +58,39 @@ export const { Provider: SwapProvider, useTracked: useSwapState } = slippage, state.amountIn.toString(), ] as const, - queryFn: async () => - swapActions[route.action].estimateRoute({ - tokenIn: route.tokenIn, - tokenOut: route.tokenOut, - amountIn: state.amountIn, - amountOut: state.amountOut, - route, - slippage, - curve: { - CurveRegistryExchange, - OethPoolUnderlyings, - }, - }), + queryFn: async () => { + let res: EstimatedSwapRoute; + try { + res = await swapActions[route.action].estimateRoute({ + tokenIn: route.tokenIn, + tokenOut: route.tokenOut, + amountIn: state.amountIn, + amountOut: state.amountOut, + route, + slippage, + curve: { + CurveRegistryExchange, + OethPoolUnderlyings, + }, + }); + } catch (error) { + console.error( + `Fail to estimate route ${route.action}\n${error.message}`, + ); + res = { + tokenIn: route.tokenIn, + tokenOut: route.tokenOut, + estimatedAmount: 0n, + action: route.action, + allowanceAmount: 0n, + approvalGas: 0n, + gas: 0n, + rate: 0, + }; + } + + return res; + }, }), ), ); diff --git a/libs/oeth/swap/src/types.ts b/libs/oeth/swap/src/types.ts index 4cf5e9db0..4a9f6076b 100644 --- a/libs/oeth/swap/src/types.ts +++ b/libs/oeth/swap/src/types.ts @@ -1,6 +1,5 @@ import type { Contract, Token } from '@origin/shared/contracts'; import type { HexAddress } from '@origin/shared/utils'; -import type { TransactionReceipt } from 'viem'; export type TokenSource = 'tokenIn' | 'tokenOut'; @@ -25,9 +24,6 @@ type Args = { CurveRegistryExchange: Contract; OethPoolUnderlyings: HexAddress[]; }; - onSuccess?: (txReceipt: TransactionReceipt) => void | Promise; - onError?: (msg: string) => void | Promise; - onReject?: (msg: string) => void | Promise; }; export type EstimateAmount = ( @@ -63,17 +59,8 @@ export type EstimateApprovalGas = ( ) => Promise; export type Approve = ( - args: Pick< - Args, - | 'tokenIn' - | 'tokenOut' - | 'amountIn' - | 'curve' - | 'onSuccess' - | 'onError' - | 'onReject' - >, -) => Promise; + args: Pick, +) => Promise; export type Swap = ( args: Pick< @@ -85,11 +72,8 @@ export type Swap = ( | 'slippage' | 'estimatedRoute' | 'curve' - | 'onSuccess' - | 'onError' - | 'onReject' >, -) => Promise; +) => Promise; export type SwapApi = { estimateAmount: EstimateAmount;