@exodus/hw-ledger 2.5.1 → 2.7.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,19 @@
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
+ ## [2.7.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@2.6.0...@exodus/hw-ledger@2.7.0) (2024-07-04)
7
+
8
+ ### Features
9
+
10
+ - **ledger:** add `bluetoothScanningAtom` atom to indicate if scanning ([#7645](https://github.com/ExodusMovement/exodus-hydra/issues/7645)) ([bc377ea](https://github.com/ExodusMovement/exodus-hydra/commit/bc377ea7c4b1dd3a33b892791b80ea53e8ec2b58))
11
+ - **ledger:** support retrieving BLE status ([#7631](https://github.com/ExodusMovement/exodus-hydra/issues/7631)) ([22eebc1](https://github.com/ExodusMovement/exodus-hydra/commit/22eebc1a059c502644f515897aefd1b4ef268cad))
12
+
13
+ ## [2.6.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@2.5.1...@exodus/hw-ledger@2.6.0) (2024-07-03)
14
+
15
+ ### Features
16
+
17
+ - **ledger:** support retrieving name & model ([#7597](https://github.com/ExodusMovement/exodus-hydra/issues/7597)) ([a3210d7](https://github.com/ExodusMovement/exodus-hydra/commit/a3210d7db77b44119bca90b6e7d4507e4012b4cf))
18
+
6
19
  ## [2.5.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@2.5.0...@exodus/hw-ledger@2.5.1) (2024-05-27)
7
20
 
8
21
  ### Bug Fixes
@@ -0,0 +1,12 @@
1
+ export declare const bluetoothEnabledAtomDefinition: {
2
+ readonly id: "bluetoothEnabledAtom";
3
+ readonly type: "atom";
4
+ readonly factory: () => import("@exodus/atoms").Atom<boolean>;
5
+ readonly dependencies: readonly [];
6
+ };
7
+ export declare const bluetoothScanningAtomDefinition: {
8
+ readonly id: "bluetoothScanningAtom";
9
+ readonly type: "atom";
10
+ readonly factory: () => import("@exodus/atoms").Atom<boolean>;
11
+ readonly dependencies: readonly [];
12
+ };
@@ -0,0 +1,13 @@
1
+ import { createInMemoryAtom } from '@exodus/atoms';
2
+ export const bluetoothEnabledAtomDefinition = {
3
+ id: 'bluetoothEnabledAtom',
4
+ type: 'atom',
5
+ factory: () => createInMemoryAtom({ defaultValue: false }),
6
+ dependencies: [],
7
+ };
8
+ export const bluetoothScanningAtomDefinition = {
9
+ id: 'bluetoothScanningAtom',
10
+ type: 'atom',
11
+ factory: () => createInMemoryAtom({ defaultValue: false }),
12
+ dependencies: [],
13
+ };
package/lib/index.d.ts CHANGED
@@ -1,16 +1,47 @@
1
1
  import type { HwLedgerConfig } from './module/types';
2
2
  declare const hardwareWalletLedger: ({ bluetoothScannerTimeout, }?: Partial<HwLedgerConfig>) => {
3
- id: string;
4
- definitions: {
5
- definition: {
3
+ readonly id: "hardwareWalletLedger";
4
+ readonly definitions: readonly [{
5
+ readonly definition: {
6
6
  readonly id: "ledgerDiscovery";
7
7
  readonly type: "module";
8
8
  readonly factory: (deps: import("./module").Dependencies) => import("./module").LedgerDiscovery;
9
9
  readonly dependencies: readonly ["config", "logger", "transportsFactories"];
10
10
  };
11
- config: {
12
- bluetoothScannerTimeout: number;
11
+ readonly config: {
12
+ readonly bluetoothScannerTimeout: number;
13
13
  };
14
- }[];
14
+ }, {
15
+ readonly definition: {
16
+ readonly id: "bluetoothEnabledAtom";
17
+ readonly type: "atom";
18
+ readonly factory: () => import("libraries/atoms/lib").Atom<boolean>;
19
+ readonly dependencies: readonly [];
20
+ };
21
+ }, {
22
+ readonly definition: {
23
+ readonly id: "bluetoothScanningAtom";
24
+ readonly type: "atom";
25
+ readonly factory: () => import("libraries/atoms/lib").Atom<boolean>;
26
+ readonly dependencies: readonly [];
27
+ };
28
+ }, {
29
+ readonly definition: {
30
+ readonly id: "hardwareWalletLedgerPlugin";
31
+ readonly type: "plugin";
32
+ readonly factory: ({ logger, transportsFactories, bluetoothEnabledAtom, bluetoothScanningAtom, port, }: {
33
+ logger: import("./shared/types").Logger;
34
+ transportsFactories: import("./module/types").TransportFactories;
35
+ bluetoothEnabledAtom: import("libraries/atoms/lib").Atom<boolean>;
36
+ bluetoothScanningAtom: import("libraries/atoms/lib").Atom<boolean>;
37
+ port: import("./shared/types").Port;
38
+ }) => {
39
+ onUnlock: () => void;
40
+ onLoad: () => void;
41
+ onStop: () => void;
42
+ };
43
+ readonly dependencies: readonly ["logger", "transportsFactories", "bluetoothEnabledAtom", "bluetoothScanningAtom", "port"];
44
+ };
45
+ }];
15
46
  };
16
47
  export default hardwareWalletLedger;
package/lib/index.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { bluetoothEnabledAtomDefinition, bluetoothScanningAtomDefinition } from './atoms';
1
2
  import ledgerDiscoveryDefinition from './module';
3
+ import hardwareWalletLedgerPluginDefinition from './plugin';
2
4
  const hardwareWalletLedger = ({ bluetoothScannerTimeout = 10000, } = {}) => {
3
5
  return {
4
6
  id: 'hardwareWalletLedger',
@@ -7,6 +9,15 @@ const hardwareWalletLedger = ({ bluetoothScannerTimeout = 10000, } = {}) => {
7
9
  definition: ledgerDiscoveryDefinition,
8
10
  config: { bluetoothScannerTimeout },
9
11
  },
12
+ {
13
+ definition: bluetoothEnabledAtomDefinition,
14
+ },
15
+ {
16
+ definition: bluetoothScanningAtomDefinition,
17
+ },
18
+ {
19
+ definition: hardwareWalletLedgerPluginDefinition,
20
+ },
10
21
  ],
11
22
  };
12
23
  };
@@ -1,19 +1,16 @@
1
- import ExodusModule from '@exodus/module';
2
1
  import { Logger } from '../shared/types';
2
+ import type { Atom } from '@exodus/atoms';
3
3
  import type { HardwareWalletDescriptor, HardwareWalletDiscovery } from '@exodus/hw-common';
4
- import type { TransportFactory, HwLedgerConfig } from './types';
4
+ import type { HwLedgerConfig, TransportFactories } from './types';
5
5
  export type Dependencies = {
6
6
  config: HwLedgerConfig;
7
7
  logger: Logger;
8
- transportsFactories: {
9
- usb: TransportFactory;
10
- bluetooth: TransportFactory;
11
- tcp: TransportFactory;
12
- };
8
+ transportsFactories: TransportFactories;
9
+ bluetoothScanningAtom: Atom<boolean>;
13
10
  };
14
- export declare class LedgerDiscovery extends ExodusModule implements HardwareWalletDiscovery {
11
+ export declare class LedgerDiscovery implements HardwareWalletDiscovery {
15
12
  #private;
16
- constructor({ config, logger, transportsFactories }: Dependencies);
13
+ constructor({ config, logger, transportsFactories, bluetoothScanningAtom }: Dependencies);
17
14
  scan: () => Promise<void>;
18
15
  stopScan: (clearDevices?: boolean) => void;
19
16
  list: () => Promise<HardwareWalletDescriptor[]>;
@@ -1,15 +1,18 @@
1
- import ExodusModule from '@exodus/module';
1
+ import { getInfosForServiceUuid, identifyUSBProductId } from '@exodus/ledgerhq-devices';
2
2
  import { APDU_PORT } from './constants';
3
3
  import { LedgerDevice } from './device';
4
4
  import { NoDeviceFoundError, UnknownTransportError } from './errors';
5
5
  const MODULE_ID = 'ledgerDiscovery';
6
- export class LedgerDiscovery extends ExodusModule {
6
+ export class LedgerDiscovery {
7
+ #logger;
7
8
  #transportsFactories;
8
9
  #scannerTimeout;
9
10
  #scannerBluetooth;
11
+ #bluetoothScanningAtom;
10
12
  #discoveredBluetoothDevices = Object.create(null);
11
- constructor({ config, logger, transportsFactories }) {
12
- super({ name: MODULE_ID, logger });
13
+ constructor({ config, logger, transportsFactories, bluetoothScanningAtom }) {
14
+ this.#logger = logger;
15
+ this.#bluetoothScanningAtom = bluetoothScanningAtom;
13
16
  if (!(typeof transportsFactories === 'object' &&
14
17
  Object.keys(transportsFactories).length > 0 &&
15
18
  Object.entries(transportsFactories).every(([transportName, transportFactory]) => ['usb', 'bluetooth', 'tcp'].includes(transportName) &&
@@ -35,19 +38,20 @@ export class LedgerDiscovery extends ExodusModule {
35
38
  }
36
39
  },
37
40
  error: (error) => {
38
- this._logger.error(`an error occured while scanning for bluetooth devices "${error.message}"`);
41
+ this.#logger.error(`an error occured while scanning for bluetooth devices "${error.message}"`);
39
42
  this.stopScan();
40
43
  reject(error);
41
44
  },
42
45
  complete: () => { },
43
46
  });
47
+ this.#bluetoothScanningAtom.set(true);
44
48
  setTimeout(() => {
45
49
  this.stopScan();
46
50
  resolve();
47
51
  }, this.#scannerTimeout);
48
52
  }
49
53
  else {
50
- this._logger.error(`Attempted to perform scan without having bluetooth transport factory`);
54
+ this.#logger.error(`Attempted to perform scan without having bluetooth transport factory`);
51
55
  reject(new Error(`Attempted to perform scan without having bluetooth transport factory`));
52
56
  }
53
57
  });
@@ -57,6 +61,7 @@ export class LedgerDiscovery extends ExodusModule {
57
61
  this.#discoveredBluetoothDevices = Object.create(null);
58
62
  }
59
63
  if (this.#scannerBluetooth) {
64
+ this.#bluetoothScanningAtom.set(false);
60
65
  this.#scannerBluetooth.unsubscribe();
61
66
  this.#scannerBluetooth = undefined;
62
67
  }
@@ -67,25 +72,38 @@ export class LedgerDiscovery extends ExodusModule {
67
72
  switch (transportName) {
68
73
  case 'usb':
69
74
  const deviceList = await transportFactory.list();
70
- const usbDeviceList = deviceList.map((hidDevice) => ({
71
- manufacturer: 'ledger',
72
- transport: 'usb',
73
- get: async () => new LedgerDevice({ transportFactory, descriptor: hidDevice }),
74
- }));
75
+ const usbDeviceList = deviceList.map((hidDevice) => {
76
+ const deviceModel = identifyUSBProductId(hidDevice.productId);
77
+ return {
78
+ name: deviceModel?.productName ?? 'Ledger USB',
79
+ manufacturer: 'ledger',
80
+ transport: 'usb',
81
+ model: deviceModel?.id ?? 'unknown',
82
+ get: async () => new LedgerDevice({ transportFactory, descriptor: hidDevice }),
83
+ };
84
+ });
75
85
  descriptors.push(...usbDeviceList);
76
86
  break;
77
87
  case 'bluetooth':
78
- const bluetoothDeviceList = Object.values(this.#discoveredBluetoothDevices).map((bluetoothDevice) => ({
79
- manufacturer: 'ledger',
80
- transport: 'bluetooth',
81
- get: async () => new LedgerDevice({ transportFactory, descriptor: bluetoothDevice }),
82
- }));
88
+ const bluetoothDeviceList = Object.values(this.#discoveredBluetoothDevices).map((bluetoothDevice) => {
89
+ const deviceModel = getInfosForServiceUuid(bluetoothDevice.serviceUUIDs?.[0] ?? '')
90
+ ?.deviceModel;
91
+ return {
92
+ name: bluetoothDevice.localName ?? deviceModel?.productName,
93
+ manufacturer: 'ledger',
94
+ transport: 'bluetooth',
95
+ model: deviceModel?.id ?? 'unknown',
96
+ get: async () => new LedgerDevice({ transportFactory, descriptor: bluetoothDevice }),
97
+ };
98
+ });
83
99
  descriptors.push(...bluetoothDeviceList);
84
100
  break;
85
101
  case 'tcp':
86
102
  descriptors.push({
103
+ name: 'TCP Emulator',
87
104
  manufacturer: 'ledger',
88
105
  transport: 'tcp',
106
+ model: 'unknown',
89
107
  get: async () => new LedgerDevice({
90
108
  transportFactory,
91
109
  descriptor: { apduPort: APDU_PORT },
@@ -1,5 +1,21 @@
1
1
  import type Transport from '@ledgerhq/hw-transport';
2
+ import type { TransportTypes } from '@exodus/hw-common';
3
+ import type { Subscription } from '@ledgerhq/hw-transport';
2
4
  export type TransportFactory = typeof Transport;
5
+ export type TransportFactories = Partial<Record<TransportTypes, TransportFactory>> & {
6
+ bluetooth?: TransportFactory & {
7
+ observeAvailability?: (observer: {
8
+ next: (available: boolean) => void;
9
+ error: (err: Error) => void;
10
+ }) => Subscription;
11
+ observeState?: (observer: {
12
+ next: (evt: {
13
+ available: boolean;
14
+ }) => void;
15
+ error: (err: Error) => void;
16
+ }) => Subscription;
17
+ };
18
+ };
3
19
  export type HwLedgerConfig = {
4
20
  bluetoothScannerTimeout: number;
5
21
  };
@@ -0,0 +1,20 @@
1
+ import type { Atom } from '@exodus/atoms';
2
+ import type { TransportFactories } from '../module/types';
3
+ import type { Logger, Port } from '../shared/types';
4
+ declare const hardwareWalletLedgerPluginDefinition: {
5
+ readonly id: "hardwareWalletLedgerPlugin";
6
+ readonly type: "plugin";
7
+ readonly factory: ({ logger, transportsFactories, bluetoothEnabledAtom, bluetoothScanningAtom, port, }: {
8
+ logger: Logger;
9
+ transportsFactories: TransportFactories;
10
+ bluetoothEnabledAtom: Atom<boolean>;
11
+ bluetoothScanningAtom: Atom<boolean>;
12
+ port: Port;
13
+ }) => {
14
+ onUnlock: () => void;
15
+ onLoad: () => void;
16
+ onStop: () => void;
17
+ };
18
+ readonly dependencies: readonly ["logger", "transportsFactories", "bluetoothEnabledAtom", "bluetoothScanningAtom", "port"];
19
+ };
20
+ export default hardwareWalletLedgerPluginDefinition;
@@ -0,0 +1,59 @@
1
+ import { createAtomObserver } from '@exodus/atoms';
2
+ const createHardwareWalletLedgerPlugin = ({ logger, transportsFactories, bluetoothEnabledAtom, bluetoothScanningAtom, port, }) => {
3
+ let subscription;
4
+ const observers = [
5
+ createAtomObserver({ atom: bluetoothEnabledAtom, port, event: 'bluetoothEnabled' }),
6
+ createAtomObserver({ atom: bluetoothScanningAtom, port, event: 'bluetoothScanning' }),
7
+ ];
8
+ observers.forEach((observer) => observer.register());
9
+ const start = () => {
10
+ if (!transportsFactories.bluetooth)
11
+ return;
12
+ if (subscription)
13
+ return;
14
+ if (transportsFactories.bluetooth.observeState) {
15
+ subscription = transportsFactories.bluetooth.observeState({
16
+ next: (e) => {
17
+ bluetoothEnabledAtom.set(!!e.available);
18
+ },
19
+ error: (error) => logger.error(error),
20
+ });
21
+ }
22
+ else if (transportsFactories.bluetooth.observeAvailability) {
23
+ subscription = transportsFactories.bluetooth.observeAvailability({
24
+ next: (available) => {
25
+ bluetoothEnabledAtom.set(available);
26
+ },
27
+ error: (error) => logger.error(error),
28
+ });
29
+ }
30
+ observers.forEach((observer) => observer.start());
31
+ };
32
+ const stop = () => {
33
+ observers.forEach((observer) => observer.unregister());
34
+ subscription?.unsubscribe();
35
+ };
36
+ const onUnlock = () => {
37
+ start();
38
+ };
39
+ const onLoad = () => {
40
+ start();
41
+ };
42
+ const onStop = () => {
43
+ stop();
44
+ };
45
+ return { onUnlock, onLoad, onStop };
46
+ };
47
+ const hardwareWalletLedgerPluginDefinition = {
48
+ id: 'hardwareWalletLedgerPlugin',
49
+ type: 'plugin',
50
+ factory: createHardwareWalletLedgerPlugin,
51
+ dependencies: [
52
+ 'logger',
53
+ 'transportsFactories',
54
+ 'bluetoothEnabledAtom',
55
+ 'bluetoothScanningAtom',
56
+ 'port',
57
+ ],
58
+ };
59
+ export default hardwareWalletLedgerPluginDefinition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/hw-ledger",
3
- "version": "2.5.1",
3
+ "version": "2.7.0",
4
4
  "description": "The ledger module",
5
5
  "author": "Exodus Movement, Inc.",
6
6
  "repository": {
@@ -30,11 +30,13 @@
30
30
  "prepublishOnly": "yarn run -T build --scope @exodus/hw-ledger"
31
31
  },
32
32
  "dependencies": {
33
+ "@exodus/atoms": "^7.5.1",
33
34
  "@exodus/bip322-js": "^1.1.0-exodus.4",
34
35
  "@exodus/bitcoinjs-lib": "^6.1.5-exodus.0",
35
36
  "@exodus/ethersproject-transactions": "^5.4.2-exodus.0",
36
- "@exodus/hw-common": "^2.0.0",
37
+ "@exodus/hw-common": "^2.1.0",
37
38
  "@exodus/ledger-bitcoin": "^0.2.3-exodus.3",
39
+ "@exodus/ledgerhq-devices": "^8.1.0-exodus.3",
38
40
  "@exodus/ledgerhq-hw-app-eth": "^6.33.7-exodus.8",
39
41
  "@exodus/module": "^1.2.2",
40
42
  "@exodus/secp256k1": "^4.0.2-exodus.0",
@@ -49,7 +51,6 @@
49
51
  "devDependencies": {
50
52
  "@exodus/bip32": "^2.0.0",
51
53
  "@exodus/ethersproject-abi": "^5.4.2-exodus.3",
52
- "@exodus/ledgerhq-devices": "^8.1.0-exodus.2",
53
54
  "@ledgerhq/hw-transport-node-hid": "^6.27.17",
54
55
  "@ledgerhq/hw-transport-node-speculos": "^6.27.16",
55
56
  "@solana/web3.js": "^1.78.0",
@@ -69,5 +70,5 @@
69
70
  "ts-jest": "^29.0.3",
70
71
  "typescript": "^4.7.3"
71
72
  },
72
- "gitHead": "c34c770e5b16f0fe2f0edaadfa5016cce7cbd1bb"
73
+ "gitHead": "c53411aed8e084439cc9d1a5450fa65266f834ab"
73
74
  }