feat: make consistent errors for connectors
This commit is contained in:
@@ -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 }) => {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{error && !error.includes('the user rejected') && (
|
||||
<p className="text-danger text-sm first-letter:uppercase">{error}</p>
|
||||
{error && error.code !== ConnectorErrors.userRejected.code && (
|
||||
<p className="text-danger text-sm first-letter:uppercase">
|
||||
{error.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 }) => {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{error && !error.includes('the user rejected') && (
|
||||
<p className="text-danger text-sm first-letter:uppercase">{error}</p>
|
||||
{error && error.code !== ConnectorErrors.userRejected.code && (
|
||||
<p className="text-danger text-sm first-letter:uppercase">
|
||||
{error.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ export {
|
||||
SnapConnector,
|
||||
JsonRpcConnector,
|
||||
ReadOnlyConnector,
|
||||
ConnectorError,
|
||||
ConnectorErrors,
|
||||
} from './connectors';
|
||||
|
||||
// Utils
|
||||
|
||||
+10
-21
@@ -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<Store>): void;
|
||||
connectWallet(chainId?: string): Promise<{ success: boolean } | IWalletError>;
|
||||
disconnectWallet(): Promise<{ success: boolean } | IWalletError>;
|
||||
getChainId(): Promise<{ chainId: string } | IWalletError>;
|
||||
listKeys(): Promise<
|
||||
Array<{ publicKey: string; name: string }> | IWalletError
|
||||
>;
|
||||
isConnected(): Promise<{ connected: boolean } | IWalletError>;
|
||||
sendTransaction(
|
||||
params: TransactionParams
|
||||
): Promise<TransactionResponse | IWalletError>;
|
||||
connectWallet(chainId?: string): Promise<{ success: boolean }>;
|
||||
disconnectWallet(): Promise<{ success: boolean }>;
|
||||
getChainId(): Promise<{ chainId: string }>;
|
||||
listKeys(): Promise<Array<{ publicKey: string; name: string }>>;
|
||||
isConnected(): Promise<{ connected: boolean }>;
|
||||
sendTransaction(params: TransactionParams): Promise<TransactionResponse>;
|
||||
on(event: VegaWalletEvent, callback: () => void): void;
|
||||
off(event: VegaWalletEvent): void;
|
||||
}
|
||||
@@ -65,14 +58,12 @@ export type CoreStore = {
|
||||
status: Status;
|
||||
current: ConnectorType | undefined;
|
||||
keys: Key[];
|
||||
// setKeys: (keys: Key[]) => void;
|
||||
error: string | undefined;
|
||||
error: ConnectorError | undefined;
|
||||
jsonRpcToken: string | undefined;
|
||||
};
|
||||
|
||||
export type SingleKeyStore = {
|
||||
pubKey: string | undefined;
|
||||
// setPubKey: (key: string) => void;
|
||||
};
|
||||
|
||||
export type Store = CoreStore & SingleKeyStore;
|
||||
@@ -89,9 +80,7 @@ export type Wallet = {
|
||||
connect: (id: ConnectorType) => Promise<{ success: boolean } | undefined>;
|
||||
disconnect: () => Promise<{ success: boolean } | undefined>;
|
||||
refreshKeys: () => Promise<void>;
|
||||
sendTransaction: (
|
||||
params: TransactionParams
|
||||
) => Promise<TransactionResponse | IWalletError>;
|
||||
sendTransaction: (params: TransactionParams) => Promise<TransactionResponse>;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type Connector,
|
||||
type ConnectorType,
|
||||
} from './types';
|
||||
import { ConnectorError, ConnectorErrors } from './connectors';
|
||||
|
||||
// get/set functions are not used in the slices so these
|
||||
// can be plain objects
|
||||
@@ -78,31 +79,20 @@ export function createConfig(cfg: Config): Wallet {
|
||||
try {
|
||||
store.setState({ status: 'connecting', current: id, error: undefined });
|
||||
|
||||
const connectWalletRes = await connector.connectWallet(
|
||||
store.getState().chainId
|
||||
);
|
||||
|
||||
if ('error' in connectWalletRes) {
|
||||
throw new Error(connectWalletRes.error);
|
||||
}
|
||||
|
||||
const listKeysRes = await connector.listKeys();
|
||||
|
||||
if ('error' in listKeysRes) {
|
||||
throw new Error('failed to get keys');
|
||||
}
|
||||
await connector.connectWallet(store.getState().chainId);
|
||||
const keys = await connector.listKeys();
|
||||
|
||||
// TODO: this shouldnt be in core as we dont want to enforce single key usage
|
||||
const storedPubKey = store.getState().pubKey;
|
||||
let defaultKey;
|
||||
if (listKeysRes.find((k) => k.publicKey === storedPubKey)) {
|
||||
if (keys.find((k) => k.publicKey === storedPubKey)) {
|
||||
defaultKey = storedPubKey;
|
||||
} else {
|
||||
defaultKey = listKeysRes[0].publicKey;
|
||||
defaultKey = keys[0].publicKey;
|
||||
}
|
||||
|
||||
store.setState({
|
||||
keys: listKeysRes,
|
||||
keys,
|
||||
status: 'connected',
|
||||
pubKey: defaultKey,
|
||||
});
|
||||
@@ -117,7 +107,7 @@ export function createConfig(cfg: Config): Wallet {
|
||||
status: 'disconnected',
|
||||
current: undefined,
|
||||
keys: [],
|
||||
error: err instanceof Error ? err.message : 'failed to connect',
|
||||
error: err instanceof ConnectorError ? err : ConnectorErrors.unknown,
|
||||
});
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user