@exodus/hardware-wallets 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.5.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@1.4.0...@exodus/hardware-wallets@1.5.0) (2024-10-17)
7
+
8
+ ### Features
9
+
10
+ - add `getSelectedDevice` ([#9822](https://github.com/ExodusMovement/exodus-hydra/issues/9822)) ([2c18c22](https://github.com/ExodusMovement/exodus-hydra/commit/2c18c22d419ba9136cd8b264e946b151df438c59))
11
+ - support adding model to wallet account ([#9936](https://github.com/ExodusMovement/exodus-hydra/issues/9936)) ([8c858f6](https://github.com/ExodusMovement/exodus-hydra/commit/8c858f6e08e41bee3261f444c3d25e8bdd385014))
12
+
13
+ ### Bug Fixes
14
+
15
+ - unify user refused error handling ([#10052](https://github.com/ExodusMovement/exodus-hydra/issues/10052)) ([db85d10](https://github.com/ExodusMovement/exodus-hydra/commit/db85d108333630d09bad545c5ec1169b937e08fe))
16
+
6
17
  ## [1.4.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@1.3.0...@exodus/hardware-wallets@1.4.0) (2024-10-07)
7
18
 
8
19
  ### Features
@@ -10,7 +10,7 @@ declare const hardwareWalletsApiDefinition: {
10
10
  hardwareWallets: {
11
11
  isDeviceConnected: () => Promise<boolean>;
12
12
  getAvailableDevices: () => Promise<{
13
- model: ("blue" | "nanoS" | "nanoSP" | "nanoX" | "stax" | "europa") | ("1" | "t" | "Safe 3" | "Safe 5") | "unknown";
13
+ model: import("libraries/hw-common/lib/types.js").HardwareWalletDeviceModels;
14
14
  name: string;
15
15
  }[]>;
16
16
  listUseableAssetNames: () => Promise<string[]>;
package/lib/index.d.ts CHANGED
@@ -13,7 +13,7 @@ declare const hardwareWallets: () => {
13
13
  hardwareWallets: {
14
14
  isDeviceConnected: () => Promise<boolean>;
15
15
  getAvailableDevices: () => Promise<{
16
- model: ("blue" | "nanoS" | "nanoSP" | "nanoX" | "stax" | "europa") | ("1" | "t" | "Safe 3" | "Safe 5") | "unknown";
16
+ model: import("libraries/hw-common/lib/types.js").HardwareWalletDeviceModels;
17
17
  name: string;
18
18
  }[]>;
19
19
  listUseableAssetNames: () => Promise<string[]>;
@@ -26,7 +26,7 @@ export declare class HardwareWallets implements HardwareSignerProvider {
26
26
  isDeviceConnected: () => Promise<boolean>;
27
27
  scanForDevices: () => Promise<void>;
28
28
  getAvailableDevices: () => Promise<{
29
- model: ("blue" | "nanoS" | "nanoSP" | "nanoX" | "stax" | "europa") | ("1" | "t" | "Safe 3" | "Safe 5") | "unknown";
29
+ model: import("@exodus/hw-common").HardwareWalletDeviceModels;
30
30
  name: string;
31
31
  }[]>;
32
32
  canAccessAsset: ({ assetName }: CanAccessAssetParams) => Promise<boolean>;
@@ -3,7 +3,7 @@ import { WalletAccount } from '@exodus/models';
3
3
  import Emitter from '@exodus/wild-emitter';
4
4
  import randomBytes from 'randombytes';
5
5
  import delay from 'delay';
6
- import { UserCancelledError } from './errors.js';
6
+ import { NoDeviceFoundError, UserRefusedError } from '@exodus/hw-common';
7
7
  export class HardwareWallets {
8
8
  #assetsModule;
9
9
  #ledgerDiscovery;
@@ -25,6 +25,13 @@ export class HardwareWallets {
25
25
  this.#walletAccountsAtom = walletAccountsAtom;
26
26
  this.#walletAccounts = walletAccounts;
27
27
  }
28
+ #getSelectedDevice = async () => {
29
+ const descriptors = await this.#ledgerDiscovery.list();
30
+ if (descriptors[0]) {
31
+ return { device: await descriptors[0].get() };
32
+ }
33
+ throw new NoDeviceFoundError();
34
+ };
28
35
  #requestUserAction = async (params) => {
29
36
  const rpc = this.#userInterface.getRPC();
30
37
  return rpc.callMethod('handle-hardware-wallet', params);
@@ -42,9 +49,9 @@ export class HardwareWallets {
42
49
  });
43
50
  needUserApproval = false;
44
51
  }
45
- const hardwareDevice = await this.#ledgerDiscovery.getFirstDevice();
46
- await hardwareDevice.ensureApplicationIsOpened(baseAssetName);
47
- const result = await sign({ hardwareDevice });
52
+ const { device } = await this.#getSelectedDevice();
53
+ await device.ensureApplicationIsOpened(baseAssetName);
54
+ const result = await sign({ device });
48
55
  this.#requestUserAction({ scenario: 'completed' });
49
56
  return result;
50
57
  }
@@ -52,6 +59,9 @@ export class HardwareWallets {
52
59
  if (error.message.includes('timeout')) {
53
60
  break;
54
61
  }
62
+ else if (error.name === 'UserRefusedError') {
63
+ throw error;
64
+ }
55
65
  if (error.name === 'DisconnectedDeviceDuringOperation') {
56
66
  this.#requestUserAction({
57
67
  scenario,
@@ -80,15 +90,15 @@ export class HardwareWallets {
80
90
  }
81
91
  }
82
92
  this.#requestUserAction({ scenario: 'completed' });
83
- throw new UserCancelledError();
93
+ throw new UserRefusedError(false);
84
94
  };
85
95
  signTransaction = async ({ baseAssetName, unsignedTx, walletAccount, multisigData, }) => {
86
96
  const baseAsset = this.#assetsModule.getAsset(baseAssetName);
87
97
  const accountIndex = walletAccount.index;
88
- const sign = async ({ hardwareDevice }) => {
98
+ const sign = async ({ device }) => {
89
99
  return baseAsset.api.signHardware({
90
100
  unsignedTx,
91
- hardwareDevice,
101
+ hardwareDevice: device,
92
102
  accountIndex,
93
103
  multisigData,
94
104
  });
@@ -97,8 +107,8 @@ export class HardwareWallets {
97
107
  };
98
108
  signMessage = async ({ assetName, derivationPath, message }) => {
99
109
  const baseAssetName = this.#assetsModule.getAsset(assetName).baseAsset.name;
100
- const sign = async ({ hardwareDevice }) => {
101
- return hardwareDevice.signMessage({
110
+ const sign = async ({ device }) => {
111
+ return device.signMessage({
102
112
  assetName: baseAssetName,
103
113
  derivationPath,
104
114
  message,
@@ -128,12 +138,12 @@ export class HardwareWallets {
128
138
  };
129
139
  canAccessAsset = async ({ assetName }) => {
130
140
  const asset = this.#assetsModule.getAsset(assetName);
131
- const device = await this.#ledgerDiscovery.getFirstDevice();
141
+ const { device } = await this.#getSelectedDevice();
132
142
  const useableAssetNames = new Set(await device.listUseableAssetNames());
133
143
  return useableAssetNames.has(asset.baseAsset.name);
134
144
  };
135
145
  listUseableAssetNames = async () => {
136
- const device = await this.#ledgerDiscovery.getFirstDevice();
146
+ const { device } = await this.#getSelectedDevice();
137
147
  return device.listUseableAssetNames();
138
148
  };
139
149
  ensureApplicationIsOpened = async ({ assetName }) => {
@@ -141,8 +151,8 @@ export class HardwareWallets {
141
151
  let i = 0;
142
152
  while (i < 3) {
143
153
  try {
144
- const hardwareDevice = await this.#ledgerDiscovery.getFirstDevice();
145
- await hardwareDevice.ensureApplicationIsOpened(asset.baseAsset.name);
154
+ const { device } = await this.#getSelectedDevice();
155
+ await device.ensureApplicationIsOpened(asset.baseAsset.name);
146
156
  }
147
157
  catch (error) {
148
158
  this.#logger.log(error);
@@ -166,7 +176,7 @@ export class HardwareWallets {
166
176
  chainIndex: 0,
167
177
  addressIndex,
168
178
  });
169
- const device = await this.#ledgerDiscovery.getFirstDevice();
179
+ const { device } = await this.#getSelectedDevice();
170
180
  return device.getAddress({
171
181
  assetName,
172
182
  derivationPath,
@@ -259,7 +269,7 @@ export class HardwareWallets {
259
269
  };
260
270
  sync = async ({ accountIndex: index, isMultisig } = {}) => {
261
271
  const keysToSync = [];
262
- const device = await this.#ledgerDiscovery.getFirstDevice();
272
+ const { device } = await this.#getSelectedDevice();
263
273
  const accountIndex = index ?? this.#walletAccounts.getNextIndex({ source: WalletAccount.LEDGER_SRC });
264
274
  const useableAssetNames = new Set(await device.listUseableAssetNames());
265
275
  for (const assetName of useableAssetNames) {
@@ -279,6 +289,7 @@ export class HardwareWallets {
279
289
  const id = randomBytes(16).toString('hex');
280
290
  this.#syncedKeysMap.set(id, {
281
291
  accountIndex,
292
+ model: device.descriptor.model,
282
293
  assetNames: useableAssetNames,
283
294
  keysToSync,
284
295
  });
@@ -295,11 +306,12 @@ export class HardwareWallets {
295
306
  };
296
307
  create = async ({ syncedKeysId, isMultisig }) => {
297
308
  assert(this.#syncedKeysMap.has(syncedKeysId), `no synchronized keys found for id ${syncedKeysId}`);
298
- const { accountIndex } = this.#syncedKeysMap.get(syncedKeysId);
309
+ const { accountIndex, model } = this.#syncedKeysMap.get(syncedKeysId);
299
310
  const walletAccount = new WalletAccount({
300
311
  label: `Ledger${accountIndex === 0 ? '' : ' ' + accountIndex}`,
301
312
  icon: 'ledger',
302
313
  source: WalletAccount.LEDGER_SRC,
314
+ model,
303
315
  index: accountIndex,
304
316
  id: randomBytes(32).toString('hex'),
305
317
  isMultisig: !!isMultisig,
@@ -1,5 +1,5 @@
1
1
  import type { WalletAccount } from '@exodus/models';
2
- import type { HardwareWalletDevice, MultisigData, SignMessageParams } from '@exodus/hw-common';
2
+ import type { HardwareWalletDeviceModels, HardwareWalletDevice, MultisigData, SignMessageParams } from '@exodus/hw-common';
3
3
  import type KeyIdentifier from '@exodus/key-identifier';
4
4
  export interface HardwareSignerProvider {
5
5
  isDeviceConnected: () => Promise<boolean>;
@@ -59,6 +59,7 @@ export interface SyncParams {
59
59
  export type SyncedKeysId = string;
60
60
  export interface SyncedKeysData {
61
61
  accountIndex: number;
62
+ model: HardwareWalletDeviceModels;
62
63
  assetNames: Set<string>;
63
64
  keysToSync: KeyToSyncData[];
64
65
  }
@@ -74,8 +75,8 @@ export interface CreateParams {
74
75
  syncedKeysId: SyncedKeysId;
75
76
  isMultisig?: boolean;
76
77
  }
77
- export type GenericSignCallback = ({ hardwareDevice, }: {
78
- hardwareDevice: HardwareWalletDevice;
78
+ export type GenericSignCallback = ({ device }: {
79
+ device: HardwareWalletDevice;
79
80
  }) => Promise<any>;
80
81
  export interface GenericSignParams {
81
82
  baseAssetName: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/hardware-wallets",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "An Exodus SDK feature that provides a high level abstraction for interacting with hardware wallet devices",
5
5
  "author": "Exodus Movement, Inc.",
6
6
  "repository": {
@@ -24,10 +24,11 @@
24
24
  "clean": "run -T tsc --build --clean",
25
25
  "lint": "run -T eslint .",
26
26
  "lint:fix": "yarn lint --fix",
27
- "test": "run -T jest",
27
+ "test": "run -T exodus-test --jest --esbuild",
28
28
  "prepublishOnly": "yarn run -T build --scope @exodus/hardware-wallets"
29
29
  },
30
30
  "dependencies": {
31
+ "@exodus/hw-common": "^3.0.0",
31
32
  "@exodus/models": "^12.0.1",
32
33
  "@exodus/wild-emitter": "^1.1.0",
33
34
  "delay": "^5.0.0",
@@ -37,11 +38,10 @@
37
38
  "devDependencies": {
38
39
  "@exodus/atoms": "^9.0.0",
39
40
  "@exodus/dependency-types": "^2.1.0",
40
- "@exodus/hw-common": "^2.5.0",
41
41
  "@exodus/key-identifier": "^1.3.0",
42
42
  "@exodus/logger": "^1.2.2",
43
43
  "@exodus/public-key-provider": "^3.0.0",
44
44
  "@types/randombytes": "^2.0.3"
45
45
  },
46
- "gitHead": "c60b8443608e3f1d4673fff692f4aa03b816badd"
46
+ "gitHead": "a4f949331317cbeb49ea2392703722914a67fcac"
47
47
  }
@@ -1,4 +0,0 @@
1
- export declare class UserCancelledError extends Error {
2
- name: string;
3
- constructor();
4
- }
@@ -1,6 +0,0 @@
1
- export class UserCancelledError extends Error {
2
- name = 'UserCancelledError';
3
- constructor() {
4
- super('User cancelled action');
5
- }
6
- }