@exodus/hw-ledger 2.1.1 → 2.2.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
+ ## [2.2.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@2.2.0...@exodus/hw-ledger@2.2.1) (2024-01-16)
7
+
8
+ ### Bug Fixes
9
+
10
+ - generalize errors for blind signing ([#5421](https://github.com/ExodusMovement/exodus-hydra/issues/5421)) ([cb0a009](https://github.com/ExodusMovement/exodus-hydra/commit/cb0a009d06b750691b80021f7a9de5f073a117a7))
11
+
12
+ ## [2.2.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@2.1.1...@exodus/hw-ledger@2.2.0) (2024-01-11)
13
+
14
+ ### Features
15
+
16
+ - support signing matic transactions with ethereum app on ledger ([#5329](https://github.com/ExodusMovement/exodus-hydra/issues/5329)) ([55eda94](https://github.com/ExodusMovement/exodus-hydra/commit/55eda944b78e1714a2b7215731febf399167a85b))
17
+
6
18
  ## [2.1.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hw-ledger@2.1.0...@exodus/hw-ledger@2.1.1) (2024-01-11)
7
19
 
8
20
  ### Bug Fixes
@@ -9,8 +9,10 @@ export interface BitcoinMessage {
9
9
  }
10
10
  declare function createHandler(transport: Transport): Promise<HardwareWalletAssetHandler<BitcoinMessage>>;
11
11
  declare const metadata: {
12
- applicationName: string;
13
- supportedVersions: string;
12
+ applications: {
13
+ applicationName: string;
14
+ supportedVersions: string;
15
+ }[];
14
16
  handler: typeof createHandler;
15
17
  };
16
18
  export default metadata;
@@ -67,8 +67,12 @@ async function createHandler(transport) {
67
67
  };
68
68
  }
69
69
  const metadata = {
70
- applicationName: 'Bitcoin',
71
- supportedVersions: '^2.1.2',
70
+ applications: [
71
+ {
72
+ applicationName: 'Bitcoin',
73
+ supportedVersions: '^2.1.2',
74
+ },
75
+ ],
72
76
  handler: createHandler,
73
77
  };
74
78
  export default metadata;
@@ -2,8 +2,10 @@ import type Transport from '@ledgerhq/hw-transport';
2
2
  import type { HardwareWalletAssetHandler } from '@exodus/hw-common';
3
3
  declare function createHandler(transport: Transport): Promise<HardwareWalletAssetHandler>;
4
4
  declare const metadata: {
5
- applicationName: string;
6
- supportedVersions: string;
5
+ applications: {
6
+ applicationName: string;
7
+ supportedVersions: string;
8
+ }[];
7
9
  handler: typeof createHandler;
8
10
  };
9
11
  export default metadata;
@@ -4,6 +4,7 @@ import AppEth, { ledgerService } from '@exodus/ledgerhq-hw-app-eth';
4
4
  import { TypedDataUtils, SignTypedDataVersion, } from '@metamask/eth-sig-util';
5
5
  import assert from 'minimalistic-assert';
6
6
  import { buildFauxXPUB } from './utils';
7
+ import { RequiresBlindSigningError } from '../errors';
7
8
  function hashEIP712Message(typedData) {
8
9
  const version = SignTypedDataVersion.V4;
9
10
  const { sanitizeData, hashStruct } = TypedDataUtils;
@@ -80,6 +81,9 @@ async function createHandler(transport) {
80
81
  withNFT: false,
81
82
  });
82
83
  }
84
+ else if (err instanceof Error && err.message.includes('EthAppPleaseEnableContractData')) {
85
+ throw new RequiresBlindSigningError();
86
+ }
83
87
  else {
84
88
  throw err;
85
89
  }
@@ -125,8 +129,12 @@ async function createHandler(transport) {
125
129
  };
126
130
  }
127
131
  const metadata = {
128
- applicationName: 'Ethereum',
129
- supportedVersions: '^1.10.0',
132
+ applications: [
133
+ {
134
+ applicationName: 'Ethereum',
135
+ supportedVersions: '^1.10.0',
136
+ },
137
+ ],
130
138
  handler: createHandler,
131
139
  };
132
140
  export default metadata;
@@ -2,8 +2,10 @@ import type Transport from '@ledgerhq/hw-transport';
2
2
  import type { HardwareWalletAssetHandler } from '@exodus/hw-common';
3
3
  type HandlerFactory = (transport: Transport) => Promise<HardwareWalletAssetHandler>;
4
4
  type AssetMetadata = {
5
- applicationName: string;
6
- supportedVersions: string;
5
+ applications: {
6
+ applicationName: string;
7
+ supportedVersions: string;
8
+ }[];
7
9
  handler: HandlerFactory;
8
10
  };
9
11
  export declare const assetApplications: Record<string, AssetMetadata>;
@@ -10,6 +10,6 @@ export const assetApplications = {
10
10
  };
11
11
  export const supportedBaseAssetNames = Object.keys(assetApplications);
12
12
  export const applicationNameToBaseAssetName = Object.entries(assetApplications).reduce((mapping, [assetName, metadata]) => {
13
- mapping[metadata.applicationName] = assetName;
13
+ mapping[metadata.applications[0].applicationName] = assetName;
14
14
  return mapping;
15
15
  }, Object.create(null));
@@ -1,6 +1,8 @@
1
1
  declare const metadata: {
2
- applicationName: string;
3
- supportedVersions: string;
2
+ applications: {
3
+ applicationName: string;
4
+ supportedVersions: string;
5
+ }[];
4
6
  handler: (transport: import("@ledgerhq/hw-transport").default) => Promise<import("libraries/hw-common/lib").HardwareWalletAssetHandler<import("libraries/hw-common/lib").Message>>;
5
7
  };
6
8
  export default metadata;
@@ -1,7 +1,12 @@
1
1
  import ethereum from './ethereum';
2
2
  const metadata = {
3
- applicationName: 'Polygon',
4
- supportedVersions: '^1.10.0',
3
+ applications: [
4
+ {
5
+ applicationName: 'Polygon',
6
+ supportedVersions: '^1.10.0',
7
+ },
8
+ ...ethereum.applications,
9
+ ],
5
10
  handler: ethereum.handler,
6
11
  };
7
12
  export default metadata;
@@ -2,8 +2,10 @@ import type Transport from '@ledgerhq/hw-transport';
2
2
  import type { HardwareWalletAssetHandler } from '@exodus/hw-common';
3
3
  declare function createHandler(transport: Transport): Promise<HardwareWalletAssetHandler>;
4
4
  declare const metadata: {
5
- applicationName: string;
6
- supportedVersions: string;
5
+ applications: {
6
+ applicationName: string;
7
+ supportedVersions: string;
8
+ }[];
7
9
  handler: typeof createHandler;
8
10
  };
9
11
  export default metadata;
@@ -2,6 +2,7 @@ import AppSolana from '@ledgerhq/hw-app-solana';
2
2
  import assert from 'minimalistic-assert';
3
3
  import bs58 from 'bs58';
4
4
  import { XPubUnsupportedError } from '@exodus/hw-common';
5
+ import { RequiresBlindSigningError } from '../errors';
5
6
  async function createHandler(transport) {
6
7
  const app = new AppSolana(transport);
7
8
  const getAddress = async (params) => {
@@ -30,7 +31,16 @@ async function createHandler(transport) {
30
31
  if (!Buffer.isBuffer(serializedTransaction)) {
31
32
  throw new TypeError(`signableTransaction must be Buffer or serializable to a Buffer.`);
32
33
  }
33
- const { signature } = await app.signTransaction(params.derivationPaths[0].replace('m/', ''), serializedTransaction);
34
+ const { signature } = await app
35
+ .signTransaction(params.derivationPaths[0].replace('m/', ''), serializedTransaction)
36
+ .catch((err) => {
37
+ if (err.message.includes('Try enabling blind signature in the app')) {
38
+ throw new RequiresBlindSigningError();
39
+ }
40
+ else {
41
+ throw err;
42
+ }
43
+ });
34
44
  const { address: publicKey } = await app.getAddress(params.derivationPaths[0].replace('m/', ''));
35
45
  return [
36
46
  {
@@ -53,8 +63,12 @@ async function createHandler(transport) {
53
63
  };
54
64
  }
55
65
  const metadata = {
56
- applicationName: 'Solana',
57
- supportedVersions: '^1.4.0',
66
+ applications: [
67
+ {
68
+ applicationName: 'Solana',
69
+ supportedVersions: '^1.4.0',
70
+ },
71
+ ],
58
72
  handler: createHandler,
59
73
  };
60
74
  export default metadata;
@@ -8,6 +8,7 @@ export type Dependencies = {
8
8
  export declare class LedgerDevice implements HardwareWalletDevice {
9
9
  #private;
10
10
  constructor({ transportFactory, descriptor }: Dependencies);
11
+ ensureApplicationIsOpened: (baseAssetName: string) => Promise<void>;
11
12
  listSupportedAssetNames: () => Promise<string[]>;
12
13
  listInstalledAssetNames: () => Promise<string[]>;
13
14
  listUseableAssetNames: () => Promise<string[]>;
@@ -2,7 +2,7 @@ import semver from 'semver';
2
2
  import { AssetUnsupportedError, DeviceLockedError, DeviceConnectionError, UserRefusedError, } from '@exodus/hw-common';
3
3
  import { applicationNameToBaseAssetName, assetApplications, supportedBaseAssetNames, } from './assets';
4
4
  import applicationFactory from './management/application';
5
- import { UnsupportedVersionError } from './errors';
5
+ import { UnsupportedVersionError, ApplicationNotFoundError } from './errors';
6
6
  export class LedgerDevice {
7
7
  #transportFactory;
8
8
  #descriptor;
@@ -10,7 +10,7 @@ export class LedgerDevice {
10
10
  this.#transportFactory = transportFactory;
11
11
  this.#descriptor = descriptor;
12
12
  }
13
- #runInSession = async (operation) => {
13
+ #runInSession = async (operation, expectErrorWhenClosing = false) => {
14
14
  const transport = await this.#transportFactory.open(this.#descriptor);
15
15
  try {
16
16
  return await operation(transport);
@@ -19,7 +19,14 @@ export class LedgerDevice {
19
19
  throw transformToCommonError(err);
20
20
  }
21
21
  finally {
22
- await transport.close();
22
+ if (expectErrorWhenClosing) {
23
+ await transport.close().catch((err) => {
24
+ console.warn(`caught error while closing ledger transport but was expected = `, err);
25
+ });
26
+ }
27
+ else {
28
+ await transport.close();
29
+ }
23
30
  }
24
31
  };
25
32
  #getAssetApplication = (assetName) => {
@@ -44,7 +51,7 @@ export class LedgerDevice {
44
51
  return this.#runInSession(async (transport) => {
45
52
  const handler = await applicationFactory(transport);
46
53
  return handler.openApplication(applicationName);
47
- });
54
+ }, true);
48
55
  };
49
56
  #quitApplication = async () => {
50
57
  return this.#runInSession(async (transport) => {
@@ -52,18 +59,37 @@ export class LedgerDevice {
52
59
  return handler.quitApplication();
53
60
  });
54
61
  };
55
- #ensureApplicationIsOpened = async (assetName) => {
56
- const { applicationName, supportedVersions } = this.#getAssetApplication(assetName);
62
+ ensureApplicationIsOpened = async (baseAssetName) => {
63
+ const { applications } = this.#getAssetApplication(baseAssetName);
64
+ const supportedApplicationNames = applications.map(({ applicationName }) => applicationName);
57
65
  const currentApplicationInfo = await this.#getInformation();
58
66
  let desiredApplicationInfo = null;
59
- if (applicationName === currentApplicationInfo.name) {
67
+ if (supportedApplicationNames.includes(currentApplicationInfo.name)) {
60
68
  desiredApplicationInfo = currentApplicationInfo;
61
69
  }
62
70
  else {
63
71
  await this.#quitApplication();
64
- await this.#openApplication(applicationName);
72
+ let applicationName = null;
73
+ for (const applicationNameToOpen of supportedApplicationNames) {
74
+ try {
75
+ await this.#openApplication(applicationNameToOpen);
76
+ applicationName = applicationNameToOpen;
77
+ break;
78
+ }
79
+ catch (err) {
80
+ const APPLICATION_NOT_INSTALLED = 26631;
81
+ if (err.statusCode === APPLICATION_NOT_INSTALLED) {
82
+ continue;
83
+ }
84
+ }
85
+ }
86
+ if (!applicationName) {
87
+ throw new ApplicationNotFoundError(supportedApplicationNames);
88
+ }
65
89
  desiredApplicationInfo = await this.#getInformation();
66
90
  }
91
+ const currentApplicationName = desiredApplicationInfo.name;
92
+ const { supportedVersions } = applications.find(({ applicationName }) => applicationName === currentApplicationName);
67
93
  const isSupportedVersion = semver.satisfies(desiredApplicationInfo.version, supportedVersions);
68
94
  if (!isSupportedVersion) {
69
95
  throw new UnsupportedVersionError(desiredApplicationInfo.version, supportedVersions);
@@ -91,35 +117,35 @@ export class LedgerDevice {
91
117
  return assetName ? [assetName] : [];
92
118
  };
93
119
  getAddress = async (params) => {
94
- await this.#ensureApplicationIsOpened(params.assetName);
120
+ await this.ensureApplicationIsOpened(params.assetName);
95
121
  return this.#runInSession(async (transport) => {
96
122
  const handler = await this.#getAssetApplication(params.assetName).handler(transport);
97
123
  return handler.getAddress(params);
98
124
  });
99
125
  };
100
126
  getXPub = async (params) => {
101
- await this.#ensureApplicationIsOpened(params.assetName);
127
+ await this.ensureApplicationIsOpened(params.assetName);
102
128
  return this.#runInSession(async (transport) => {
103
129
  const handler = await this.#getAssetApplication(params.assetName).handler(transport);
104
130
  return handler.getXPub(params);
105
131
  });
106
132
  };
107
133
  getPublicKey = async (params) => {
108
- await this.#ensureApplicationIsOpened(params.assetName);
134
+ await this.ensureApplicationIsOpened(params.assetName);
109
135
  return this.#runInSession(async (transport) => {
110
136
  const handler = await this.#getAssetApplication(params.assetName).handler(transport);
111
137
  return handler.getPublicKey(params);
112
138
  });
113
139
  };
114
140
  signTransaction = async (params) => {
115
- await this.#ensureApplicationIsOpened(params.assetName);
141
+ await this.ensureApplicationIsOpened(params.assetName);
116
142
  return this.#runInSession(async (transport) => {
117
143
  const handler = await this.#getAssetApplication(params.assetName).handler(transport);
118
144
  return handler.signTransaction(params);
119
145
  });
120
146
  };
121
147
  signMessage = async (params) => {
122
- await this.#ensureApplicationIsOpened(params.assetName);
148
+ await this.ensureApplicationIsOpened(params.assetName);
123
149
  return this.#runInSession(async (transport) => {
124
150
  const handler = await this.#getAssetApplication(params.assetName).handler(transport);
125
151
  return handler.signMessage(params);
@@ -7,8 +7,15 @@ export declare class NoDeviceFoundError extends HardwareWalletError {
7
7
  name: string;
8
8
  constructor();
9
9
  }
10
+ export declare class ApplicationNotFoundError extends HardwareWalletError {
11
+ name: string;
12
+ supportedApplications: string[];
13
+ constructor(supportedApplications: string[]);
14
+ }
10
15
  export declare class UnsupportedVersionError extends HardwareWalletError {
11
16
  name: string;
17
+ supportedVersions: string;
18
+ actualVersion: string;
12
19
  constructor(actualVersion: string, supportedVersions: string);
13
20
  }
14
21
  export declare class RequiresBlindSigningError extends HardwareWalletError {
@@ -11,10 +11,22 @@ export class NoDeviceFoundError extends HardwareWalletError {
11
11
  super(`unable to find a connected device`);
12
12
  }
13
13
  }
14
+ export class ApplicationNotFoundError extends HardwareWalletError {
15
+ name = 'ApplicationNotFoundError';
16
+ supportedApplications;
17
+ constructor(supportedApplications) {
18
+ super(`required application not installed, please install a supported application (${supportedApplications.join(', ')}).`);
19
+ this.supportedApplications = supportedApplications;
20
+ }
21
+ }
14
22
  export class UnsupportedVersionError extends HardwareWalletError {
15
23
  name = 'UnsupportedVersionError';
24
+ supportedVersions;
25
+ actualVersion;
16
26
  constructor(actualVersion, supportedVersions) {
17
27
  super(`version does not meet version requirements: ${actualVersion} does not match ${supportedVersions}`);
28
+ this.supportedVersions = supportedVersions;
29
+ this.actualVersion = actualVersion;
18
30
  }
19
31
  }
20
32
  export class RequiresBlindSigningError extends HardwareWalletError {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/hw-ledger",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "description": "The ledger module",
5
5
  "author": "Exodus Movement Inc.",
6
6
  "repository": {
@@ -67,5 +67,5 @@
67
67
  "ts-jest": "^29.0.3",
68
68
  "typescript": "^4.7.3"
69
69
  },
70
- "gitHead": "fb16ccc337a5c2b15e6321c757b454d66ff4acb2"
70
+ "gitHead": "58a6b3ab7d222a9a18a48fae2f450ebef9c67738"
71
71
  }