From e47b868c532ff136d69952e25155e9f50afeeb2d Mon Sep 17 00:00:00 2001 From: Maciek Date: Fri, 9 Jun 2023 08:58:16 +0200 Subject: [PATCH] fix(withdraws): clear eth network errors (#4044) --- libs/utils/src/index.ts | 1 + libs/utils/src/lib/resolve-network-name.ts | 6 ++ .../use-ethereum-withdraw-approval-toasts.tsx | 67 +++++++++++++------ ...hereum-withdraw-approvals-manager.spec.tsx | 31 +++++++-- ...se-ethereum-withdraw-approvals-manager.tsx | 20 ++++-- .../use-ethereum-withdraw-approvals-store.tsx | 10 +++ .../src/lib/withdrawal-approval-status.tsx | 56 +++++++++++++++- 7 files changed, 162 insertions(+), 29 deletions(-) create mode 100644 libs/utils/src/lib/resolve-network-name.ts diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index d260e0cff..163506667 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -13,3 +13,4 @@ export * from './lib/remove-0x'; export * from './lib/remove-pagination-wrapper'; export * from './lib/time'; export * from './lib/validate'; +export * from './lib/resolve-network-name'; diff --git a/libs/utils/src/lib/resolve-network-name.ts b/libs/utils/src/lib/resolve-network-name.ts new file mode 100644 index 000000000..ccab8f237 --- /dev/null +++ b/libs/utils/src/lib/resolve-network-name.ts @@ -0,0 +1,6 @@ +const NETWORK_NAME_MAP: Readonly> = { + '1': 'Ethereum Mainnet', + '11155111': 'Sepolia test network', +}; +export const resolveNetworkName = (chainId?: string): string => + NETWORK_NAME_MAP[chainId || ''] || `(chainID: ${chainId})`; diff --git a/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx b/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx index 0471eb3cc..077768bc2 100644 --- a/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx +++ b/libs/web3/src/lib/use-ethereum-withdraw-approval-toasts.tsx @@ -9,7 +9,10 @@ import { Intent } from '@vegaprotocol/ui-toolkit'; import { useCallback } from 'react'; import compact from 'lodash/compact'; import type { EthWithdrawalApprovalState } from './use-ethereum-withdraw-approvals-store'; -import { useEthWithdrawApprovalsStore } from './use-ethereum-withdraw-approvals-store'; +import { + useEthWithdrawApprovalsStore, + WithdrawalFailure, +} from './use-ethereum-withdraw-approvals-store'; import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store'; import { VerificationStatus } from './withdrawal-approval-status'; @@ -26,12 +29,23 @@ const EthWithdrawalApprovalToastContent = ({ }: { tx: EthWithdrawalApprovalState; }) => { + const isConnectionFailure = + tx.failureReason && + [ + WithdrawalFailure.WrongConnection, + WithdrawalFailure.NoConnection, + ].includes(tx.failureReason); + let title = ''; if (tx.status === ApprovalStatus.Error) { title = t('Error occurred'); } if (tx.status === ApprovalStatus.Pending) { - title = t('Pending approval'); + if (tx.failureReason && isConnectionFailure) { + title = tx.message || t('Withdraw failure'); + } else { + title = t('Pending approval'); + } } if (tx.status === ApprovalStatus.Delayed) { title = t('Delayed'); @@ -39,11 +53,12 @@ const EthWithdrawalApprovalToastContent = ({ if (tx.status === ApprovalStatus.Ready) { title = t('Approved'); } + const num = formatNumber( toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals), tx.withdrawal.asset.decimals ); - const details = ( + const details = isConnectionFailure ? null : ( {t('Withdraw')} {num} {tx.withdrawal.asset.symbol} @@ -61,7 +76,8 @@ const EthWithdrawalApprovalToastContent = ({ }; const isFinal = (tx: EthWithdrawalApprovalState) => - [ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status); + [ApprovalStatus.Ready, ApprovalStatus.Error].includes(tx.status) && + !tx.failureReason; export const useEthereumWithdrawApprovalsToasts = () => { const [setToast, remove] = useToasts((state) => [ @@ -74,21 +90,34 @@ export const useEthereumWithdrawApprovalsToasts = () => { ]); const fromWithdrawalApproval = useCallback( - (tx: EthWithdrawalApprovalState): Toast => ({ - id: `withdrawal-${tx.id}`, - intent: intentMap[tx.status], - onClose: () => { - if ([ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status)) { - deleteTx(tx.id); - } else { - dismissTx(tx.id); - } - remove(`withdrawal-${tx.id}`); - }, - loader: tx.status === ApprovalStatus.Pending, - content: , - closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined, - }), + (tx: EthWithdrawalApprovalState): Toast => { + const loader = + tx.status === ApprovalStatus.Pending && + !( + tx.failureReason && + [ + WithdrawalFailure.WrongConnection, + WithdrawalFailure.NoConnection, + ].includes(tx.failureReason) + ); + return { + id: `withdrawal-${tx.id}`, + intent: intentMap[tx.status], + onClose: () => { + if ( + [ApprovalStatus.Error, ApprovalStatus.Ready].includes(tx.status) + ) { + deleteTx(tx.id); + } else { + dismissTx(tx.id); + } + remove(`withdrawal-${tx.id}`); + }, + loader, + content: , + closeAfter: isFinal(tx) ? CLOSE_AFTER : undefined, + }; + }, [deleteTx, dismissTx, remove] ); diff --git a/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.spec.tsx b/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.spec.tsx index f9321ea35..03ae3f7a4 100644 --- a/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.spec.tsx +++ b/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.spec.tsx @@ -5,7 +5,10 @@ import type { ReactNode } from 'react'; import { MockedProvider } from '@apollo/client/testing'; import waitForNextTick from 'flush-promises'; import * as Schema from '@vegaprotocol/types'; -import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store'; +import { + ApprovalStatus, + WithdrawalFailure, +} from './use-ethereum-withdraw-approvals-store'; import BigNumber from 'bignumber.js'; import type { EthWithdrawApprovalStore, @@ -21,7 +24,7 @@ import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; const mockWeb3Provider = jest.fn(); -let mockChainId = 111111; +let mockChainId: number | undefined = 111111; jest.mock('@web3-react/core', () => ({ useWeb3React: () => ({ provider: mockWeb3Provider(), @@ -314,9 +317,29 @@ describe('useEthWithdrawApprovalsManager', () => { update, }); render(); - expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Error); + expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Pending); + expect(update.mock.calls[0][1].message).toEqual('Change network'); + expect(update.mock.calls[0][1].failureReason).toEqual( + WithdrawalFailure.WrongConnection + ); + mockChainId = 111111; + }); + + it('detect no chainId', () => { + mockChainId = undefined; + const transaction = createWithdrawTransaction(); + mockEthTransactionStoreState.mockReturnValue({ create }); + mockEthWithdrawApprovalsStoreState.mockReturnValue({ + transactions: [transaction], + update, + }); + render(); + expect(update.mock.calls[0][1].status).toEqual(ApprovalStatus.Pending); expect(update.mock.calls[0][1].message).toEqual( - 'You are on the wrong network' + 'Connect wallet to withdraw' + ); + expect(update.mock.calls[0][1].failureReason).toEqual( + WithdrawalFailure.NoConnection ); mockChainId = 111111; }); diff --git a/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.tsx b/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.tsx index 89d9947cd..d06abcddf 100644 --- a/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.tsx +++ b/libs/web3/src/lib/use-ethereum-withdraw-approvals-manager.tsx @@ -1,6 +1,6 @@ import { useApolloClient } from '@apollo/client'; import BigNumber from 'bignumber.js'; -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { addDecimal } from '@vegaprotocol/utils'; import { useGetWithdrawThreshold } from './use-get-withdraw-threshold'; import { useGetWithdrawDelay } from './use-get-withdraw-delay'; @@ -21,8 +21,9 @@ import { WithdrawalApprovalDocument } from '@vegaprotocol/wallet'; import { useEthTransactionStore } from './use-ethereum-transaction-store'; import { - useEthWithdrawApprovalsStore, ApprovalStatus, + useEthWithdrawApprovalsStore, + WithdrawalFailure, } from './use-ethereum-withdraw-approvals-store'; export const useEthWithdrawApprovalsManager = () => { @@ -55,16 +56,27 @@ export const useEthWithdrawApprovalsManager = () => { message: t( `Invalid asset source: ${withdrawal.asset.source.__typename}` ), + failureReason: WithdrawalFailure.InvalidAsset, + }); + return; + } + if (!chainId) { + update(transaction.id, { + status: ApprovalStatus.Pending, + message: t(`Connect wallet to withdraw`), + failureReason: WithdrawalFailure.NoConnection, }); return; } if (chainId?.toString() !== config?.chain_id) { update(transaction.id, { - status: ApprovalStatus.Error, - message: t(`You are on the wrong network`), + status: ApprovalStatus.Pending, + message: t(`Change network`), + failureReason: WithdrawalFailure.WrongConnection, }); return; } + update(transaction.id, { status: ApprovalStatus.Pending, message: t('Verifying withdrawal approval'), diff --git a/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx b/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx index 4287dda7f..6949a8716 100644 --- a/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx +++ b/libs/web3/src/lib/use-ethereum-withdraw-approvals-store.tsx @@ -14,6 +14,13 @@ export enum ApprovalStatus { Error = 'Error', Ready = 'Ready', } + +export enum WithdrawalFailure { + InvalidAsset, + NoConnection, + WrongConnection, +} + export interface EthWithdrawalApprovalState { id: number; createdAt: Date; @@ -24,6 +31,7 @@ export interface EthWithdrawalApprovalState { dialogOpen?: boolean; withdrawal: WithdrawalBusEventFieldsFragment; approval?: WithdrawalApprovalQuery['erc20WithdrawalApproval']; + failureReason?: WithdrawalFailure; } export interface EthWithdrawApprovalStore { transactions: (EthWithdrawalApprovalState | undefined)[]; @@ -42,6 +50,7 @@ export interface EthWithdrawApprovalStore { | 'threshold' | 'completeTimestamp' | 'dialogOpen' + | 'failureReason' > > ) => void; @@ -86,6 +95,7 @@ export const useEthWithdrawApprovalsStore = create()( | 'threshold' | 'completeTimestamp' | 'dialogOpen' + | 'failureReason' > > ) => diff --git a/libs/web3/src/lib/withdrawal-approval-status.tsx b/libs/web3/src/lib/withdrawal-approval-status.tsx index 63527b439..61f233ef9 100644 --- a/libs/web3/src/lib/withdrawal-approval-status.tsx +++ b/libs/web3/src/lib/withdrawal-approval-status.tsx @@ -1,17 +1,69 @@ import { t } from '@vegaprotocol/i18n'; -import { getDateTimeFormat } from '@vegaprotocol/utils'; +import { + getDateTimeFormat, + resolveNetworkName, + truncateByChars, +} from '@vegaprotocol/utils'; import type { EthWithdrawalApprovalState } from './use-ethereum-withdraw-approvals-store'; -import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store'; +import { + ApprovalStatus, + useEthWithdrawApprovalsStore, + WithdrawalFailure, +} from './use-ethereum-withdraw-approvals-store'; +import { useEthereumConfig } from './use-ethereum-config'; +import { Button, useToasts } from '@vegaprotocol/ui-toolkit'; +import { useWeb3ConnectStore } from './web3-connect-store'; export const VerificationStatus = ({ state, }: { state: EthWithdrawalApprovalState; }) => { + const { config } = useEthereumConfig(); + const openDialog = useWeb3ConnectStore((state) => state.open); + const remove = useToasts((state) => state.remove); + const deleteTx = useEthWithdrawApprovalsStore((state) => state.delete); + if (state.status === ApprovalStatus.Error) { return

{state.message || t('Something went wrong')}

; } + if ( + state.failureReason && + [ + WithdrawalFailure.WrongConnection, + WithdrawalFailure.NoConnection, + ].includes(state.failureReason) + ) { + return state.failureReason === WithdrawalFailure.NoConnection ? ( + <> +

+ {t('To complete this withdrawal, connect the Ethereum wallet %s', [ + truncateByChars(state.withdrawal.details?.receiverAddress || ' '), + ])} +

+ + + ) : ( + <> +

{t('Your Ethereum wallet is connected to the wrong network.')}

+

+ {t('Go to your Ethereum wallet and connect to the network %s', [ + resolveNetworkName(config?.chain_id), + ])} +

+ + ); + } + if (state.status === ApprovalStatus.Pending) { return

{t('Verifying...')}

; }