@exodus/hardware-wallets 3.3.0 → 3.4.1

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,18 @@
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
+ ## [3.4.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@3.4.0...@exodus/hardware-wallets@3.4.1) (2025-11-28)
7
+
8
+ ### Bug Fixes
9
+
10
+ - fix: various trezor and hardware-wallets fixes (#14502)
11
+
12
+ ## [3.4.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@3.3.0...@exodus/hardware-wallets@3.4.0) (2025-11-13)
13
+
14
+ ### Features
15
+
16
+ - feat: trezor bluetooth (#14374)
17
+
6
18
  ## [3.3.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@3.2.0...@exodus/hardware-wallets@3.3.0) (2025-10-27)
7
19
 
8
20
  ### Features
@@ -14,7 +14,7 @@ declare const hardwareWalletsApiDefinition: {
14
14
  name: string;
15
15
  }[]>;
16
16
  listUseableAssetNames: () => Promise<string[]>;
17
- scanForDevices: () => Promise<void>;
17
+ scanForDevices: (deviceType?: import("libraries/hw-common/lib/types.js").HardwareWalletManufacturer) => Promise<void>;
18
18
  canAccessAsset: ({ assetName }: import("../module/interfaces.js").CanAccessAssetParams) => Promise<boolean>;
19
19
  ensureApplicationIsOpened: ({ assetName }: import("../module/interfaces.js").EnsureApplicationIsOpenedParams) => Promise<void>;
20
20
  scan: ({ assetName, accountIndexes, addressLimit, addressOffset, }: import("../module/interfaces.js").ScanParams) => Promise<import("../module/interfaces.js").ScanResult>;
@@ -1,5 +1,4 @@
1
1
  export { hardwareWalletSigningRequestsAtomDefinition } from './hardwareWalletSigningRequestsAtom.js';
2
- export { hardwareWalletPairingPromptAtomDefinition } from './hardwareWalletPairingPromptAtom.js';
3
2
  type WalletAccountName = string;
4
3
  type AssetName = string;
5
4
  export type WalletAccountNameToConnectedAssetNamesMap = Record<WalletAccountName, AssetName[]>;
@@ -2,7 +2,6 @@ import { combine, compute } from '@exodus/atoms';
2
2
  import { memoize } from '@exodus/basic-utils';
3
3
  import { HARDENED_OFFSET } from '@exodus/bip32';
4
4
  export { hardwareWalletSigningRequestsAtomDefinition } from './hardwareWalletSigningRequestsAtom.js';
5
- export { hardwareWalletPairingPromptAtomDefinition } from './hardwareWalletPairingPromptAtom.js';
6
5
  export const createHardwareWalletConnectedAssetNamesAtom = ({ hardwareWalletPublicKeysAtom, assetsModule, walletAccountsAtom, availableAssetNamesAtom, }) => {
7
6
  const selector = memoize(({ hardwareWalletPublicKeys, walletAccounts, availableAssetNames }) => {
8
7
  const result = Object.create(null);
package/lib/index.d.ts CHANGED
@@ -17,7 +17,7 @@ declare const hardwareWallets: () => {
17
17
  name: string;
18
18
  }[]>;
19
19
  listUseableAssetNames: () => Promise<string[]>;
20
- scanForDevices: () => Promise<void>;
20
+ scanForDevices: (deviceType?: import("libraries/hw-common/lib/types.js").HardwareWalletManufacturer) => Promise<void>;
21
21
  canAccessAsset: ({ assetName }: import("./module/interfaces.js").CanAccessAssetParams) => Promise<boolean>;
22
22
  ensureApplicationIsOpened: ({ assetName }: import("./module/interfaces.js").EnsureApplicationIsOpenedParams) => Promise<void>;
23
23
  scan: ({ assetName, accountIndexes, addressLimit, addressOffset, }: import("./module/interfaces.js").ScanParams) => Promise<import("./module/interfaces.js").ScanResult>;
@@ -37,7 +37,7 @@ declare const hardwareWallets: () => {
37
37
  readonly id: "hardwareWallets";
38
38
  readonly type: "module";
39
39
  readonly factory: (opts: import("./module/hardware-wallets.js").Dependencies) => import("./module/hardware-wallets.js").HardwareWallets;
40
- readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "trezorDiscovery", "publicKeyStore", "hardwareWalletSigningRequestsAtom", "hardwareWalletPairingPromptAtom", "wallet", "walletAccountsAtom", "walletAccounts"];
40
+ readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "trezorDiscovery", "publicKeyStore", "hardwareWalletSigningRequestsAtom", "wallet", "walletAccountsAtom", "walletAccounts"];
41
41
  readonly public: true;
42
42
  };
43
43
  }, {
@@ -55,13 +55,6 @@ declare const hardwareWallets: () => {
55
55
  readonly dependencies: readonly [];
56
56
  readonly public: false;
57
57
  };
58
- }, {
59
- readonly definition: {
60
- readonly id: "hardwareWalletPairingPromptAtom";
61
- readonly type: "atom";
62
- readonly factory: () => import("@exodus/atoms").Atom<boolean>;
63
- readonly public: true;
64
- };
65
58
  }, {
66
59
  readonly definition: {
67
60
  readonly id: "hardwareWalletsPlugin";
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import hardwareWalletsApiDefinition from './api/index.js';
2
2
  import hardwareWalletsModuleDefinition from './module/hardware-wallets.js';
3
- import { hardwareWalletConnectedAssetNamesAtomDefinition, hardwareWalletSigningRequestsAtomDefinition, hardwareWalletPairingPromptAtomDefinition, } from './atoms/index.js';
3
+ import { hardwareWalletConnectedAssetNamesAtomDefinition, hardwareWalletSigningRequestsAtomDefinition, } from './atoms/index.js';
4
4
  import hardwareWalletsPluginDefinition from './plugin/index.js';
5
5
  const hardwareWallets = () => {
6
6
  return {
@@ -18,9 +18,6 @@ const hardwareWallets = () => {
18
18
  {
19
19
  definition: hardwareWalletSigningRequestsAtomDefinition,
20
20
  },
21
- {
22
- definition: hardwareWalletPairingPromptAtomDefinition,
23
- },
24
21
  {
25
22
  definition: hardwareWalletsPluginDefinition,
26
23
  },
@@ -1,7 +1,7 @@
1
1
  import { WalletAccount } from '@exodus/models';
2
2
  import Emitter from '@exodus/wild-emitter';
3
3
  import type { HardwareSignerProvider, CanAccessAssetParams, CreateParams, StoreSyncedKeysParams, ScanParams, SyncParams, EnsureApplicationIsOpenedParams, SignTransactionParams, ScanResult, SyncedKeysId, GetAddressParams, RequireDeviceForParams, ProcessPairingCodeParams, SigningRequestState } from './interfaces.js';
4
- import type { HardwareWalletDiscovery, SignMessageParams } from '@exodus/hw-common';
4
+ import type { HardwareWalletDiscovery, HardwareWalletManufacturer, SignMessageParams } from '@exodus/hw-common';
5
5
  import type { Atom } from '@exodus/atoms';
6
6
  import type { IPublicKeyStore } from '@exodus/public-key-provider/lib/module/store/types';
7
7
  import type { Logger } from '@exodus/logger';
@@ -11,10 +11,10 @@ export type Dependencies = {
11
11
  trezorDiscovery: HardwareWalletDiscovery & {
12
12
  connected: boolean;
13
13
  submitPairingCode: (code: string) => Promise<any>;
14
+ clearPairingPrompt: () => Promise<void>;
14
15
  };
15
16
  logger: Logger;
16
17
  hardwareWalletSigningRequestsAtom: Atom<SigningRequestState>;
17
- hardwareWalletPairingPromptAtom: Atom<boolean>;
18
18
  publicKeyStore: IPublicKeyStore;
19
19
  wallet: any;
20
20
  walletAccountsAtom: Atom<WalletAccount>;
@@ -25,13 +25,13 @@ export type Dependencies = {
25
25
  export declare class HardwareWallets implements HardwareSignerProvider {
26
26
  #private;
27
27
  readonly events: Emitter<string, any>;
28
- constructor({ assetsModule, ledgerDiscovery, trezorDiscovery, logger, hardwareWalletSigningRequestsAtom, hardwareWalletPairingPromptAtom, publicKeyStore, wallet, walletAccountsAtom, walletAccounts, }: Dependencies);
28
+ constructor({ assetsModule, ledgerDiscovery, trezorDiscovery, logger, hardwareWalletSigningRequestsAtom, publicKeyStore, wallet, walletAccountsAtom, walletAccounts, }: Dependencies);
29
29
  retrySigningRequest: (id: string) => Promise<void>;
30
30
  cancelSigningRequest: (id: string, fromUI: boolean) => Promise<void>;
31
31
  signTransaction: ({ baseAssetName, unsignedTx, walletAccount, multisigData, }: SignTransactionParams) => Promise<any>;
32
32
  signMessage: ({ assetName, derivationPath, message }: SignMessageParams) => Promise<any>;
33
33
  isDeviceConnected: () => Promise<boolean>;
34
- scanForDevices: () => Promise<void>;
34
+ scanForDevices: (deviceType?: HardwareWalletManufacturer) => Promise<void>;
35
35
  getAvailableDevices: () => Promise<{
36
36
  model: import("@exodus/hw-common").HardwareWalletDeviceModels;
37
37
  name: string;
@@ -55,7 +55,7 @@ declare const hardwareWalletsModuleDefinition: {
55
55
  readonly id: "hardwareWallets";
56
56
  readonly type: "module";
57
57
  readonly factory: (opts: Dependencies) => HardwareWallets;
58
- readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "trezorDiscovery", "publicKeyStore", "hardwareWalletSigningRequestsAtom", "hardwareWalletPairingPromptAtom", "wallet", "walletAccountsAtom", "walletAccounts"];
58
+ readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "trezorDiscovery", "publicKeyStore", "hardwareWalletSigningRequestsAtom", "wallet", "walletAccountsAtom", "walletAccounts"];
59
59
  readonly public: true;
60
60
  };
61
61
  export default hardwareWalletsModuleDefinition;
@@ -13,21 +13,20 @@ export class HardwareWallets {
13
13
  #logger;
14
14
  #publicKeyStore;
15
15
  #signingRequestAtom;
16
- #pairingPromptAtom;
17
16
  #wallet;
18
17
  #walletAccountsAtom;
19
18
  #walletAccounts;
20
19
  #syncedKeysMap = new Map();
21
20
  #signingRequest;
21
+ #isRetrying = false;
22
22
  events = new Emitter();
23
- constructor({ assetsModule, ledgerDiscovery, trezorDiscovery, logger, hardwareWalletSigningRequestsAtom, hardwareWalletPairingPromptAtom, publicKeyStore, wallet, walletAccountsAtom, walletAccounts, }) {
23
+ constructor({ assetsModule, ledgerDiscovery, trezorDiscovery, logger, hardwareWalletSigningRequestsAtom, publicKeyStore, wallet, walletAccountsAtom, walletAccounts, }) {
24
24
  this.#assetsModule = assetsModule;
25
25
  this.#ledgerDiscovery = ledgerDiscovery;
26
26
  this.#trezorDiscovery = trezorDiscovery;
27
27
  this.#logger = logger;
28
28
  this.#publicKeyStore = publicKeyStore;
29
29
  this.#signingRequestAtom = hardwareWalletSigningRequestsAtom;
30
- this.#pairingPromptAtom = hardwareWalletPairingPromptAtom;
31
30
  this.#wallet = wallet;
32
31
  this.#walletAccountsAtom = walletAccountsAtom;
33
32
  this.#walletAccounts = walletAccounts;
@@ -64,6 +63,11 @@ export class HardwareWallets {
64
63
  this.#logger.warn(`No signing request found for id: ${id}`);
65
64
  return;
66
65
  }
66
+ if (this.#isRetrying) {
67
+ this.#logger.debug(`Retry already in progress for id: ${id}, ignoring duplicate call`);
68
+ return;
69
+ }
70
+ this.#isRetrying = true;
67
71
  try {
68
72
  this.#logger.debug(`Attempting to get selected device for signing request with id: ${id}`);
69
73
  const { device } = await this.#getSelectedDevice();
@@ -81,6 +85,7 @@ export class HardwareWallets {
81
85
  if (['DisconnectedDevice', 'DisconnectedDeviceDuringOperation'].includes(_error.name)) {
82
86
  this.#logger.debug(`Device disconnected during signing request, likely due to app opening: ${id}`, _error);
83
87
  await delay(300);
88
+ this.#isRetrying = false;
84
89
  await this.retrySigningRequest(id);
85
90
  return;
86
91
  }
@@ -95,6 +100,9 @@ export class HardwareWallets {
95
100
  baseAssetName: this.#signingRequest.baseAssetName,
96
101
  });
97
102
  }
103
+ finally {
104
+ this.#isRetrying = false;
105
+ }
98
106
  };
99
107
  cancelSigningRequest = async (id, fromUI) => {
100
108
  const request = this.#signingRequest;
@@ -164,9 +172,13 @@ export class HardwareWallets {
164
172
  return this.#signGeneric({ baseAssetName, scenario: 'signMessage', sign });
165
173
  };
166
174
  isDeviceConnected = async () => {
167
- if (this.#trezorDiscovery.connected) {
168
- return true;
175
+ try {
176
+ const devices = await this.#trezorDiscovery.list();
177
+ if (devices.length > 0) {
178
+ return true;
179
+ }
169
180
  }
181
+ catch { }
170
182
  try {
171
183
  const devices = await this.#ledgerDiscovery.list();
172
184
  return devices.length > 0;
@@ -175,10 +187,15 @@ export class HardwareWallets {
175
187
  return false;
176
188
  }
177
189
  };
178
- scanForDevices = async () => {
190
+ scanForDevices = async (deviceType) => {
179
191
  this.#trezorDiscovery.stopScan(true);
180
192
  this.#ledgerDiscovery.stopScan(true);
181
- await Promise.all([this.#ledgerDiscovery.scan(), this.#trezorDiscovery.scan()]);
193
+ if (!deviceType || deviceType === 'ledger') {
194
+ await this.#ledgerDiscovery.scan();
195
+ }
196
+ else {
197
+ await this.#trezorDiscovery.scan();
198
+ }
182
199
  };
183
200
  getAvailableDevices = async () => {
184
201
  const [ledgers, trezors] = await Promise.all([
@@ -220,10 +237,10 @@ export class HardwareWallets {
220
237
  getAddress = async ({ assetName, accountIndex, addressIndex, multisigData, displayOnDevice, }) => {
221
238
  const asset = this.#assetsModule.getAsset(assetName);
222
239
  const { device } = await this.#getSelectedDevice();
223
- const supportedPurposes = asset.baseAsset.api.getSupportedPurposes({
240
+ const supportedPurposes = asset.baseAsset.api.getSupportedPurposes?.({
224
241
  compatibilityMode: device.descriptor.manufacturer,
225
242
  isMultisig: !!multisigData,
226
- });
243
+ }) ?? [44];
227
244
  const { derivationPath } = asset.baseAsset.api.getKeyIdentifier({
228
245
  compatibilityMode: device.descriptor.manufacturer,
229
246
  purpose: supportedPurposes[0],
@@ -341,10 +358,10 @@ export class HardwareWallets {
341
358
  continue;
342
359
  }
343
360
  const baseAsset = asset.baseAsset;
344
- const supportedPurposes = baseAsset.api.getSupportedPurposes({
361
+ const supportedPurposes = baseAsset.api.getSupportedPurposes?.({
345
362
  compatibilityMode: source,
346
363
  isMultisig,
347
- });
364
+ }) ?? [44];
348
365
  for (const purpose of supportedPurposes) {
349
366
  const { keyIdentifier, xpub, publicKey, } = (await this.#getXPUB({ device, baseAsset, purpose, accountIndex })) ||
350
367
  (await this.#getPublicKey({ device, baseAsset, purpose, accountIndex }));
@@ -374,6 +391,10 @@ export class HardwareWallets {
374
391
  const { device } = await this.#getSelectedDevice();
375
392
  const source = device.descriptor.manufacturer;
376
393
  const label = source === WalletAccount.LEDGER_SRC ? 'Ledger' : 'Trezor';
394
+ const id = source === WalletAccount.TREZOR_SRC
395
+ ? device.descriptor.internalDescriptor
396
+ :
397
+ randomBytes(32).toString('hex');
377
398
  const { accountIndex, model } = this.#syncedKeysMap.get(syncedKeysId);
378
399
  const walletAccount = new WalletAccount({
379
400
  label: `${label}${accountIndex === 0 ? '' : '_' + accountIndex}`,
@@ -381,7 +402,7 @@ export class HardwareWallets {
381
402
  source,
382
403
  model,
383
404
  index: accountIndex,
384
- id: randomBytes(32).toString('hex'),
405
+ id,
385
406
  isMultisig: !!isMultisig,
386
407
  });
387
408
  const walletAccountName = walletAccount.toString();
@@ -391,7 +412,7 @@ export class HardwareWallets {
391
412
  return walletAccount;
392
413
  };
393
414
  clearPairingRequest = async () => {
394
- await this.#pairingPromptAtom.set(false);
415
+ await this.#trezorDiscovery.clearPairingPrompt();
395
416
  };
396
417
  submitPairingCode = async ({ code }) => {
397
418
  return this.#trezorDiscovery.submitPairingCode(code);
@@ -415,7 +436,6 @@ const hardwareWalletsModuleDefinition = {
415
436
  'trezorDiscovery',
416
437
  'publicKeyStore',
417
438
  'hardwareWalletSigningRequestsAtom',
418
- 'hardwareWalletPairingPromptAtom',
419
439
  'wallet',
420
440
  'walletAccountsAtom',
421
441
  'walletAccounts',
@@ -1,9 +1,9 @@
1
1
  import type { WalletAccount } from '@exodus/models';
2
- import type { HardwareWalletDeviceModels, HardwareWalletDevice, MultisigData, SignMessageParams } from '@exodus/hw-common';
2
+ import type { HardwareWalletDeviceModels, HardwareWalletDevice, HardwareWalletManufacturer, 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>;
6
- scanForDevices: () => Promise<void>;
6
+ scanForDevices: (deviceType: HardwareWalletManufacturer) => Promise<void>;
7
7
  canAccessAsset: ({ assetName }: CanAccessAssetParams) => Promise<boolean>;
8
8
  listUseableAssetNames: () => Promise<string[]>;
9
9
  ensureApplicationIsOpened: ({ assetName }: EnsureApplicationIsOpenedParams) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/hardware-wallets",
3
- "version": "3.3.0",
3
+ "version": "3.4.1",
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": {
@@ -46,12 +46,12 @@
46
46
  "@exodus/dependency-types": "^2.1.1",
47
47
  "@exodus/key-identifier": "^1.3.0",
48
48
  "@exodus/logger": "^1.2.3",
49
- "@exodus/public-key-provider": "^4.2.0",
49
+ "@exodus/public-key-provider": "^4.2.1",
50
50
  "redux": "^4.2.1"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public",
54
54
  "provenance": false
55
55
  },
56
- "gitHead": "48ffae642085d466f42c47df71156bd16bd7b5c8"
56
+ "gitHead": "7c1b8b88571d94cec15b9512e5301922ac1b6017"
57
57
  }
@@ -1,6 +0,0 @@
1
- export declare const hardwareWalletPairingPromptAtomDefinition: {
2
- readonly id: "hardwareWalletPairingPromptAtom";
3
- readonly type: "atom";
4
- readonly factory: () => import("@exodus/atoms").Atom<boolean>;
5
- readonly public: true;
6
- };
@@ -1,9 +0,0 @@
1
- import { createInMemoryAtom } from '@exodus/atoms';
2
- export const hardwareWalletPairingPromptAtomDefinition = {
3
- id: 'hardwareWalletPairingPromptAtom',
4
- type: 'atom',
5
- factory: () => createInMemoryAtom({
6
- defaultValue: false,
7
- }),
8
- public: true,
9
- };