Skip to content

Commit 9693198

Browse files
committed
feat: add custom WalletError class to normalise wallet errors
- Closes #51 - Closes #54
1 parent 4b3a751 commit 9693198

14 files changed

+126
-75
lines changed

src/wallet/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export {
1313
type ChainInfo,
1414
type EventCallback,
1515
} from "./wallets/WalletController";
16+
export { WalletError } from "./wallets/WalletError";
1617
export { CompassController } from "./wallets/compass/CompassController";
1718
export { CosmostationController } from "./wallets/cosmostation/CosmostationController";
1819
export { KeplrController } from "./wallets/keplr/KeplrController";

src/wallet/wallets/WalletError.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Custom error class which wraps around an error thrown by a wallet.
3+
*/
4+
export class WalletError extends Error {
5+
/**
6+
* Holds the original error and type thrown by the wallet.
7+
*/
8+
public raw: unknown;
9+
10+
constructor(message: string, raw: unknown) {
11+
super(message);
12+
this.name = "WalletError";
13+
this.raw = raw;
14+
}
15+
16+
/**
17+
* Returns the result of the `promise` if it resolves successfully, normalising
18+
* any errors thrown into a `WalletError` instance.
19+
*
20+
* It is best to wrap all wallet API calls with this function as some wallets
21+
* throw other data types other than actual `Error` instances.
22+
*/
23+
public static async wrap<T>(promise: Promise<T>): Promise<T> {
24+
try {
25+
return await promise;
26+
} catch (err) {
27+
if (typeof err === "string") {
28+
throw new WalletError(err, err);
29+
}
30+
if (err instanceof Error) {
31+
throw new WalletError(err.message, err);
32+
}
33+
throw new WalletError("unknown error", err);
34+
}
35+
}
36+
}

src/wallet/wallets/compass/CompassController.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { WalletConnectV1 } from "../../walletconnect/WalletConnectV1";
77
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
88
import { ConnectedWallet } from "../ConnectedWallet";
99
import { ChainInfo, WalletController } from "../WalletController";
10+
import { WalletError } from "../WalletError";
1011
import { CompassExtension } from "./CompassExtension";
1112

1213
export class CompassController extends WalletController {
@@ -35,9 +36,11 @@ export class CompassController extends WalletController {
3536
if (!ext) {
3637
throw new Error("Compass extension is not installed");
3738
}
38-
await ext.enable(chains.map(({ chainId }) => chainId));
39+
await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId)));
3940
for (const { chainId, rpc, gasPrice } of Object.values(chains)) {
40-
const { bech32Address, pubKey, isNanoLedger } = await ext.getKey(chainId);
41+
const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap(
42+
ext.getKey(chainId)
43+
);
4144
const key = new Secp256k1PubKey({
4245
chainId,
4346
key: pubKey,

src/wallet/wallets/cosmostation/CosmostationController.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { onWindowEvent } from "../../utils/window";
77
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
88
import { ConnectedWallet } from "../ConnectedWallet";
99
import { ChainInfo, WalletController } from "../WalletController";
10+
import { WalletError } from "../WalletError";
1011
import { CosmostationExtension } from "./CosmostationExtension";
1112
import { CosmostationWalletConnectV2 } from "./CosmostationWalletConnectV2";
1213

@@ -33,10 +34,14 @@ export class CosmostationController extends WalletController {
3334
chains: ChainInfo<T>[]
3435
) {
3536
const wallets = new Map<T, ConnectedWallet>();
36-
await this.wc.connect(chains.map(({ chainId }) => chainId));
37+
await WalletError.wrap(
38+
this.wc.connect(chains.map(({ chainId }) => chainId))
39+
);
3740
for (let i = 0; i < chains.length; i++) {
3841
const { chainId, rpc, gasPrice } = chains[i];
39-
const { pubkey, address } = await this.wc.getAccount(chainId);
42+
const { pubkey, address } = await WalletError.wrap(
43+
this.wc.getAccount(chainId)
44+
);
4045
const key = new Secp256k1PubKey({
4146
chainId,
4247
key: base64.decode(pubkey),
@@ -64,9 +69,11 @@ export class CosmostationController extends WalletController {
6469
if (!ext) {
6570
throw new Error("Cosmostation extension is not installed");
6671
}
67-
await ext.enable(chains.map(({ chainId }) => chainId));
72+
await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId)));
6873
for (const { chainId, rpc, gasPrice } of Object.values(chains)) {
69-
const { bech32Address, pubKey, isNanoLedger } = await ext.getKey(chainId);
74+
const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap(
75+
ext.getKey(chainId)
76+
);
7077
const key = new Secp256k1PubKey({
7178
chainId,
7279
key: pubKey,

src/wallet/wallets/keplr/KeplrController.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { onWindowEvent } from "../../utils/window";
77
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
88
import { ConnectedWallet } from "../ConnectedWallet";
99
import { ChainInfo, WalletController } from "../WalletController";
10+
import { WalletError } from "../WalletError";
1011
import { KeplrExtension } from "./KeplrExtension";
1112
import { KeplrWalletConnectV2 } from "./KeplrWalletConnectV2";
1213

@@ -33,10 +34,14 @@ export class KeplrController extends WalletController {
3334
chains: ChainInfo<T>[]
3435
) {
3536
const wallets = new Map<T, ConnectedWallet>();
36-
await this.wc.connect(chains.map(({ chainId }) => chainId));
37+
await WalletError.wrap(
38+
this.wc.connect(chains.map(({ chainId }) => chainId))
39+
);
3740
for (let i = 0; i < chains.length; i++) {
3841
const { chainId, rpc, gasPrice } = chains[i];
39-
const { pubkey, address } = await this.wc.getAccount(chainId);
42+
const { pubkey, address } = await WalletError.wrap(
43+
this.wc.getAccount(chainId)
44+
);
4045
const key = new Secp256k1PubKey({
4146
key: base64.decode(pubkey),
4247
chainId,
@@ -64,9 +69,11 @@ export class KeplrController extends WalletController {
6469
if (!ext) {
6570
throw new Error("Keplr extension is not installed");
6671
}
67-
await ext.enable(chains.map(({ chainId }) => chainId));
72+
await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId)));
6873
for (const { chainId, rpc, gasPrice } of Object.values(chains)) {
69-
const { bech32Address, pubKey, isNanoLedger } = await ext.getKey(chainId);
74+
const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap(
75+
ext.getKey(chainId)
76+
);
7077
const key = new Secp256k1PubKey({
7178
key: pubKey,
7279
chainId,

src/wallet/wallets/keplr/KeplrExtension.ts

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
SignArbitraryResponse,
2121
UnsignedTx,
2222
} from "../ConnectedWallet";
23+
import { WalletError } from "../WalletError";
2324

2425
export class KeplrExtension extends ConnectedWallet {
2526
private readonly ext: Keplr;
@@ -55,7 +56,7 @@ export class KeplrExtension extends ConnectedWallet {
5556
}
5657

5758
public async signArbitrary(data: string): Promise<SignArbitraryResponse> {
58-
const res = await this.normaliseError(
59+
const res = await WalletError.wrap(
5960
this.ext.signArbitrary(this.chainId, this.address, data)
6061
);
6162
return {
@@ -86,38 +87,17 @@ export class KeplrExtension extends ConnectedWallet {
8687
};
8788
let txRaw: TxRaw;
8889
if (this.useAmino) {
89-
const { signed, signature } = await this.normaliseError(
90+
const { signed, signature } = await WalletError.wrap(
9091
this.ext.signAmino(this.chainId, this.address, tx.toStdSignDoc(params))
9192
);
9293
txRaw = tx.toSignedAmino(signed, signature.signature);
9394
} else {
94-
const { signed, signature } = await this.normaliseError(
95+
const { signed, signature } = await WalletError.wrap(
9596
this.ext.signDirect(this.chainId, this.address, tx.toSignDoc(params))
9697
);
9798
txRaw = tx.toSignedDirect(signed, signature.signature);
9899
}
99100

100101
return RpcClient.broadcastTx(this.rpc, txRaw);
101102
}
102-
103-
/**
104-
* Returns the result of the `promise` if it resolves successfully, normalising
105-
* any errors thrown into a standard `Error` instance.
106-
*
107-
* It is best to wrap all wallet API calls with this function as some wallets
108-
* throw raw strings instead of actual `Error` instances.
109-
*/
110-
private async normaliseError<T>(promise: Promise<T>): Promise<T> {
111-
try {
112-
return await promise;
113-
} catch (err) {
114-
if (typeof err === "string") {
115-
throw new Error(err);
116-
}
117-
if (err instanceof Error) {
118-
throw err;
119-
}
120-
throw new Error("Unknown error: " + JSON.stringify(err));
121-
}
122-
}
123103
}

src/wallet/wallets/keplr/KeplrWalletConnectV2.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
CosmosTxV1beta1Fee as Fee,
1212
CosmosTxV1beta1TxRaw as TxRaw,
1313
} from "cosmes/protobufs";
14-
import { WalletName, WalletType } from "cosmes/wallet";
14+
import { WalletError, WalletName, WalletType } from "cosmes/wallet";
1515

1616
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
1717
import {
@@ -74,17 +74,13 @@ export class KeplrWalletConnectV2 extends ConnectedWallet {
7474
};
7575
let txRaw: TxRaw;
7676
if (this.useAmino) {
77-
const { signed, signature } = await this.wc.signAmino(
78-
this.chainId,
79-
this.address,
80-
tx.toStdSignDoc(params)
77+
const { signed, signature } = await WalletError.wrap(
78+
this.wc.signAmino(this.chainId, this.address, tx.toStdSignDoc(params))
8179
);
8280
txRaw = tx.toSignedAmino(signed, signature.signature);
8381
} else {
84-
const { signed, signature } = await this.wc.signDirect(
85-
this.chainId,
86-
this.address,
87-
tx.toSignDoc(params)
82+
const { signed, signature } = await WalletError.wrap(
83+
this.wc.signDirect(this.chainId, this.address, tx.toSignDoc(params))
8884
);
8985
txRaw = tx.toSignedDirect(signed, signature.signature);
9086
}

src/wallet/wallets/leap/LeapController.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { onWindowEvent } from "../../utils/window";
77
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
88
import { ConnectedWallet } from "../ConnectedWallet";
99
import { ChainInfo, WalletController } from "../WalletController";
10+
import { WalletError } from "../WalletError";
1011
import { LeapExtension } from "./LeapExtension";
1112
import { LeapWalletConnectV2 } from "./LeapWalletConnectV2";
1213

@@ -32,10 +33,14 @@ export class LeapController extends WalletController {
3233
chains: ChainInfo<T>[]
3334
) {
3435
const wallets = new Map<T, ConnectedWallet>();
35-
await this.wc.connect(chains.map(({ chainId }) => chainId));
36+
await WalletError.wrap(
37+
this.wc.connect(chains.map(({ chainId }) => chainId))
38+
);
3639
for (let i = 0; i < chains.length; i++) {
3740
const { chainId, rpc, gasPrice } = chains[i];
38-
const { pubkey, address } = await this.wc.getAccount(chainId);
41+
const { pubkey, address } = await WalletError.wrap(
42+
this.wc.getAccount(chainId)
43+
);
3944
const key = new Secp256k1PubKey({
4045
chainId,
4146
key: base64.decode(pubkey),
@@ -63,9 +68,11 @@ export class LeapController extends WalletController {
6368
if (!ext) {
6469
throw new Error("Leap extension is not installed");
6570
}
66-
await ext.enable(chains.map(({ chainId }) => chainId));
71+
await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId)));
6772
for (const { chainId, rpc, gasPrice } of Object.values(chains)) {
68-
const { bech32Address, pubKey, isNanoLedger } = await ext.getKey(chainId);
73+
const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap(
74+
ext.getKey(chainId)
75+
);
6976
const key = new Secp256k1PubKey({
7077
chainId,
7178
key: pubKey,

src/wallet/wallets/metamask-injective/MetamaskInjectiveController.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { WalletConnectV1 } from "../../walletconnect/WalletConnectV1";
1313
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
1414
import { ConnectedWallet } from "../ConnectedWallet";
1515
import { ChainInfo, WalletController } from "../WalletController";
16+
import { WalletError } from "../WalletError";
1617
import { MetamaskInjectiveExtension } from "./MetamaskInjectiveExtension";
1718
import { Ethereum } from "./types";
1819

@@ -48,22 +49,20 @@ export class MetamaskInjectiveController extends WalletController {
4849
throw new Error("MetaMask extension is not installed");
4950
}
5051

51-
const ethAddresses = await ext.request<string[]>({
52-
method: "eth_requestAccounts",
53-
});
52+
const ethAddresses = await WalletError.wrap(
53+
ext.request<string[]>({
54+
method: "eth_requestAccounts",
55+
})
56+
);
5457
const ethAddress = ethAddresses?.[0];
5558
if (!ethAddress) {
5659
throw new Error("Failed to connect to MetaMask");
5760
}
5861
const injAddress = translateEthToBech32Address(ethAddress, "inj");
5962

6063
const [chain] = chains;
61-
const pubKey = await this.getPubKey(
62-
ext,
63-
chain.chainId,
64-
chain.rpc,
65-
ethAddress,
66-
injAddress
64+
const pubKey = await WalletError.wrap(
65+
this.getPubKey(ext, chain.chainId, chain.rpc, ethAddress, injAddress)
6766
);
6867
const wallets = new Map<T, ConnectedWallet>();
6968
wallets.set(

src/wallet/wallets/metamask-injective/MetamaskInjectiveExtension.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
SignArbitraryResponse,
1717
UnsignedTx,
1818
} from "../ConnectedWallet";
19+
import { WalletError } from "../WalletError";
1920
import { Ethereum } from "./types";
2021

2122
export class MetamaskInjectiveExtension extends ConnectedWallet {
@@ -46,10 +47,12 @@ export class MetamaskInjectiveExtension extends ConnectedWallet {
4647
}
4748

4849
public async signArbitrary(data: string): Promise<SignArbitraryResponse> {
49-
const signature = await this.ext.request<string>({
50-
method: "personal_sign",
51-
params: [base16.encode(utf8.decode(data)), this.ethAddress],
52-
});
50+
const signature = await WalletError.wrap(
51+
this.ext.request<string>({
52+
method: "personal_sign",
53+
params: [base16.encode(utf8.decode(data)), this.ethAddress],
54+
})
55+
);
5356
if (!signature) {
5457
throw new Error("Failed to sign arbitrary message");
5558
}
@@ -81,10 +84,12 @@ export class MetamaskInjectiveExtension extends ConnectedWallet {
8184
});
8285
const typedData = this.getTypedData(stdSignDoc);
8386

84-
const signature = await this.ext.request<string>({
85-
method: "eth_signTypedData_v4",
86-
params: [this.ethAddress, JSON.stringify(typedData)],
87-
});
87+
const signature = await WalletError.wrap(
88+
this.ext.request<string>({
89+
method: "eth_signTypedData_v4",
90+
params: [this.ethAddress, JSON.stringify(typedData)],
91+
})
92+
);
8893
if (!signature) {
8994
throw new Error("Failed to sign transaction");
9095
}

src/wallet/wallets/ninji/NinjiController.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { WalletConnectV1 } from "../../walletconnect/WalletConnectV1";
66
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
77
import { ConnectedWallet } from "../ConnectedWallet";
88
import { ChainInfo, WalletController } from "../WalletController";
9+
import { WalletError } from "../WalletError";
910
import { NinjiExtension } from "./NinjiExtension";
1011

1112
export class NinjiController extends WalletController {
@@ -34,9 +35,11 @@ export class NinjiController extends WalletController {
3435
if (!ext) {
3536
throw new Error("Ninji extension is not installed");
3637
}
37-
await ext.enable(chains.map(({ chainId }) => chainId));
38+
await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId)));
3839
for (const { chainId, rpc, gasPrice } of Object.values(chains)) {
39-
const { bech32Address, pubKey, isNanoLedger } = await ext.getKey(chainId);
40+
const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap(
41+
ext.getKey(chainId)
42+
);
4043
const key = new Secp256k1PubKey({
4144
chainId,
4245
key: pubKey,

src/wallet/wallets/owallet/OWalletController.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { WalletConnectV1 } from "../../walletconnect/WalletConnectV1";
77
import { WalletConnectV2 } from "../../walletconnect/WalletConnectV2";
88
import { ConnectedWallet } from "../ConnectedWallet";
99
import { ChainInfo, WalletController } from "../WalletController";
10+
import { WalletError } from "../WalletError";
1011
import { OWalletExtension } from "./OWalletExtension";
1112

1213
export class OWalletController extends WalletController {
@@ -35,9 +36,11 @@ export class OWalletController extends WalletController {
3536
if (!ext) {
3637
throw new Error("OWallet extension is not installed");
3738
}
38-
await ext.enable(chains.map(({ chainId }) => chainId));
39+
await WalletError.wrap(ext.enable(chains.map(({ chainId }) => chainId)));
3940
for (const { chainId, rpc, gasPrice } of Object.values(chains)) {
40-
const { bech32Address, pubKey, isNanoLedger } = await ext.getKey(chainId);
41+
const { bech32Address, pubKey, isNanoLedger } = await WalletError.wrap(
42+
ext.getKey(chainId)
43+
);
4144
const key = new Secp256k1PubKey({
4245
chainId,
4346
key: pubKey,

0 commit comments

Comments
 (0)