diff --git a/apps/trading/components/welcome-dialog/welcome-dialog.tsx b/apps/trading/components/welcome-dialog/welcome-dialog.tsx index f7bf46661..1578af15a 100644 --- a/apps/trading/components/welcome-dialog/welcome-dialog.tsx +++ b/apps/trading/components/welcome-dialog/welcome-dialog.tsx @@ -13,7 +13,7 @@ import { Routes } from '../../lib/links'; import { WelcomeDialogContent } from './welcome-dialog-content'; import { useOnboardingStore } from './use-get-onboarding-step'; import { ensureSuffix } from '@vegaprotocol/utils'; -import { type ConnectorType } from '@vegaprotocol/wallet'; +import { ConnectorErrors, type ConnectorType } from '@vegaprotocol/wallet'; /** * A list of paths on which the welcome dialog should be omitted. @@ -106,8 +106,10 @@ const ConnectionOptions = ({ onConnect }: { onConnect: () => void }) => { ); })} - {error && !error.includes('the user rejected') && ( -
{error}
+ {error && error.code !== ConnectorErrors.userRejected.code && ( ++ {error.message} +
)} ); diff --git a/libs/wallet-react/src/components/connect-dialog/connect-dialog.tsx b/libs/wallet-react/src/components/connect-dialog/connect-dialog.tsx index eadf02a83..3e755453b 100644 --- a/libs/wallet-react/src/components/connect-dialog/connect-dialog.tsx +++ b/libs/wallet-react/src/components/connect-dialog/connect-dialog.tsx @@ -5,7 +5,11 @@ import { VegaIcon, VegaIconNames, } from '@vegaprotocol/ui-toolkit'; -import { type ConnectorType, type Status } from '@vegaprotocol/wallet'; +import { + ConnectorErrors, + type ConnectorType, + type Status, +} from '@vegaprotocol/wallet'; import { useWallet } from '../../hooks/use-wallet'; import { useConnect } from '../../hooks/use-connect'; import classNames from 'classnames'; @@ -59,8 +63,10 @@ export const ConnectionOptions = ({ onConnect }: { onConnect: () => void }) => { ); })} - {error && !error.includes('the user rejected') && ( -{error}
+ {error && error.code !== ConnectorErrors.userRejected.code && ( ++ {error.message} +
)} ); diff --git a/libs/wallet/src/connectors/index.ts b/libs/wallet/src/connectors/index.ts index af3f8555a..df67927a4 100644 --- a/libs/wallet/src/connectors/index.ts +++ b/libs/wallet/src/connectors/index.ts @@ -2,3 +2,24 @@ export { InjectedConnector } from './injected-connector'; export { SnapConnector } from './snap-connector'; export { JsonRpcConnector } from './json-rpc-connector'; export { ReadOnlyConnector } from './read-only-connector'; + +export class ConnectorError extends Error { + code: number; + + constructor(message: string, code: number) { + super(message); + this.code = code; + } +} + +export const ConnectorErrors = { + userRejected: new ConnectorError('user rejected', 0), + noConnector: new ConnectorError('no connector', 1), + connect: new ConnectorError('failed to connect', 2), + disconnect: new ConnectorError('failed to disconnect', 3), + chainId: new ConnectorError('incorrect chain', 4), + listKeys: new ConnectorError('failed to list keys', 5), + isConnected: new ConnectorError('failed to check connection', 6), + sendTransaction: new ConnectorError('failed to send transaction', 7), + unknown: new ConnectorError('unknown error', 8), +}; diff --git a/libs/wallet/src/connectors/injected-connector.ts b/libs/wallet/src/connectors/injected-connector.ts index 2e3dcd3d2..b4a96122b 100644 --- a/libs/wallet/src/connectors/injected-connector.ts +++ b/libs/wallet/src/connectors/injected-connector.ts @@ -1,4 +1,9 @@ -import { type TransactionParams, type Connector } from '../types'; +import { ConnectorErrors } from '.'; +import { + type TransactionParams, + type Connector, + type VegaWalletEvent, +} from '../types'; export class InjectedConnector implements Connector { readonly id = 'injected'; @@ -13,9 +18,7 @@ export class InjectedConnector implements Connector { await window.vega.connectWallet({ chainId }); return { success: true }; } catch (err) { - return { - error: err instanceof Error ? err.message : 'failed to connect', - }; + throw ConnectorErrors.connect; } } @@ -24,7 +27,7 @@ export class InjectedConnector implements Connector { await window.vega.disconnectWallet(); return { success: true }; } catch (err) { - return { error: 'failed to disconnect' }; + throw ConnectorErrors.disconnect; } } @@ -34,7 +37,7 @@ export class InjectedConnector implements Connector { const res = await window.vega.getChainId(); return { chainId: res.chainID }; } catch (err) { - return { error: 'failed to get chain id' }; + throw ConnectorErrors.chainId; } } @@ -43,7 +46,7 @@ export class InjectedConnector implements Connector { const res = await window.vega.listKeys(); return res.keys; } catch (err) { - return { error: 'failed to list keys' }; + throw ConnectorErrors.listKeys; } } @@ -52,7 +55,7 @@ export class InjectedConnector implements Connector { const res = await window.vega.isConnected(); return { connected: res }; } catch (err) { - return { error: 'failed to check isConnected' }; + throw ConnectorErrors.isConnected; } } @@ -67,16 +70,15 @@ export class InjectedConnector implements Connector { sentAt: res.sentAt, }; } catch (err) { - console.error(err); - return { error: 'failed to send transaction' }; + throw ConnectorErrors.isConnected; } } - on(event: 'client.disconnected', callback: () => void) { + on(event: VegaWalletEvent, callback: () => void) { window.vega.on(event, callback); } - off(event: 'client.disconnected') { + off(event: VegaWalletEvent) { window.vega.off(event); } } diff --git a/libs/wallet/src/connectors/json-rpc-connector.ts b/libs/wallet/src/connectors/json-rpc-connector.ts index c390ed1fc..bd0f1b891 100644 --- a/libs/wallet/src/connectors/json-rpc-connector.ts +++ b/libs/wallet/src/connectors/json-rpc-connector.ts @@ -5,6 +5,7 @@ import { type TransactionParams, type Store, } from '../types'; +import { ConnectorError, ConnectorErrors } from '.'; type JsonRpcConnectorConfig = { url: string; token?: string }; @@ -31,11 +32,11 @@ export class JsonRpcConnector implements Connector { const chainRes = await this.getChainId(); if ('error' in chainRes) { - return { error: chainRes.error }; + throw ConnectorErrors.chainId; } if (chainRes.chainId !== desiredChainId) { - return { error: 'incorrect chain' }; + throw ConnectorErrors.chainId; } if (!this.token) { @@ -49,15 +50,16 @@ export class JsonRpcConnector implements Connector { const token = response.headers.get('Authorization'); if (!response.ok) { - if ('error' in data) { - return { error: data.error.data }; + // TODO: extend ConnectorError with data on jsonrpc error + if ('error' in data && data.error.code === 3001) { + // user rejected + throw ConnectorErrors.userRejected; } - - return { error: 'failed to connect' }; + throw ConnectorErrors.connect; } if (!token) { - return { error: 'failed to connect' }; + throw ConnectorErrors.connect; } this.token = token; @@ -65,7 +67,11 @@ export class JsonRpcConnector implements Connector { return { success: true }; } catch (err) { - return { error: 'wallet not running' }; + if (err instanceof ConnectorError) { + throw err; + } + + throw ConnectorErrors.noConnector; } } @@ -74,7 +80,7 @@ export class JsonRpcConnector implements Connector { await this.request(JsonRpcMethod.DisconnectWallet); return { success: true }; } catch (err) { - return { error: 'wallet not running' }; + throw ConnectorErrors.disconnect; } } @@ -85,7 +91,7 @@ export class JsonRpcConnector implements Connector { return { chainId: data.result.chainID }; } catch (err) { - return { error: 'wallet not running' }; + throw ConnectorErrors.chainId; } } @@ -94,7 +100,7 @@ export class JsonRpcConnector implements Connector { const { data } = await this.request(JsonRpcMethod.ListKeys); return data.result.keys as Array<{ publicKey: string; name: string }>; } catch (err) { - return { error: 'wallet not running' }; + throw ConnectorErrors.noConnector; } } @@ -103,7 +109,7 @@ export class JsonRpcConnector implements Connector { await this.listKeys(); return { connected: true }; } catch (err) { - return { error: 'wallet not running' }; + throw ConnectorErrors.noConnector; } } @@ -114,6 +120,8 @@ export class JsonRpcConnector implements Connector { params ); + // TODO handle not okay responses but wallet is running + return { transactionHash: data.result.transactionHash, signature: data.result.transaction.signature.value, @@ -121,7 +129,11 @@ export class JsonRpcConnector implements Connector { sentAt: data.result.sentAt, }; } catch (err) { - return { error: 'wallet not running' }; + if (err instanceof ConnectorError) { + throw err; + } + + throw ConnectorErrors.noConnector; } } diff --git a/libs/wallet/src/connectors/read-only-connector.ts b/libs/wallet/src/connectors/read-only-connector.ts index b61f34aff..94e75fd5a 100644 --- a/libs/wallet/src/connectors/read-only-connector.ts +++ b/libs/wallet/src/connectors/read-only-connector.ts @@ -1,6 +1,7 @@ import { type StoreApi } from 'zustand'; import { type Store, type Connector } from '../types'; import { isValidVegaPublicKey } from '@vegaprotocol/utils'; +import { ConnectorError, ConnectorErrors } from '.'; export class ReadOnlyConnector implements Connector { readonly id = 'readOnly'; @@ -20,20 +21,33 @@ export class ReadOnlyConnector implements Connector { } async connectWallet() { - if (!this.pubKey) { + try { + if (this.pubKey) { + return { success: true }; + } + const value = window.prompt('Enter public key'); if (value === null) { - return { error: 'the user rejected' }; + throw ConnectorErrors.userRejected; } + // TODO: extend connect error with messaging for invalid public key if (!isValidVegaPublicKey(value)) { - return { error: 'invalid public key' }; + // throw new Error('invalid public key'); + throw ConnectorErrors.connect; } this.pubKey = value; + + return { success: true }; + } catch (err) { + if (err instanceof ConnectorError) { + throw err; + } + + throw ConnectorErrors.connect; } - return { success: true }; } async disconnectWallet() { @@ -42,14 +56,12 @@ export class ReadOnlyConnector implements Connector { } async getChainId() { - return { - error: `You are connected in a view only state for public key: ${this.pubKey}`, - }; + throw ConnectorErrors.chainId; } async listKeys() { if (!this.pubKey) { - return { error: 'failed to list keys' }; + throw ConnectorErrors.listKeys; } return [ { @@ -69,9 +81,12 @@ export class ReadOnlyConnector implements Connector { // @ts-ignore deliberate fail async sendTransaction() { - return { - error: `You are connected in a view only state for public key: ${this.pubKey}. In order to send transactions you must connect to a real wallet.`, - }; + // TODO: extend send tx with more information + // + // return { + // error: `You are connected in a view only state for public key: ${this.pubKey}. In order to send transactions you must connect to a real wallet.`, + // }; + throw ConnectorErrors.sendTransaction; } on() { diff --git a/libs/wallet/src/connectors/snap-connector.ts b/libs/wallet/src/connectors/snap-connector.ts index 74a9788ba..6997d49a7 100644 --- a/libs/wallet/src/connectors/snap-connector.ts +++ b/libs/wallet/src/connectors/snap-connector.ts @@ -1,3 +1,4 @@ +import { ConnectorError, ConnectorErrors } from '.'; import { JsonRpcMethod, type Connector, @@ -41,20 +42,22 @@ export class SnapConnector implements Connector { const { chainId } = await this.getChainId(); if (chainId !== desiredChainId) { - throw new Error('incorrect chain id'); + throw ConnectorErrors.chainId; } return { success: true }; } catch (err) { - return { - error: err instanceof Error ? err.message : 'failed to connect', - }; + if (err instanceof ConnectorError) { + throw err; + } + + throw ConnectorErrors.noConnector; } } + // TODO: check how snaps should actually disconnect async disconnectWallet() { return { success: true }; - // return { error: 'failed to disconnect' }; } // deprecated, pass chain on connect @@ -65,7 +68,7 @@ export class SnapConnector implements Connector { }); return { chainId: res.chainID }; } catch (err) { - return { error: 'failed to get chain id' }; + throw ConnectorErrors.chainId; } } @@ -74,13 +77,13 @@ export class SnapConnector implements Connector { const res = await this.invokeSnap(JsonRpcMethod.ListKeys); return res.keys as Array<{ publicKey: string; name: string }>; } catch (err) { - return { error: 'failed to list keys' }; + throw ConnectorErrors.listKeys; } } async isConnected() { console.warn('isConnected not implemented'); - return { error: 'failed to check if connected' }; + throw ConnectorErrors.isConnected; } async sendTransaction(params: TransactionParams) { @@ -99,8 +102,7 @@ export class SnapConnector implements Connector { sentAt: res.sentAt, }; } catch (err) { - console.error(err); - return { error: 'failed to send transaction' }; + throw ConnectorErrors.sendTransaction; } } diff --git a/libs/wallet/src/index.ts b/libs/wallet/src/index.ts index 82137d2d6..8d6584181 100644 --- a/libs/wallet/src/index.ts +++ b/libs/wallet/src/index.ts @@ -16,6 +16,8 @@ export { SnapConnector, JsonRpcConnector, ReadOnlyConnector, + ConnectorError, + ConnectorErrors, } from './connectors'; // Utils diff --git a/libs/wallet/src/types.ts b/libs/wallet/src/types.ts index 0f0cfd359..af126e3c1 100644 --- a/libs/wallet/src/types.ts +++ b/libs/wallet/src/types.ts @@ -4,6 +4,7 @@ import { type TransactionResponse, } from './transaction-types'; import { type Chain } from './chains'; +import { type ConnectorError } from './connectors'; export enum JsonRpcMethod { ConnectWallet = 'client.connect_wallet', @@ -14,17 +15,13 @@ export enum JsonRpcMethod { GetChainId = 'client.get_chain_id', } -export interface IWalletError { - error: string; -} - export interface TransactionParams { publicKey: string; transaction: Transaction; sendingMode: 'TYPE_SYNC'; } -type VegaWalletEvent = 'client.disconnected'; +export type VegaWalletEvent = 'client.disconnected'; export type ConnectorType = | 'injected' @@ -39,16 +36,12 @@ export interface Connector { readonly description: string; bindStore(state: StoreApi