From 853ec8f69c955b089fb19ae9a452e5cf89ccc6a2 Mon Sep 17 00:00:00 2001 From: macqbat Date: Fri, 27 Jan 2023 15:45:29 +0100 Subject: [PATCH] chore(2351): improve handling wallet errors (#2729) --- .../connect-dialog/connect-dialog.spec.tsx | 21 +++++--- .../src/connect-dialog/connect-dialog.tsx | 29 ++++++----- .../json-rpc-connector-form.tsx | 48 +++++++++++------ .../src/connectors/json-rpc-connector.ts | 52 ++++++++++++------- libs/wallet/src/connectors/vega-connector.ts | 12 ++--- libs/wallet/src/provider.tsx | 4 +- libs/wallet/src/use-json-rpc-connect.ts | 6 +-- .../src/use-vega-transaction-manager.tsx | 6 +-- libs/wallet/src/use-vega-transaction.spec.tsx | 6 ++- libs/wallet/src/use-vega-transaction.tsx | 13 ++--- libs/wallet/src/use-vega-wallet.ts | 6 +-- .../vega-transaction-dialog.tsx | 21 +++----- package.json | 2 +- yarn.lock | 8 +-- 14 files changed, 126 insertions(+), 108 deletions(-) diff --git a/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx b/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx index e9c9805c9..f9ed03f7f 100644 --- a/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx +++ b/libs/wallet/src/connect-dialog/connect-dialog.spec.tsx @@ -9,6 +9,7 @@ import type { MockedResponse } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing'; import { VegaWalletProvider } from '../provider'; import { VegaConnectDialog, CLOSE_DELAY } from './connect-dialog'; +import type { VegaWalletDialogStore } from './connect-dialog'; import type { VegaConnectDialogProps } from '..'; import { ClientErrors, @@ -23,13 +24,15 @@ import { ChainIdDocument } from '@vegaprotocol/react-helpers'; const mockUpdateDialogOpen = jest.fn(); const mockCloseVegaDialog = jest.fn(); +const mockStoreObj: Partial = { + updateVegaWalletDialog: mockUpdateDialogOpen, + closeVegaWalletDialog: mockCloseVegaDialog, + vegaWalletDialogOpen: true, +}; jest.mock('zustand', () => ({ - create: () => () => ({ - updateVegaWalletDialog: mockUpdateDialogOpen, - closeVegaWalletDialog: mockCloseVegaDialog, - vegaWalletDialogOpen: true, - }), + create: () => (storeGetter: (store: VegaWalletDialogStore) => unknown) => + storeGetter(mockStoreObj as VegaWalletDialogStore), })); let defaultProps: VegaConnectDialogProps; @@ -301,7 +304,9 @@ describe('VegaConnectDialog', () => { spyOnConnectWallet .mockClear() .mockImplementation(() => - delayedReject(new WalletError('message', 3001, 'data')) + delayedReject( + new WalletError('User error', 3001, 'The user rejected the request') + ) ); render(generateJSX()); @@ -322,9 +327,9 @@ describe('VegaConnectDialog', () => { await act(async () => { jest.advanceTimersByTime(delay); }); - expect(screen.getByText('Connection declined')).toBeInTheDocument(); + expect(screen.getByText('User error')).toBeInTheDocument(); expect( - screen.getByText('Your wallet connection was rejected') + screen.getByText('The user rejected the request') ).toBeInTheDocument(); }); diff --git a/libs/wallet/src/connect-dialog/connect-dialog.tsx b/libs/wallet/src/connect-dialog/connect-dialog.tsx index a50972067..293532534 100644 --- a/libs/wallet/src/connect-dialog/connect-dialog.tsx +++ b/libs/wallet/src/connect-dialog/connect-dialog.tsx @@ -9,8 +9,9 @@ import { Loader, } from '@vegaprotocol/ui-toolkit'; import { useCallback, useState } from 'react'; +import type { WalletClientError } from '@vegaprotocol/wallet-client'; import { ExternalLinks, t, useChainIdQuery } from '@vegaprotocol/react-helpers'; -import type { VegaConnector, WalletError } from '../connectors'; +import type { VegaConnector } from '../connectors'; import { ViewConnector } from '../connectors'; import { JsonRpcConnector, RestConnector } from '../connectors'; import { RestConnectorForm } from './rest-connector-form'; @@ -44,7 +45,7 @@ export const useVegaWalletDialogStore = create( }) ); -interface VegaWalletDialogStore { +export interface VegaWalletDialogStore { vegaWalletDialogOpen: boolean; updateVegaWalletDialog: (open: boolean) => void; openVegaWalletDialog: () => void; @@ -55,25 +56,25 @@ export const VegaConnectDialog = ({ connectors, onChangeOpen, }: VegaConnectDialogProps) => { - const { - vegaWalletDialogOpen, - closeVegaWalletDialog, - updateVegaWalletDialog, - } = useVegaWalletDialogStore((store) => ({ - vegaWalletDialogOpen: store.vegaWalletDialogOpen, - updateVegaWalletDialog: onChangeOpen + const vegaWalletDialogOpen = useVegaWalletDialogStore( + (store) => store.vegaWalletDialogOpen + ); + const updateVegaWalletDialog = useVegaWalletDialogStore((store) => + onChangeOpen ? (open: boolean) => { store.updateVegaWalletDialog(open); onChangeOpen(open); } - : store.updateVegaWalletDialog, - closeVegaWalletDialog: onChangeOpen + : store.updateVegaWalletDialog + ); + const closeVegaWalletDialog = useVegaWalletDialogStore((store) => + onChangeOpen ? () => { store.closeVegaWalletDialog(); onChangeOpen(false); } - : store.closeVegaWalletDialog, - })); + : store.closeVegaWalletDialog + ); const { data, error, loading } = useChainIdQuery(); @@ -254,7 +255,7 @@ const SelectedForm = ({ appChainId: string; jsonRpcState: { status: Status; - error: WalletError | null; + error: WalletClientError | null; }; reset: () => void; onConnect: () => void; diff --git a/libs/wallet/src/connect-dialog/json-rpc-connector-form.tsx b/libs/wallet/src/connect-dialog/json-rpc-connector-form.tsx index bb248bfaf..ce038ecf1 100644 --- a/libs/wallet/src/connect-dialog/json-rpc-connector-form.tsx +++ b/libs/wallet/src/connect-dialog/json-rpc-connector-form.tsx @@ -8,16 +8,15 @@ import { Tick, } from '@vegaprotocol/ui-toolkit'; import type { ReactNode } from 'react'; +import type { WalletClientError } from '@vegaprotocol/wallet-client'; import type { JsonRpcConnector } from '../connectors'; import { ClientErrors } from '../connectors'; -import type { WalletError } from '../connectors'; import { ConnectDialogTitle } from './connect-dialog-elements'; import { Status } from '../use-json-rpc-connect'; import { useEnvironment } from '@vegaprotocol/environment'; export const ServiceErrors = { NO_HEALTHY_NODE: 1000, - CONNECTION_DECLINED: 3001, REQUEST_PROCESSING: -32000, }; @@ -31,7 +30,7 @@ export const JsonRpcConnectorForm = ({ connector: JsonRpcConnector; appChainId: string; status: Status; - error: WalletError | null; + error: WalletClientError | null; onConnect: () => void; reset: () => void; }) => { @@ -58,7 +57,7 @@ const Connecting = ({ reset, }: { status: Status; - error: WalletError | null; + error: WalletClientError | null; connector: JsonRpcConnector; appChainId: string; reset: () => void; @@ -141,7 +140,7 @@ const Error = ({ appChainId, onTryAgain, }: { - error: WalletError | null; + error: WalletClientError | null; connectorUrl: string | null; appChainId: string; onTryAgain: () => void; @@ -158,18 +157,20 @@ const Error = ({ if (error) { if (error.code === ClientErrors.NO_SERVICE.code) { title = t('No wallet detected'); - text = t(`No wallet application running at ${connectorUrl}`); + text = connectorUrl + ? t('No wallet application running at %s', connectorUrl) + : t('No Vega Wallet application running'); } else if (error.code === ClientErrors.WRONG_NETWORK.code) { title = t('Wrong network'); - text = `To complete your wallet connection, set your wallet network in your app to "${appChainId}".`; - } else if (error.code === ServiceErrors.CONNECTION_DECLINED) { - title = t('Connection declined'); - text = t('Your wallet connection was rejected'); + text = t( + 'To complete your wallet connection, set your wallet network in your app to "%s".', + appChainId + ); } else if (error.code === ServiceErrors.NO_HEALTHY_NODE) { - title = error.message; + title = error.title; text = ( <> - {capitalize(error.data)} + {capitalize(error.message)} {'. '} {VEGA_DOCS_URL && ( - {t(`To complete your wallet connection, set your wallet network in your - app to ${appChainId}.`)} + {t( + `To complete your wallet connection, set your wallet network in your + app to %s.`, + appChainId + )} ); + } else if (error.code === ClientErrors.INVALID_WALLET.code) { + title = error.title; + const errorData = error.message?.split('\n ') || []; + text = ( + + {errorData.map((str, i) => ( + {str} + ))} + + ); } else { - title = error.message; - text = `${error.data} (${error.code})`; + title = t(error.title); + text = t(error.message); } } return ( <> {title} -

{text}

+

{text}

{tryAgain} ); diff --git a/libs/wallet/src/connectors/json-rpc-connector.ts b/libs/wallet/src/connectors/json-rpc-connector.ts index 0a4c0f253..a9c0dacbf 100644 --- a/libs/wallet/src/connectors/json-rpc-connector.ts +++ b/libs/wallet/src/connectors/json-rpc-connector.ts @@ -8,8 +8,6 @@ const VERSION = 'v2'; export const ClientErrors = { NO_SERVICE: new WalletError(t('No service'), 100), - NO_TOKEN: new WalletError(t('No token'), 101), - INVALID_RESPONSE: new WalletError(t('Something went wrong'), 102), INVALID_WALLET: new WalletError(t('Wallet version invalid'), 103), WRONG_NETWORK: new WalletError( t('Wrong network'), @@ -22,11 +20,6 @@ export const ClientErrors = { t('Unknown error occurred') ), NO_CLIENT: new WalletError(t('No client found.'), 106), - REQUEST_REJECTED: new WalletError( - t('Request rejected'), - 107, - t('The request has been rejected by the user') - ), } as const; export class JsonRpcConnector implements VegaConnector { @@ -70,7 +63,9 @@ export class JsonRpcConnector implements VegaConnector { }), }); } - + get url() { + return this._url || ''; + } async getChainId() { if (!this.client) { throw ClientErrors.NO_CLIENT; @@ -79,7 +74,12 @@ export class JsonRpcConnector implements VegaConnector { const { result } = await this.client.GetChainId(); return result; } catch (err) { - throw ClientErrors.INVALID_RESPONSE; + const { + code = ClientErrors.UNKNOWN.code, + message = ClientErrors.UNKNOWN.message, + title, + } = err as WalletClientError; + throw new WalletError(title, code, message); } } @@ -92,11 +92,12 @@ export class JsonRpcConnector implements VegaConnector { await this.client.ConnectWallet(); return null; } catch (err) { - const clientErr = - err instanceof WalletClientError && err.code === 3001 - ? ClientErrors.REQUEST_REJECTED - : ClientErrors.INVALID_RESPONSE; - throw clientErr; + const { + code = ClientErrors.UNKNOWN.code, + message = ClientErrors.UNKNOWN.message, + title, + } = err as WalletClientError; + throw new WalletError(title, code, message); } } @@ -111,7 +112,12 @@ export class JsonRpcConnector implements VegaConnector { const { result } = await this.client.ListKeys(); return result.keys; } catch (err) { - throw ClientErrors.INVALID_RESPONSE; + const { + code = ClientErrors.UNKNOWN.code, + message = ClientErrors.UNKNOWN.message, + title, + } = err as WalletClientError; + throw new WalletError(title, code, message); } } @@ -147,15 +153,21 @@ export class JsonRpcConnector implements VegaConnector { try { const result = await fetch(`${this._url}/api/${this.version}/methods`); if (!result.ok) { - const err = ClientErrors.INVALID_WALLET; - err.data = t( - `The wallet running at ${this._url} is not supported. Required version is ${this.version}` + const sent1 = t( + 'The version of the wallet service running at %s is not supported.', + this._url as string ); - throw err; + const sent2 = t( + 'Update the wallet software to a version that expose the API %s.', + this.version + ); + const data = `${sent1}\n ${sent2}`; + const title = t('Wallet version invalid'); + throw new WalletError(title, ClientErrors.INVALID_WALLET.code, data); } return true; } catch (err) { - if (err instanceof WalletError) { + if (err instanceof WalletClientError) { throw err; } diff --git a/libs/wallet/src/connectors/vega-connector.ts b/libs/wallet/src/connectors/vega-connector.ts index 6702a9761..7128b5a8f 100644 --- a/libs/wallet/src/connectors/vega-connector.ts +++ b/libs/wallet/src/connectors/vega-connector.ts @@ -1,3 +1,4 @@ +import { WalletClientError } from '@vegaprotocol/wallet-client'; import type * as Schema from '@vegaprotocol/types'; export interface DelegateSubmissionBody { @@ -329,14 +330,11 @@ export interface TransactionResponse { receivedAt: string; sentAt: string; } -export class WalletError { - message: string; - code: number; - data?: string; +export class WalletError extends WalletClientError { + data: string; - constructor(message: string, code: number, data?: string) { - this.message = message; - this.code = code; + constructor(message: string, code: number, data = 'Wallet error') { + super({ code, message, data }); this.data = data; } } diff --git a/libs/wallet/src/provider.tsx b/libs/wallet/src/provider.tsx index 07b049fae..2d2c3c4cc 100644 --- a/libs/wallet/src/provider.tsx +++ b/libs/wallet/src/provider.tsx @@ -1,6 +1,7 @@ import { LocalStorage } from '@vegaprotocol/react-helpers'; import type { ReactNode } from 'react'; import { useCallback, useMemo, useRef, useState } from 'react'; +import { WalletClientError } from '@vegaprotocol/wallet-client'; import type { VegaWalletContextShape } from '.'; import type { PubKey, @@ -9,7 +10,6 @@ import type { } from './connectors/vega-connector'; import { VegaWalletContext } from './context'; import { WALLET_KEY } from './storage'; -import { WalletError } from './connectors/vega-connector'; import { ViewConnector } from './connectors'; interface VegaWalletProviderProps { @@ -53,7 +53,7 @@ export const VegaWalletProvider = ({ children }: VegaWalletProviderProps) => { return null; } } catch (err) { - if (err instanceof WalletError) { + if (err instanceof WalletClientError) { throw err; } return null; diff --git a/libs/wallet/src/use-json-rpc-connect.ts b/libs/wallet/src/use-json-rpc-connect.ts index 2bf2939a9..c715a6c13 100644 --- a/libs/wallet/src/use-json-rpc-connect.ts +++ b/libs/wallet/src/use-json-rpc-connect.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react'; +import { WalletClientError } from '@vegaprotocol/wallet-client'; import type { JsonRpcConnector } from './connectors'; import { ClientErrors } from './connectors'; -import { WalletError } from './connectors'; import { useVegaWallet } from './use-vega-wallet'; export enum Status { @@ -18,7 +18,7 @@ export enum Status { export const useJsonRpcConnect = (onConnect: () => void) => { const { connect } = useVegaWallet(); const [status, setStatus] = useState(Status.Idle); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const attemptConnect = useCallback( async (connector: JsonRpcConnector, appChainId: string) => { @@ -56,7 +56,7 @@ export const useJsonRpcConnect = (onConnect: () => void) => { setStatus(Status.Connected); onConnect(); } catch (err) { - if (err instanceof WalletError) { + if (err instanceof WalletClientError) { setError(err); } setStatus(Status.Error); diff --git a/libs/wallet/src/use-vega-transaction-manager.tsx b/libs/wallet/src/use-vega-transaction-manager.tsx index 67f0cd488..0c5b82a3f 100644 --- a/libs/wallet/src/use-vega-transaction-manager.tsx +++ b/libs/wallet/src/use-vega-transaction-manager.tsx @@ -1,7 +1,6 @@ import { useVegaWallet } from './use-vega-wallet'; import { useEffect, useRef } from 'react'; import { ClientErrors } from './connectors'; -import { WalletError } from './connectors'; import { VegaTxStatus } from './use-vega-transaction'; import { useVegaTransactionStore } from './use-vega-transaction-store'; import { WalletClientError } from '@vegaprotocol/wallet-client'; @@ -40,10 +39,7 @@ export const useVegaTransactionManager = () => { }) .catch((err) => { update(transaction.id, { - error: - err instanceof WalletError || err instanceof WalletClientError - ? err - : ClientErrors.UNKNOWN, + error: err instanceof WalletClientError ? err : ClientErrors.UNKNOWN, status: VegaTxStatus.Error, }); }); diff --git a/libs/wallet/src/use-vega-transaction.spec.tsx b/libs/wallet/src/use-vega-transaction.spec.tsx index 10ce51932..7e804e074 100644 --- a/libs/wallet/src/use-vega-transaction.spec.tsx +++ b/libs/wallet/src/use-vega-transaction.spec.tsx @@ -98,10 +98,14 @@ describe('useVegaTransaction', () => { }); expect(result.current.transaction.status).toEqual(VegaTxStatus.Error); expect(result.current.transaction.error).toHaveProperty( - 'message', + 'title', 'Something went wrong' ); expect(result.current.transaction.error).toHaveProperty('code', 105); + expect(result.current.transaction.error).toHaveProperty( + 'message', + 'Unknown error occurred' + ); expect(result.current.transaction.error).toHaveProperty( 'data', 'Unknown error occurred' diff --git a/libs/wallet/src/use-vega-transaction.tsx b/libs/wallet/src/use-vega-transaction.tsx index 118962d93..a6f414c83 100644 --- a/libs/wallet/src/use-vega-transaction.tsx +++ b/libs/wallet/src/use-vega-transaction.tsx @@ -6,8 +6,6 @@ import { VegaTransactionDialog } from './vega-transaction-dialog'; import type { Intent } from '@vegaprotocol/ui-toolkit'; import type { Transaction } from './connectors'; import { ClientErrors } from './connectors'; -import { WalletError } from './connectors'; -import type { WalletClientError } from '@vegaprotocol/wallet-client'; export interface DialogProps { intent?: Intent; @@ -26,7 +24,7 @@ export enum VegaTxStatus { export interface VegaTxState { status: VegaTxStatus; - error: WalletError | WalletClientError | Error | null; + error: Error | null; txHash: string | null; signature: string | null; dialogOpen: boolean; @@ -90,14 +88,9 @@ export const useVegaTransaction = () => { return null; } catch (err) { - const error = - err instanceof WalletError - ? err - : err instanceof Error - ? err - : ClientErrors.UNKNOWN; + const error = err instanceof Error ? err : ClientErrors.UNKNOWN; setTransaction({ - error: error, + error, status: VegaTxStatus.Error, }); return null; diff --git a/libs/wallet/src/use-vega-wallet.ts b/libs/wallet/src/use-vega-wallet.ts index a018fce0e..0c5aa68fa 100644 --- a/libs/wallet/src/use-vega-wallet.ts +++ b/libs/wallet/src/use-vega-wallet.ts @@ -10,9 +10,9 @@ export function useVegaWallet() { } export function useReconnectVegaWallet() { - const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({ - openVegaWalletDialog: store.openVegaWalletDialog, - })); + const openVegaWalletDialog = useVegaWalletDialogStore( + (store) => store.openVegaWalletDialog + ); const { disconnect } = useVegaWallet(); const reconnect = useCallback(async () => { await disconnect(); diff --git a/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx b/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx index 641b7ba19..e71606db4 100644 --- a/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx +++ b/libs/wallet/src/vega-transaction-dialog/vega-transaction-dialog.tsx @@ -2,7 +2,7 @@ import { Networks, useEnvironment } from '@vegaprotocol/environment'; import { t } from '@vegaprotocol/react-helpers'; import { Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit'; import type { ReactNode } from 'react'; -import { WalletError } from '../connectors'; +import { WalletClientError } from '@vegaprotocol/wallet-client'; import type { VegaTxState } from '../use-vega-transaction'; import { VegaTxStatus } from '../use-vega-transaction'; @@ -107,18 +107,13 @@ export const VegaDialog = ({ transaction }: VegaDialogProps) => { } if (transaction.status === VegaTxStatus.Error) { - content = ( -
- {transaction.error instanceof WalletError && ( -

- {transaction.error.message}: {transaction.error.data} -

- )} - {transaction.error instanceof Error && ( -

{transaction.error.message}

- )} -
- ); + let messageText = ''; + if (transaction.error instanceof WalletClientError) { + messageText = `${transaction.error.title}: ${transaction.error.message}`; + } else if (transaction.error instanceof Error) { + messageText = transaction.error.message; + } + content =
{messageText}
; } if (transaction.status === VegaTxStatus.Pending) { diff --git a/package.json b/package.json index 6779bbe18..898f5e226 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@sentry/nextjs": "^6.19.3", "@sentry/react": "^6.19.2", "@sentry/tracing": "^6.19.2", - "@vegaprotocol/wallet-client": "0.1.8", + "@vegaprotocol/wallet-client": "0.1.9", "@walletconnect/ethereum-provider": "^1.7.5", "@web3-react/core": "8.0.20-beta.0", "@web3-react/metamask": "8.0.16-beta.0", diff --git a/yarn.lock b/yarn.lock index 41936e6bc..47a1aa6f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7321,10 +7321,10 @@ "@typescript-eslint/types" "5.40.0" eslint-visitor-keys "^3.3.0" -"@vegaprotocol/wallet-client@0.1.8": - version "0.1.8" - resolved "https://registry.yarnpkg.com/@vegaprotocol/wallet-client/-/wallet-client-0.1.8.tgz#38ca8566d78b9f6694b12ad9364bb34d6482935d" - integrity sha512-FVvDvvlccKyXn0ujhivPUCVnkZYQJxtI1q8OgipNnbmAjU1mLyeuRTBw0Isu330yPI1KppNbW6Qicd8OTHBmxw== +"@vegaprotocol/wallet-client@0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@vegaprotocol/wallet-client/-/wallet-client-0.1.9.tgz#8c6a71c8b2222b3de5d73cade8fc6db57e332de9" + integrity sha512-oacfJGT0zHM+1If4I/pgIWi7zzU/3uHy4+sjuFEh8pWI8bWBzCF+mHbOGA6iTM+5/5mhayMFc+YZ9GexH1sBnQ== dependencies: express "4.18.2" nanoid "3.3.4"