From 26832152d50886d558d885204f82c524a5d69d0c Mon Sep 17 00:00:00 2001 From: Nam Nguyen Date: Tue, 15 Aug 2023 21:52:31 +0700 Subject: [PATCH] refactor: handle rejected tx & messages (#2172) --- .../SwapForm/AddMEVProtectionModal.tsx | 10 ++- src/components/SwapForm/SwapModal/index.tsx | 8 +-- .../TransactionConfirmationModal/index.tsx | 6 +- .../WalletPopup/SendToken/index.tsx | 4 +- src/components/swapv2/LimitOrder/helpers.ts | 1 + src/components/swapv2/styleds.tsx | 6 +- src/constants/connectors/utils.ts | 24 +++---- src/hooks/kyberdao/index.tsx | 45 +++++-------- src/hooks/useApproveCallback.ts | 18 ++++- src/hooks/useElasticLegacy.ts | 3 +- src/hooks/web3/useChangeNetwork.ts | 32 ++++----- src/pages/AddLiquidity/TokenPair.tsx | 3 +- src/pages/AddLiquidity/ZapIn.tsx | 3 +- src/pages/AddLiquidityV2/index.tsx | 29 +++++--- src/pages/CreatePool/index.tsx | 5 +- src/pages/ElasticSwap/hooks/index.ts | 3 +- src/pages/IncreaseLiquidity/index.tsx | 3 +- src/pages/MyEarnings/hooks.ts | 3 +- src/pages/RemoveLiquidity/TokenPair.tsx | 5 +- src/pages/RemoveLiquidity/ZapOut.tsx | 9 +-- src/pages/RemoveLiquidityProAmm/index.tsx | 3 +- src/pages/SwapV2/index.tsx | 2 +- src/utils/dmm.ts | 39 ----------- src/utils/errorMessage.ts | 66 ++++++++++++++++--- src/utils/sentry.ts | 14 ++-- 25 files changed, 187 insertions(+), 157 deletions(-) diff --git a/src/components/SwapForm/AddMEVProtectionModal.tsx b/src/components/SwapForm/AddMEVProtectionModal.tsx index dc6ff46402..cefff1d597 100644 --- a/src/components/SwapForm/AddMEVProtectionModal.tsx +++ b/src/components/SwapForm/AddMEVProtectionModal.tsx @@ -1,6 +1,5 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { Trans, t } from '@lingui/macro' -import { Connector } from '@web3-react/types' import { darken } from 'polished' import { useCallback, useState } from 'react' import { X } from 'react-feather' @@ -14,12 +13,12 @@ import { NotificationType } from 'components/Announcement/type' import { ButtonEmpty, ButtonOutlined, ButtonPrimary } from 'components/Button' import Modal from 'components/Modal' import Row, { RowBetween } from 'components/Row' -import { didUserReject } from 'constants/connectors/utils' import { Z_INDEXS } from 'constants/styles' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import { useChangeNetwork } from 'hooks/web3/useChangeNetwork' import { useNotify } from 'state/application/hooks' import { ExternalLink, MEDIA_WIDTHS } from 'theme' +import { friendlyError } from 'utils/errorMessage' const Wrapper = styled.div` padding: 20px; @@ -113,10 +112,9 @@ export default function AddMEVProtectionModal({ isOpen, onClose }: { isOpen: boo onClose?.() mixpanelHandler(MIXPANEL_TYPE.MEV_ADD_RESULT, { type: addingOption.name, result: 'success' }) }, - (connector: Connector, error: Error) => { - let reason = error?.message || 'Unknown reason' - if (didUserReject(connector, error)) reason = 'User rejected' - mixpanelHandler(MIXPANEL_TYPE.MEV_ADD_RESULT, { type: addingOption.name, result: 'fail', reason }) + (error: Error) => { + const message = friendlyError(error) + mixpanelHandler(MIXPANEL_TYPE.MEV_ADD_RESULT, { type: addingOption.name, result: 'fail', reason: message }) }, ) }, [addNewNetwork, notify, onClose, selectedOption, mixpanelHandler]) diff --git a/src/components/SwapForm/SwapModal/index.tsx b/src/components/SwapForm/SwapModal/index.tsx index c8d9d2a569..f162f4a2bf 100644 --- a/src/components/SwapForm/SwapModal/index.tsx +++ b/src/components/SwapForm/SwapModal/index.tsx @@ -30,6 +30,7 @@ type Props = { const SwapModal: React.FC = props => { const { isOpen, tokenAddToMetaMask, onDismiss, swapCallback, buildResult, isBuildingRoute } = props const { chainId, account } = useActiveWeb3React() + const dispatch = useDispatch() // modal and loading const [{ error, isAttemptingTx, txHash }, setSwapState] = useState<{ @@ -48,7 +49,7 @@ const SwapModal: React.FC = props => { const amountOut = currencyOut && CurrencyAmount.fromRawAmount(currencyOut, buildResult?.data?.amountOut || '0') // text to show while loading - const pendingText = `Swapping ${routeSummary?.parsedAmountIn?.toSignificant(6)} ${ + const pendingText = t`Swapping ${routeSummary?.parsedAmountIn?.toSignificant(6)} ${ currencyIn?.symbol } for ${amountOut?.toSignificant(6)} ${currencyOut?.symbol}` @@ -107,9 +108,8 @@ const SwapModal: React.FC = props => { const hash = await swapCallback() handleTxSubmitted(hash) } catch (e) { - if (e?.code !== 4001 && e?.code !== 'ACTION_REJECTED') captureSwapError(e) - const msg = t`Something went wrong. Please try again` - handleError(e.message === '[object Object]' ? msg : e.message || msg) + captureSwapError(e) + handleError(e.message) } } diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index 26b1d9ec1a..cf6ccc8e87 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -22,7 +22,7 @@ import { VIEW_MODE } from 'state/user/reducer' import { ExternalLink } from 'theme' import { CloseIcon } from 'theme/components' import { getEtherscanLink, getTokenLogoURL } from 'utils' -import { errorFriendly } from 'utils/dmm' +import { friendlyError } from 'utils/errorMessage' const Wrapper = styled.div` width: 100%; @@ -280,9 +280,9 @@ export function TransactionErrorContent({ lineHeight={'24px'} style={{ textAlign: 'center', width: '85%' }} > - {errorFriendly(message)} + {friendlyError(message)} - {message !== errorFriendly(message) && ( + {message !== friendlyError(message) && ( ` @@ -142,7 +142,7 @@ export default function SendToken({ setFlowState(state => ({ ...state, attemptingTxn: false, - errorMessage: errorFriendly(error?.message ?? 'Error occur, please try again'), + errorMessage: friendlyError(error), })) } } diff --git a/src/components/swapv2/LimitOrder/helpers.ts b/src/components/swapv2/LimitOrder/helpers.ts index 7bbefc2d0d..0f59adcb8c 100644 --- a/src/components/swapv2/LimitOrder/helpers.ts +++ b/src/components/swapv2/LimitOrder/helpers.ts @@ -132,6 +132,7 @@ export const calcPercentFilledOrder = (value: string, total: string, decimals: n } } +// todo: move to friendlyError export const getErrorMessage = (error: any) => { console.error('Limit order error: ', error) const errorCode: string = error?.response?.data?.code || error.code || '' diff --git a/src/components/swapv2/styleds.tsx b/src/components/swapv2/styleds.tsx index a09fa6b5ec..44b48ad8c8 100644 --- a/src/components/swapv2/styleds.tsx +++ b/src/components/swapv2/styleds.tsx @@ -8,7 +8,7 @@ import { AutoColumn } from 'components/Column' import Modal, { ModalProps } from 'components/Modal' import { Z_INDEXS } from 'constants/styles' import useTheme from 'hooks/useTheme' -import { errorFriendly } from 'utils/dmm' +import { friendlyError } from 'utils/errorMessage' export const PageWrapper = styled.div` display: flex; @@ -136,9 +136,9 @@ export function SwapCallbackError({ error, style = {} }: { error: string; style? - {errorFriendly(error)} + {friendlyError(error)} - {error !== errorFriendly(error) && ( + {error !== friendlyError(error) && ( Boolean(window.ethereum) @@ -51,21 +48,24 @@ export enum ErrorCode { MM_ALREADY_PENDING = -32002, ACTION_REJECTED = 'ACTION_REJECTED', - WC_MODAL_CLOSED = 'Error: User closed modal', - CB_REJECTED_REQUEST = 'Error: User denied account authorization', - ALPHA_WALLET_USER_REJECTED_REQUEST = -32050, + WALLETCONNECT_MODAL_CLOSED = 'Error: User closed modal', + WALLETCONNECT_CANCELED = 'The transaction was cancelled', + COINBASE_REJECTED_REQUEST = 'Error: User denied account authorization', + ALPHA_WALLET_REJECTED_CODE = -32050, ALPHA_WALLET_REJECTED = 'Request rejected', - CANCELED = 'The transaction was cancelled', } -export function didUserReject(connector: Connector, error: any): boolean { +const rejectedPhrases: string[] = ['user rejected transaction', 'user denied transaction', 'you must accept'] + +export function didUserReject(error: any): boolean { return ( error?.code === ErrorCode.USER_REJECTED_REQUEST || error?.code === ErrorCode.ACTION_REJECTED || - error?.code === ErrorCode.ALPHA_WALLET_USER_REJECTED_REQUEST || + error?.code === ErrorCode.ALPHA_WALLET_REJECTED_CODE || error?.message === ErrorCode.ALPHA_WALLET_REJECTED || - (connector === walletConnectV2 && error?.toString?.() === ErrorCode.WC_MODAL_CLOSED) || - (connector === walletConnectV2 && error?.message === ErrorCode.CANCELED) || - (connector === coinbaseWallet && error?.toString?.() === ErrorCode.CB_REJECTED_REQUEST) + error?.message === ErrorCode.WALLETCONNECT_MODAL_CLOSED || + error?.message === ErrorCode.WALLETCONNECT_CANCELED || + error?.message === ErrorCode.WALLETCONNECT_MODAL_CLOSED || + rejectedPhrases.some(phrase => error?.message?.toLowerCase?.()?.includes?.(phrase.toLowerCase())) ) } diff --git a/src/hooks/kyberdao/index.tsx b/src/hooks/kyberdao/index.tsx index be14342034..39732e3974 100644 --- a/src/hooks/kyberdao/index.tsx +++ b/src/hooks/kyberdao/index.tsx @@ -37,7 +37,7 @@ import { useTransactionAdder } from 'state/transactions/hooks' import { TRANSACTION_TYPE } from 'state/transactions/type' import { calculateGasMargin } from 'utils' import { aggregateValue } from 'utils/array' -import { formatWalletErrorMessage } from 'utils/errorMessage' +import { friendlyError } from 'utils/errorMessage' import { formatUnitsToFixed } from 'utils/formatBalance' import { sendEVMTransaction } from 'utils/sendTransaction' @@ -89,7 +89,7 @@ export function useKyberDaoStakeActions() { }) return tx.hash } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { throw error @@ -120,7 +120,7 @@ export function useKyberDaoStakeActions() { }) return tx.hash } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { throw error @@ -155,7 +155,7 @@ export function useKyberDaoStakeActions() { }) return tx.hash } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { throw error @@ -181,7 +181,7 @@ export function useKyberDaoStakeActions() { }) return tx.hash } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { throw error @@ -208,7 +208,7 @@ export function useKyberDaoStakeActions() { }) return tx.hash } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { throw error @@ -272,7 +272,7 @@ export function useClaimVotingRewards() { }) return tx.hash as string } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { throw error @@ -312,7 +312,7 @@ export const useVotingActions = () => { }) return tx.hash } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { throw error @@ -620,7 +620,7 @@ export function useGasRefundInfo({ rewardStatus = KNCUtilityTabs.Available }: { export function useClaimGasRefundRewards() { const { account, chainId } = useActiveWeb3React() - const { library, connector } = useWeb3React() + const { library } = useWeb3React() const addTransactionWithType = useTransactionAdder() const { claimableReward } = useGasRefundInfo({}) const refetch = useRefetchGasRefundInfo() @@ -669,25 +669,16 @@ export function useClaimGasRefundRewards() { return tx.hash as string } catch (error) { refetch() - if (didUserReject(connector, error)) { - notify({ - title: t`Transaction rejected`, - summary: t`In order to claim, you must accept in your wallet.`, - type: NotificationType.ERROR, - }) - throw new Error('Transaction rejected.') - } else { - const message = formatWalletErrorMessage(error) - console.error('Claim error:', { error, message }) - notify({ - title: t`Claim Error`, - summary: message, - type: NotificationType.ERROR, - }) - throw error - } + const message = friendlyError(error) + console.error('Claim error:', { message, error }) + notify({ + title: t`Claim Error`, + summary: message, + type: NotificationType.ERROR, + }) + throw error } - }, [account, addTransactionWithType, chainId, claimableReward, library, notify, connector, refetch]) + }, [account, addTransactionWithType, chainId, claimableReward, library, notify, refetch]) return claimGasRefundRewards } diff --git a/src/hooks/useApproveCallback.ts b/src/hooks/useApproveCallback.ts index 8d3fe2c628..209daba3bd 100644 --- a/src/hooks/useApproveCallback.ts +++ b/src/hooks/useApproveCallback.ts @@ -1,15 +1,19 @@ import { MaxUint256 } from '@ethersproject/constants' import { TransactionResponse } from '@ethersproject/providers' import { Currency, CurrencyAmount, TokenAmount } from '@kyberswap/ks-sdk-core' +import { t } from '@lingui/macro' import JSBI from 'jsbi' import { useCallback, useMemo } from 'react' +import { NotificationType } from 'components/Announcement/type' import { useTokenAllowance } from 'data/Allowances' +import { useNotify } from 'state/application/hooks' import { Field } from 'state/swap/actions' import { useHasPendingApproval, useTransactionAdder } from 'state/transactions/hooks' import { TRANSACTION_TYPE } from 'state/transactions/type' import { calculateGasMargin } from 'utils' import { Aggregator } from 'utils/aggregator' +import { friendlyError } from 'utils/errorMessage' import { computeSlippageAdjustedAmounts } from 'utils/prices' import { useActiveWeb3React } from './index' @@ -55,6 +59,7 @@ export function useApproveCallback( : ApprovalState.NOT_APPROVED : ApprovalState.APPROVED }, [amountToApprove, currentAllowance, isSolana, pendingApproval, spender]) + const notify = useNotify() const tokenContract = useTokenContract(token?.address) const addTransactionWithType = useTransactionAdder() @@ -123,11 +128,20 @@ export function useApproveCallback( }) }) .catch((error: Error) => { - console.debug('Failed to approve token', error) + const message = friendlyError(error) + console.error('Approve token error:', { message, error }) + notify( + { + title: t`Approve Error`, + summary: message, + type: NotificationType.ERROR, + }, + 8000, + ) throw error }) }, - [approvalState, token, tokenContract, amountToApprove, spender, addTransactionWithType, forceApprove], + [approvalState, token, tokenContract, amountToApprove, spender, addTransactionWithType, forceApprove, notify], ) return [approvalState, approve, currentAllowance] diff --git a/src/hooks/useElasticLegacy.ts b/src/hooks/useElasticLegacy.ts index 465b472cf3..6699174773 100644 --- a/src/hooks/useElasticLegacy.ts +++ b/src/hooks/useElasticLegacy.ts @@ -8,6 +8,7 @@ import JSBI from 'jsbi' import { useEffect, useRef, useState } from 'react' import TickReaderABI from 'constants/abis/v2/ProAmmTickReader.json' +import { didUserReject } from 'constants/connectors/utils' import { EVMNetworkInfo } from 'constants/networks/type' import { useActiveWeb3React, useWeb3React } from 'hooks' import { useTransactionAdder } from 'state/transactions/hooks' @@ -464,7 +465,7 @@ export const useRemoveLiquidityLegacy = ( setShowPendingModal('removeLiquidity') setAttemptingTxn(false) - if (error?.code !== 'ACTION_REJECTED') { + if (!didUserReject(error)) { const e = new Error('Remove Legacy Elastic Liquidity Error', { cause: error }) e.name = ErrorName.RemoveElasticLiquidityError captureException(e, { diff --git a/src/hooks/web3/useChangeNetwork.ts b/src/hooks/web3/useChangeNetwork.ts index 7db7db477f..3039ecb17f 100644 --- a/src/hooks/web3/useChangeNetwork.ts +++ b/src/hooks/web3/useChangeNetwork.ts @@ -1,7 +1,6 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { t } from '@lingui/macro' import { captureException } from '@sentry/react' -import { Connector } from '@web3-react/types' import { useCallback } from 'react' import { NotificationType } from 'components/Announcement/type' @@ -57,10 +56,9 @@ export function useChangeNetwork() { const failureCallback = useCallback( ( - connector: Connector, desiredChainId: ChainId, error: any, - customFailureCallback?: (connector: Connector, error: Error) => void, + customFailureCallback?: (error: Error) => void, customTexts?: { name?: string title?: string @@ -71,7 +69,7 @@ export function useChangeNetwork() { const title = customTexts?.title || t`Failed to switch network` let message: string = customTexts?.default || t`Error when changing network.` - if (didUserReject(connector, error)) { + if (didUserReject(error)) { message = customTexts?.rejected || t`In order to use KyberSwap on ${ @@ -99,7 +97,7 @@ export function useChangeNetwork() { type: NotificationType.ERROR, summary: message, }) - customFailureCallback?.(connector, error) + customFailureCallback?.(error) }, [chainId, notify, walletEVM.walletKey], ) @@ -115,7 +113,7 @@ export function useChangeNetwork() { default?: string }, customSuccessCallback?: () => void, - customFailureCallback?: (connector: Connector, error: Error) => void, + customFailureCallback?: (error: Error) => void, waitUtilUpdatedChainId = false, ) => { const wrappedSuccessCallback = () => @@ -144,8 +142,8 @@ export function useChangeNetwork() { wrappedSuccessCallback() } catch (error) { console.error('Add new network failed', { addChainParameter, error }) - failureCallback(connector, desiredChainId, error, customFailureCallback, customTexts) - if (!didUserReject(connector, error)) { + failureCallback(desiredChainId, error, customFailureCallback, customTexts) + if (!didUserReject(error)) { const e = new Error(`[Wallet] ${error.message}`) e.name = 'Add new network Error' e.stack = '' @@ -157,22 +155,14 @@ export function useChangeNetwork() { } } }, - [ - library?.provider, - chainId, - connector, - failureCallback, - fetchKyberswapConfig, - successCallback, - walletEVM.walletKey, - ], + [library?.provider, chainId, failureCallback, fetchKyberswapConfig, successCallback, walletEVM.walletKey], ) const changeNetwork = useCallback( async ( desiredChainId: ChainId, customSuccessCallback?: () => void, - customFailureCallback?: (connector: Connector, error: Error) => void, + customFailureCallback?: (error: Error) => void, waitUtilUpdatedChainId = false, isAddNetworkIfPossible = true, ) => { @@ -203,8 +193,8 @@ export function useChangeNetwork() { console.error('Switch network failed', { desiredChainId, error }) // walletconnect v2 not support add network, so halt execution here - if (didUserReject(connector, error) || connector === walletConnectV2) { - failureCallback(connector, desiredChainId, error, customFailureCallback) + if (didUserReject(error) || connector === walletConnectV2) { + failureCallback(desiredChainId, error, customFailureCallback) return } if (isAddNetworkIfPossible) { @@ -224,7 +214,7 @@ export function useChangeNetwork() { waitUtilUpdatedChainId, ) } else { - failureCallback(connector, desiredChainId, error, customFailureCallback) + failureCallback(desiredChainId, error, customFailureCallback) } } } else { diff --git a/src/pages/AddLiquidity/TokenPair.tsx b/src/pages/AddLiquidity/TokenPair.tsx index 0862bdafd8..1537a891bb 100644 --- a/src/pages/AddLiquidity/TokenPair.tsx +++ b/src/pages/AddLiquidity/TokenPair.tsx @@ -23,6 +23,7 @@ import TransactionConfirmationModal, { ConfirmationModalContent, TransactionErrorContent, } from 'components/TransactionConfirmationModal' +import { didUserReject } from 'constants/connectors/utils' import { AMP_HINT, APP_PATHS } from 'constants/index' import { EVMNetworkInfo } from 'constants/networks/type' import { NativeCurrencies } from 'constants/tokens' @@ -315,7 +316,7 @@ const TokenPair = ({ e.name = 'AddLiquidityError' captureException(e, { extra: { args } }) // we only care if the error is something _other_ than the user rejected the tx - if (err?.code !== 4001) { + if (!didUserReject(error)) { console.error(err) } diff --git a/src/pages/AddLiquidity/ZapIn.tsx b/src/pages/AddLiquidity/ZapIn.tsx index 976f8982b8..5d61485169 100644 --- a/src/pages/AddLiquidity/ZapIn.tsx +++ b/src/pages/AddLiquidity/ZapIn.tsx @@ -25,6 +25,7 @@ import TransactionConfirmationModal, { } from 'components/TransactionConfirmationModal' import ZapError from 'components/ZapError' import FormattedPriceImpact from 'components/swapv2/FormattedPriceImpact' +import { didUserReject } from 'constants/connectors/utils' import { AMP_HINT, APP_PATHS } from 'constants/index' import { EVMNetworkInfo } from 'constants/networks/type' import { NativeCurrencies } from 'constants/tokens' @@ -283,7 +284,7 @@ const ZapIn = ({ captureException(e, { extra: { args } }) // we only care if the error is something _other_ than the user rejected the tx - if (err?.code !== 4001) { + if (!didUserReject(err)) { console.error(err) } diff --git a/src/pages/AddLiquidityV2/index.tsx b/src/pages/AddLiquidityV2/index.tsx index 4c50d73999..f6c63843f8 100644 --- a/src/pages/AddLiquidityV2/index.tsx +++ b/src/pages/AddLiquidityV2/index.tsx @@ -19,6 +19,7 @@ import { useMedia } from 'react-use' import { Box, Flex, Text } from 'rebass' import styled from 'styled-components' +import { NotificationType } from 'components/Announcement/type' import { ButtonError, ButtonLight, ButtonPrimary } from 'components/Button' import { OutlineCard, SubTextCard, WarningCard } from 'components/Card' import { AutoColumn } from 'components/Column' @@ -62,7 +63,7 @@ import useTheme from 'hooks/useTheme' import useTransactionDeadline from 'hooks/useTransactionDeadline' import { convertTickToPrice } from 'pages/Farm/ElasticFarmv2/utils' import { ApplicationModal } from 'state/application/actions' -import { useOpenModal, useWalletModalToggle } from 'state/application/hooks' +import { useNotify, useOpenModal, useWalletModalToggle } from 'state/application/hooks' import { FarmUpdater } from 'state/farms/elastic/hooks' import { useElasticFarmsV2 } from 'state/farms/elasticv2/hooks' import ElasticFarmV2Updater from 'state/farms/elasticv2/updater' @@ -85,6 +86,7 @@ import { VIEW_MODE } from 'state/user/reducer' import { ExternalLink, MEDIA_WIDTHS, StyledInternalLink, TYPE } from 'theme' import { basisPointsToPercent, calculateGasMargin, formattedNum } from 'utils' import { currencyId } from 'utils/currencyId' +import { friendlyError } from 'utils/errorMessage' import { toSignificantOrMaxIntegerPart } from 'utils/formatCurrencyAmount' import { maxAmountSpend } from 'utils/maxAmountSpend' import { formatNotDollarAmount } from 'utils/numbers' @@ -139,6 +141,7 @@ export default function AddLiquidity() { const [showChart, setShowChart] = useState(false) const [positionIndex, setPositionIndex] = useState(0) const { mixpanelHandler } = useMixpanel() + const notify = useNotify() // fee selection from url const feeAmount: FeeAmount = @@ -212,9 +215,10 @@ export default function AddLiquidity() { // show this for Zohar can get tick to add farm useEffect(() => { - console.log('-------------------') - console.log('tickLower: ', tickLower) - console.log('tickUpper: ', tickUpper) + console.groupCollapsed('ticks ------------------') + console.debug('tickLower: ', tickLower) + console.debug('tickUpper: ', tickUpper) + console.groupEnd() }, [tickLower, tickUpper]) const poolAddress = useProAmmPoolInfo(baseCurrency, currencyB, feeAmount) @@ -498,12 +502,18 @@ export default function AddLiquidity() { }) }) .catch((error: any) => { - console.error('Failed to send transaction', error) setAttemptingTxn(false) - // we only care if the error is something _other_ than the user rejected the tx - if (error?.code !== 4001) { - console.error(error) - } + // sending tx error, not tx execute error + const message = friendlyError(error) + console.error('Add liquidity error:', { message, error }) + notify( + { + title: t`Add liquidity error`, + summary: message, + type: NotificationType.ERROR, + }, + 8000, + ) }) } else { return @@ -530,6 +540,7 @@ export default function AddLiquidity() { isMultiplePosition, poolAddress, currencyAmountSum, + notify, ], ) diff --git a/src/pages/CreatePool/index.tsx b/src/pages/CreatePool/index.tsx index 4e0dba71d7..4ebaae0698 100644 --- a/src/pages/CreatePool/index.tsx +++ b/src/pages/CreatePool/index.tsx @@ -21,6 +21,7 @@ import QuestionHelper from 'components/QuestionHelper' import Row, { AutoRow, RowBetween, RowFlat } from 'components/Row' import TransactionConfirmationModal, { ConfirmationModalContent } from 'components/TransactionConfirmationModal' import { TutorialType } from 'components/Tutorial' +import { didUserReject } from 'constants/connectors/utils' import { APP_PATHS, CREATE_POOL_AMP_HINT } from 'constants/index' import { ONLY_DYNAMIC_FEE_CHAINS, ONLY_STATIC_FEE_CHAINS, STATIC_FEE_OPTIONS } from 'constants/networks' import { EVMNetworkInfo } from 'constants/networks/type' @@ -287,7 +288,7 @@ export default function CreatePool() { setAttemptingTxn(false) setShowConfirm(false) // we only care if the error is something _other_ than the user rejected the tx - if (error?.code !== 4001) { + if (!didUserReject(error)) { console.error(error) } }) @@ -296,7 +297,7 @@ export default function CreatePool() { setAttemptingTxn(false) setShowConfirm(false) // we only care if the error is something _other_ than the user rejected the tx - if (error?.code !== 4001) { + if (!didUserReject(error)) { console.error(error) } }) diff --git a/src/pages/ElasticSwap/hooks/index.ts b/src/pages/ElasticSwap/hooks/index.ts index 061a8a66f7..5ed0b37867 100644 --- a/src/pages/ElasticSwap/hooks/index.ts +++ b/src/pages/ElasticSwap/hooks/index.ts @@ -6,6 +6,7 @@ import { useMemo } from 'react' import { useSearchParams } from 'react-router-dom' import { abi as QuoterABI } from 'constants/abis/v2/ProAmmQuoter.json' +import { didUserReject } from 'constants/connectors/utils' import { INITIAL_ALLOWED_SLIPPAGE } from 'constants/index' import { EVMNetworkInfo } from 'constants/networks/type' import { useActiveWeb3React, useWeb3React } from 'hooks' @@ -555,7 +556,7 @@ export function useSwapCallback( }) .catch((error: any) => { // if the user rejected the tx, pass this along - if (error?.code === 4001) { + if (didUserReject(error)) { throw new Error('Transaction rejected.') } else { // otherwise, the error was unexpected and we need to convey that diff --git a/src/pages/IncreaseLiquidity/index.tsx b/src/pages/IncreaseLiquidity/index.tsx index 3e88c2c4ad..6d53224488 100644 --- a/src/pages/IncreaseLiquidity/index.tsx +++ b/src/pages/IncreaseLiquidity/index.tsx @@ -30,6 +30,7 @@ import { RowBetween } from 'components/Row' import { SLIPPAGE_EXPLANATION_URL } from 'components/SlippageWarningNote' import TransactionConfirmationModal, { ConfirmationModalContent } from 'components/TransactionConfirmationModal' import { TutorialType } from 'components/Tutorial' +import { didUserReject } from 'constants/connectors/utils' import { APP_PATHS } from 'constants/index' import { EVMNetworkInfo } from 'constants/networks/type' import { NativeCurrencies } from 'constants/tokens' @@ -310,7 +311,7 @@ export default function IncreaseLiquidity() { console.error('Failed to send transaction', error) setAttemptingTxn(false) // we only care if the error is something _other_ than the user rejected the tx - if (error?.code !== 4001) { + if (!didUserReject(error)) { console.error(error) } }) diff --git a/src/pages/MyEarnings/hooks.ts b/src/pages/MyEarnings/hooks.ts index 33656dbbbf..1914cdbcbd 100644 --- a/src/pages/MyEarnings/hooks.ts +++ b/src/pages/MyEarnings/hooks.ts @@ -9,6 +9,7 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { useDispatch } from 'react-redux' import { POOLS_BULK_WITH_PAGINATION, POOLS_HISTORICAL_BULK_WITH_PAGINATION, POOL_COUNT } from 'apollo/queries' +import { didUserReject } from 'constants/connectors/utils' import { NETWORKS_INFO, ONLY_DYNAMIC_FEE_CHAINS, isEVM as isEVMChain } from 'constants/networks' import { useActiveWeb3React, useWeb3React } from 'hooks' import { Position as SubgraphLegacyPosition, config, parsePosition } from 'hooks/useElasticLegacy' @@ -360,7 +361,7 @@ export function useRemoveLiquidityFromLegacyPosition( dispatch(setShowPendingModal(MODAL_PENDING_TEXTS.REMOVE_LIQUIDITY)) dispatch(setAttemptingTxn(false)) - if (error?.code !== 'ACTION_REJECTED') { + if (!didUserReject(error)) { const e = new Error('Remove Legacy Elastic Liquidity Error', { cause: error }) e.name = ErrorName.RemoveElasticLiquidityError captureException(e, { diff --git a/src/pages/RemoveLiquidity/TokenPair.tsx b/src/pages/RemoveLiquidity/TokenPair.tsx index 2a55a89bb2..a1cf53837a 100644 --- a/src/pages/RemoveLiquidity/TokenPair.tsx +++ b/src/pages/RemoveLiquidity/TokenPair.tsx @@ -24,6 +24,7 @@ import TransactionConfirmationModal, { ConfirmationModalContent, TransactionErrorContent, } from 'components/TransactionConfirmationModal' +import { didUserReject } from 'constants/connectors/utils' import { APP_PATHS, EIP712Domain } from 'constants/index' import { EVMNetworkInfo } from 'constants/networks/type' import { NativeCurrencies } from 'constants/tokens' @@ -204,7 +205,7 @@ export default function TokenPair({ }) .catch((error: any) => { // for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve - if (error?.code !== 4001) { + if (!didUserReject(error)) { approveCallback() } }) @@ -397,7 +398,7 @@ export default function TokenPair({ .catch((err: Error) => { setAttemptingTxn(false) // we only care if the error is something _other_ than the user rejected the tx - if ((err as any)?.code !== 4001 && (err as any)?.code !== 'ACTION_REJECTED') { + if (!didUserReject(err)) { const e = new Error('Remove Classic Liquidity Error', { cause: err }) e.name = ErrorName.RemoveClassicLiquidityError captureException(e, { extra: { args } }) diff --git a/src/pages/RemoveLiquidity/ZapOut.tsx b/src/pages/RemoveLiquidity/ZapOut.tsx index 15f2d6f892..532f19021f 100644 --- a/src/pages/RemoveLiquidity/ZapOut.tsx +++ b/src/pages/RemoveLiquidity/ZapOut.tsx @@ -35,6 +35,7 @@ import TransactionConfirmationModal, { } from 'components/TransactionConfirmationModal' import ZapError from 'components/ZapError' import FormattedPriceImpact from 'components/swapv2/FormattedPriceImpact' +import { didUserReject } from 'constants/connectors/utils' import { APP_PATHS, EIP712Domain } from 'constants/index' import { EVMNetworkInfo } from 'constants/networks/type' import { NativeCurrencies } from 'constants/tokens' @@ -246,7 +247,7 @@ export default function ZapOut({ }) .catch((error: any) => { // for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve - if (error?.code !== 4001) { + if (!didUserReject(error)) { approveCallback() } }) @@ -370,7 +371,7 @@ export default function ZapOut({ .then(calculateGasMargin) .catch(err => { // we only care if the error is something other than the user rejected the tx - if ((err as any)?.code !== 4001) { + if (!didUserReject(err)) { console.error(`estimateGas failed`, methodName, args, err) } @@ -381,7 +382,7 @@ export default function ZapOut({ setZapOutError(t`Insufficient Liquidity in the Liquidity Pool to Swap`) } else { setZapOutError(err?.message) - if ((err as any)?.code !== 4001 && (err as any)?.code !== 'ACTION_REJECTED') { + if (!didUserReject(err)) { const e = new Error('estimate gas zap out failed', { cause: err }) e.name = ErrorName.RemoveClassicLiquidityError captureException(e, { extra: { args } }) @@ -441,7 +442,7 @@ export default function ZapOut({ .catch((err: Error) => { setAttemptingTxn(false) // we only care if the error is something _other_ than the user rejected the tx - if ((err as any)?.code !== 4001 && (err as any)?.code !== 'ACTION_REJECTED') { + if (!didUserReject(err)) { const e = new Error('zap out failed', { cause: err }) e.name = ErrorName.RemoveClassicLiquidityError captureException(e, { extra: { args } }) diff --git a/src/pages/RemoveLiquidityProAmm/index.tsx b/src/pages/RemoveLiquidityProAmm/index.tsx index 13d643a2e9..fc679f9d45 100644 --- a/src/pages/RemoveLiquidityProAmm/index.tsx +++ b/src/pages/RemoveLiquidityProAmm/index.tsx @@ -37,6 +37,7 @@ import TransactionConfirmationModal, { } from 'components/TransactionConfirmationModal' import { TutorialType } from 'components/Tutorial' import FarmV2ABI from 'constants/abis/v2/farmv2.json' +import { didUserReject } from 'constants/connectors/utils' import { EVMNetworkInfo } from 'constants/networks/type' import { useActiveWeb3React, useWeb3React } from 'hooks' import { useContract, useProAmmNFTPositionManagerContract, useProMMFarmContract } from 'hooks/useContract' @@ -416,7 +417,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) { .catch((error: any) => { setAttemptingTxn(false) - if (error?.code !== 'ACTION_REJECTED') { + if (!didUserReject(error)) { const e = new Error('Remove Elastic Liquidity Error', { cause: error }) e.name = ErrorName.RemoveElasticLiquidityError captureException(e, { diff --git a/src/pages/SwapV2/index.tsx b/src/pages/SwapV2/index.tsx index c63a965911..86562c686d 100644 --- a/src/pages/SwapV2/index.tsx +++ b/src/pages/SwapV2/index.tsx @@ -311,7 +311,7 @@ export default function Swap() { setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: hash }) }) .catch(error => { - if (error?.code !== 4001 && error?.code !== 'ACTION_REJECTED') captureSwapError(error) + captureSwapError(error) setSwapState({ attemptingTxn: false, tradeToConfirm, diff --git a/src/utils/dmm.ts b/src/utils/dmm.ts index 2baa3751d6..5e00914532 100644 --- a/src/utils/dmm.ts +++ b/src/utils/dmm.ts @@ -2,7 +2,6 @@ import { getAddress } from '@ethersproject/address' import { BigNumber } from '@ethersproject/bignumber' import { Pair } from '@kyberswap/ks-sdk-classic' import { ChainId, Currency, CurrencyAmount, Fraction, Price, Token, TokenAmount } from '@kyberswap/ks-sdk-core' -import { t } from '@lingui/macro' import JSBI from 'jsbi' import { useMemo } from 'react' @@ -522,41 +521,3 @@ export function useRewardTokensFullInfo(): Token[] { [chainId, nativeName, JSON.stringify(rewardTokens)], ) } - -export function errorFriendly(text: string): string { - const error = text?.toLowerCase?.() || '' - - if (!error || error.includes('router: expired')) { - return 'An error occurred. Refresh the page and try again ' - } - - if ( - error.includes('mintotalamountout') || - error.includes('err_limit_out') || - error.includes('return amount is not enough') || - error.includes('code=call_exception') || - error.includes('none of the calls threw an error') - ) { - return t`An error occurred. Try refreshing the price rate or increase max slippage` - } - if (error.includes('header not found') || error.includes('swap failed')) { - return t`An error occurred. Refresh the page and try again. If the issue still persists, it might be an issue with your RPC node settings in Metamask.` - } - if (error.includes('user rejected transaction') || error.includes('user denied transaction')) { - return t`User rejected transaction.` - } - - // classic/elastic remove liquidity error - if (error.includes('insufficient')) { - return t`An error occurred. Please try increasing max slippage` - } - - if (error.includes('permit')) { - return t`An error occurred. Invalid Permit Signature` - } - if (error.includes('burn amount exceeds balance')) { - return t`Insufficient fee rewards amount, try to remove your liquidity without claiming fees for now and you can try to claim it later` - } - - return t`An error occurred` -} diff --git a/src/utils/errorMessage.ts b/src/utils/errorMessage.ts index 84e197007f..99246bcdd1 100644 --- a/src/utils/errorMessage.ts +++ b/src/utils/errorMessage.ts @@ -1,18 +1,66 @@ import { t } from '@lingui/macro' +import { didUserReject } from 'constants/connectors/utils' + function capitalizeFirstLetter(string: string) { return string.charAt(0).toUpperCase() + string.slice(1) } -// to be add more patterns ... -const pattern1 = /{"originalError":.+"message":"execution reverted: ([^"]+)"/ -export function formatWalletErrorMessage(error: Error): string { - const message = error.message - if (message.length < 100) return message +function parseKnownPattern(text: string): string | undefined { + const error = text?.toLowerCase?.() || '' + console.info('parseError:', { text, error }) + if (!error || error.includes('router: expired')) return 'An error occurred. Refresh the page and try again ' + + if ( + error.includes('mintotalamountout') || + error.includes('err_limit_out') || + error.includes('return amount is not enough') || + error.includes('code=call_exception') || + error.includes('none of the calls threw an error') + ) + return t`An error occurred. Try refreshing the price rate or increase max slippage` + + if (error.includes('header not found') || error.includes('swap failed')) + return t`An error occurred. Refresh the page and try again. If the issue still persists, it might be an issue with your RPC node settings in Metamask.` + + if (didUserReject(error)) return t`User rejected the transaction.` + + // classic/elastic remove liquidity error + if (error.includes('insufficient')) return t`An error occurred. Please try increasing max slippage` + + if (error.includes('permit')) return t`An error occurred. Invalid Permit Signature` + + if (error.includes('burn amount exceeds balance')) + return t`Insufficient fee rewards amount, try to remove your liquidity without claiming fees for now and you can try to claim it later` - // extract & format long messages - const pattern1Result = pattern1.exec(message) - if (pattern1Result) return capitalizeFirstLetter(pattern1Result[1]) + if (error === '[object Object]') return t`Something went wrong. Please try again` + + return undefined +} + +const patterns: { pattern: RegExp; getMessage: (match: RegExpExecArray) => string }[] = [ + { + pattern: /{"originalError":.+"message":"execution reverted: ([^"]+)"/, + getMessage: match => match[1], + }, + { pattern: /^([\w ]*\w+) \(.+?\)$/, getMessage: match => match[1] }, +] +function parseKnownRegexPattern(text: string): string | undefined { + const pattern = patterns.find(pattern => pattern.pattern.exec(text)) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if (pattern) return capitalizeFirstLetter(pattern.getMessage(pattern.pattern.exec(text)!)) + return undefined +} + +export function friendlyError(error: Error | string): string { + const message = typeof error === 'string' ? error : error.message + + const knownPattern = parseKnownPattern(message) + if (knownPattern) return knownPattern + + if (message.length < 100) return message + const knownRegexPattern = parseKnownRegexPattern(message) + if (knownRegexPattern) return knownRegexPattern - return t`Unknown error. Please try again.` + return t`An error occurred` } diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts index 2b30c34c60..8d9e73c78e 100644 --- a/src/utils/sentry.ts +++ b/src/utils/sentry.ts @@ -2,6 +2,10 @@ import { TransactionRequest } from '@ethersproject/abstract-provider' import { captureException } from '@sentry/react' import { Deferrable } from 'ethers/lib/utils' +import { didUserReject } from 'constants/connectors/utils' + +import { friendlyError } from './errorMessage' + export enum ErrorName { SwappError = 'SwapError', RemoveElasticLiquidityError = 'RemoveElasticLiquidityError', @@ -9,10 +13,12 @@ export enum ErrorName { } export function captureSwapError(error: TransactionError) { - if (error.message.toLowerCase().includes('user canceled') || error.message.toLowerCase().includes('user reject')) { - return - } - const e = new Error('Swap failed', { cause: error }) + if (didUserReject(error)) return + + const friendlyErrorResult = friendlyError(error) + if (friendlyErrorResult.includes('slippage')) return + + const e = new Error(friendlyErrorResult, { cause: error }) e.name = ErrorName.SwappError const tmp = JSON.stringify(error)