@exodus/hardware-wallets 2.1.0 → 3.0.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 +16 -0
- package/lib/api/index.d.ts +2 -0
- package/lib/api/index.js +2 -0
- package/lib/atoms/hardwareWalletSigningRequestsAtom.d.ts +8 -0
- package/lib/atoms/hardwareWalletSigningRequestsAtom.js +10 -0
- package/lib/atoms/index.d.ts +1 -1
- package/lib/atoms/index.js +1 -0
- package/lib/index.d.ts +14 -3
- package/lib/index.js +4 -1
- package/lib/module/hardware-wallets.d.ts +6 -4
- package/lib/module/hardware-wallets.js +93 -56
- package/lib/module/interfaces.d.ts +13 -1
- package/lib/plugin/index.d.ts +4 -2
- package/lib/plugin/index.js +11 -2
- package/lib/redux/index.d.ts +14 -2
- package/lib/redux/index.js +4 -0
- package/lib/redux/initial-state.d.ts +2 -0
- package/lib/redux/initial-state.js +1 -0
- package/lib/redux/selectors/getSigningRequests.d.ts +9 -0
- package/lib/redux/selectors/getSigningRequests.js +8 -0
- package/lib/redux/selectors/index.d.ts +8 -2
- package/lib/redux/selectors/index.js +5 -1
- package/lib/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
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.0.1](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@3.0.0...@exodus/hardware-wallets@3.0.1) (2025-06-17)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- fix: retry logic on hardware signing (#12944)
|
|
11
|
+
|
|
12
|
+
## [3.0.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@2.1.0...@exodus/hardware-wallets@3.0.0) (2025-06-16)
|
|
13
|
+
|
|
14
|
+
### ⚠ BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
- rework hardware wallet UI signing (#12852)
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
- feat!: rework hardware wallet UI signing (#12852)
|
|
21
|
+
|
|
6
22
|
## [2.1.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/hardware-wallets@2.0.5...@exodus/hardware-wallets@2.1.0) (2025-05-13)
|
|
7
23
|
|
|
8
24
|
### Features
|
package/lib/api/index.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ declare const hardwareWalletsApiDefinition: {
|
|
|
21
21
|
sync: ({ accountIndex: index, isMultisig }?: import("../module/interfaces.js").SyncParams) => Promise<import("../module/interfaces.js").SyncedKeysId>;
|
|
22
22
|
addPublicKeysToWalletAccount: ({ walletAccount, syncedKeysId }: import("../module/interfaces.js").StoreSyncedKeysParams) => Promise<void>;
|
|
23
23
|
create: ({ syncedKeysId, isMultisig }: import("../module/interfaces.js").CreateParams) => Promise<import("libraries/models/lib/index.js").WalletAccount>;
|
|
24
|
+
retrySigningRequest: (id: string) => Promise<void>;
|
|
25
|
+
cancelSigningRequest: (id: string, fromUI: boolean) => Promise<void>;
|
|
24
26
|
};
|
|
25
27
|
};
|
|
26
28
|
readonly dependencies: readonly ["hardwareWallets", "txLogMonitors", "restoreProgressTracker"];
|
package/lib/api/index.js
CHANGED
|
@@ -28,6 +28,8 @@ const createHardwareWalletsApi = ({ hardwareWallets, restoreProgressTracker, txL
|
|
|
28
28
|
sync: hardwareWallets.sync,
|
|
29
29
|
addPublicKeysToWalletAccount: hardwareWallets.addPublicKeysToWalletAccount,
|
|
30
30
|
create: hardwareWallets.create,
|
|
31
|
+
retrySigningRequest: hardwareWallets.retrySigningRequest,
|
|
32
|
+
cancelSigningRequest: hardwareWallets.cancelSigningRequest,
|
|
31
33
|
},
|
|
32
34
|
};
|
|
33
35
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SigningRequestState } from '../module/interfaces.js';
|
|
2
|
+
export declare const hardwareWalletSigningRequestsAtomDefinition: {
|
|
3
|
+
readonly id: "hardwareWalletSigningRequestsAtom";
|
|
4
|
+
readonly type: "atom";
|
|
5
|
+
readonly factory: () => import("@exodus/atoms").Atom<SigningRequestState>;
|
|
6
|
+
readonly dependencies: readonly [];
|
|
7
|
+
readonly public: false;
|
|
8
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createInMemoryAtom } from '@exodus/atoms';
|
|
2
|
+
export const hardwareWalletSigningRequestsAtomDefinition = {
|
|
3
|
+
id: 'hardwareWalletSigningRequestsAtom',
|
|
4
|
+
type: 'atom',
|
|
5
|
+
factory: () => createInMemoryAtom({
|
|
6
|
+
defaultValue: Object.create(null),
|
|
7
|
+
}),
|
|
8
|
+
dependencies: [],
|
|
9
|
+
public: false,
|
|
10
|
+
};
|
package/lib/atoms/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { hardwareWalletSigningRequestsAtomDefinition } from './hardwareWalletSigningRequestsAtom.js';
|
|
1
2
|
type WalletAccountName = string;
|
|
2
3
|
type AssetName = string;
|
|
3
4
|
export type WalletAccountNameToConnectedAssetNamesMap = Record<WalletAccountName, AssetName[]>;
|
|
@@ -8,4 +9,3 @@ export declare const hardwareWalletConnectedAssetNamesAtomDefinition: {
|
|
|
8
9
|
readonly factory: ({ assetsModule, hardwareWalletPublicKeysAtom, walletAccountsAtom, }: any) => import("@exodus/atoms").ReadonlyAtom<unknown>;
|
|
9
10
|
readonly dependencies: readonly ["hardwareWalletPublicKeysAtom", "assetsModule", "walletAccountsAtom"];
|
|
10
11
|
};
|
|
11
|
-
export {};
|
package/lib/atoms/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { combine, compute } from '@exodus/atoms';
|
|
2
2
|
import { memoize } from '@exodus/basic-utils';
|
|
3
3
|
import { HARDENED_OFFSET } from '@exodus/bip32';
|
|
4
|
+
export { hardwareWalletSigningRequestsAtomDefinition } from './hardwareWalletSigningRequestsAtom.js';
|
|
4
5
|
export const createHardwareWalletConnectedAssetNamesAtom = ({ assetsModule, hardwareWalletPublicKeysAtom, walletAccountsAtom, }) => {
|
|
5
6
|
const selector = memoize(({ hardwareWalletPublicKeys, walletAccounts }) => {
|
|
6
7
|
const result = Object.create(null);
|
package/lib/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ declare const hardwareWallets: () => {
|
|
|
24
24
|
sync: ({ accountIndex: index, isMultisig }?: import("./module/interfaces.js").SyncParams) => Promise<import("./module/interfaces.js").SyncedKeysId>;
|
|
25
25
|
addPublicKeysToWalletAccount: ({ walletAccount, syncedKeysId }: import("./module/interfaces.js").StoreSyncedKeysParams) => Promise<void>;
|
|
26
26
|
create: ({ syncedKeysId, isMultisig }: import("./module/interfaces.js").CreateParams) => Promise<import("libraries/models/lib/index.js").WalletAccount>;
|
|
27
|
+
retrySigningRequest: (id: string) => Promise<void>;
|
|
28
|
+
cancelSigningRequest: (id: string, fromUI: boolean) => Promise<void>;
|
|
27
29
|
};
|
|
28
30
|
};
|
|
29
31
|
readonly dependencies: readonly ["hardwareWallets", "txLogMonitors", "restoreProgressTracker"];
|
|
@@ -33,7 +35,7 @@ declare const hardwareWallets: () => {
|
|
|
33
35
|
readonly id: "hardwareWallets";
|
|
34
36
|
readonly type: "module";
|
|
35
37
|
readonly factory: (opts: import("./module/hardware-wallets.js").Dependencies) => import("./module/hardware-wallets.js").HardwareWallets;
|
|
36
|
-
readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "
|
|
38
|
+
readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "publicKeyStore", "hardwareWalletSigningRequestsAtom", "wallet", "walletAccountsAtom", "walletAccounts"];
|
|
37
39
|
readonly public: true;
|
|
38
40
|
};
|
|
39
41
|
}, {
|
|
@@ -43,19 +45,28 @@ declare const hardwareWallets: () => {
|
|
|
43
45
|
readonly factory: ({ assetsModule, hardwareWalletPublicKeysAtom, walletAccountsAtom, }: any) => import("libraries/atoms/lib/index.js").ReadonlyAtom<unknown>;
|
|
44
46
|
readonly dependencies: readonly ["hardwareWalletPublicKeysAtom", "assetsModule", "walletAccountsAtom"];
|
|
45
47
|
};
|
|
48
|
+
}, {
|
|
49
|
+
readonly definition: {
|
|
50
|
+
readonly id: "hardwareWalletSigningRequestsAtom";
|
|
51
|
+
readonly type: "atom";
|
|
52
|
+
readonly factory: () => import("libraries/atoms/lib/index.js").Atom<import("./module/interfaces.js").SigningRequestState>;
|
|
53
|
+
readonly dependencies: readonly [];
|
|
54
|
+
readonly public: false;
|
|
55
|
+
};
|
|
46
56
|
}, {
|
|
47
57
|
readonly definition: {
|
|
48
58
|
readonly id: "hardwareWalletsPlugin";
|
|
49
59
|
readonly type: "plugin";
|
|
50
|
-
readonly factory: ({ hardwareWalletConnectedAssetNamesAtom, port, }: {
|
|
60
|
+
readonly factory: ({ hardwareWalletConnectedAssetNamesAtom, hardwareWalletSigningRequestsAtom, port, }: {
|
|
51
61
|
hardwareWalletConnectedAssetNamesAtom: import("libraries/atoms/lib/index.js").Atom<import("./atoms/index.js").WalletAccountNameToConnectedAssetNamesMap>;
|
|
62
|
+
hardwareWalletSigningRequestsAtom: import("libraries/atoms/lib/index.js").Atom<import("./module/interfaces.js").SigningRequestState>;
|
|
52
63
|
port: import("./shared/types.js").Port;
|
|
53
64
|
}) => {
|
|
54
65
|
onUnlock: () => void;
|
|
55
66
|
onLoad: () => void;
|
|
56
67
|
onStop: () => void;
|
|
57
68
|
};
|
|
58
|
-
readonly dependencies: readonly ["hardwareWalletConnectedAssetNamesAtom", "port"];
|
|
69
|
+
readonly dependencies: readonly ["hardwareWalletConnectedAssetNamesAtom", "hardwareWalletSigningRequestsAtom", "port"];
|
|
59
70
|
};
|
|
60
71
|
}];
|
|
61
72
|
};
|
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 } 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 {
|
|
@@ -15,6 +15,9 @@ const hardwareWallets = () => {
|
|
|
15
15
|
{
|
|
16
16
|
definition: hardwareWalletConnectedAssetNamesAtomDefinition,
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
definition: hardwareWalletSigningRequestsAtomDefinition,
|
|
20
|
+
},
|
|
18
21
|
{
|
|
19
22
|
definition: hardwareWalletsPluginDefinition,
|
|
20
23
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WalletAccount } from '@exodus/models';
|
|
2
2
|
import Emitter from '@exodus/wild-emitter';
|
|
3
|
-
import type { HardwareSignerProvider, CanAccessAssetParams, CreateParams, StoreSyncedKeysParams, ScanParams, SyncParams, EnsureApplicationIsOpenedParams, SignTransactionParams, ScanResult, SyncedKeysId, GetAddressParams, RequireDeviceForParams } from './interfaces.js';
|
|
3
|
+
import type { HardwareSignerProvider, CanAccessAssetParams, CreateParams, StoreSyncedKeysParams, ScanParams, SyncParams, EnsureApplicationIsOpenedParams, SignTransactionParams, ScanResult, SyncedKeysId, GetAddressParams, RequireDeviceForParams, SigningRequestState } from './interfaces.js';
|
|
4
4
|
import type { HardwareWalletDiscovery, 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';
|
|
@@ -9,7 +9,7 @@ export type Dependencies = {
|
|
|
9
9
|
assetsModule: any;
|
|
10
10
|
ledgerDiscovery: HardwareWalletDiscovery;
|
|
11
11
|
logger: Logger;
|
|
12
|
-
|
|
12
|
+
hardwareWalletSigningRequestsAtom: Atom<SigningRequestState>;
|
|
13
13
|
publicKeyStore: IPublicKeyStore;
|
|
14
14
|
wallet: any;
|
|
15
15
|
walletAccountsAtom: Atom<WalletAccount>;
|
|
@@ -20,7 +20,9 @@ export type Dependencies = {
|
|
|
20
20
|
export declare class HardwareWallets implements HardwareSignerProvider {
|
|
21
21
|
#private;
|
|
22
22
|
readonly events: Emitter<string, any>;
|
|
23
|
-
constructor({ assetsModule, ledgerDiscovery, logger,
|
|
23
|
+
constructor({ assetsModule, ledgerDiscovery, logger, hardwareWalletSigningRequestsAtom, publicKeyStore, wallet, walletAccountsAtom, walletAccounts, }: Dependencies);
|
|
24
|
+
retrySigningRequest: (id: string) => Promise<void>;
|
|
25
|
+
cancelSigningRequest: (id: string, fromUI: boolean) => Promise<void>;
|
|
24
26
|
signTransaction: ({ baseAssetName, unsignedTx, walletAccount, multisigData, }: SignTransactionParams) => Promise<any>;
|
|
25
27
|
signMessage: ({ assetName, derivationPath, message }: SignMessageParams) => Promise<any>;
|
|
26
28
|
isDeviceConnected: () => Promise<boolean>;
|
|
@@ -46,7 +48,7 @@ declare const hardwareWalletsModuleDefinition: {
|
|
|
46
48
|
readonly id: "hardwareWallets";
|
|
47
49
|
readonly type: "module";
|
|
48
50
|
readonly factory: (opts: Dependencies) => HardwareWallets;
|
|
49
|
-
readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "
|
|
51
|
+
readonly dependencies: readonly ["assetsModule", "logger", "ledgerDiscovery", "publicKeyStore", "hardwareWalletSigningRequestsAtom", "wallet", "walletAccountsAtom", "walletAccounts"];
|
|
50
52
|
readonly public: true;
|
|
51
53
|
};
|
|
52
54
|
export default hardwareWalletsModuleDefinition;
|
|
@@ -4,23 +4,26 @@ import Emitter from '@exodus/wild-emitter';
|
|
|
4
4
|
import { randomBytes } from '@exodus/crypto/randomBytes';
|
|
5
5
|
import delay from 'delay';
|
|
6
6
|
import { NoDeviceFoundError, UserRefusedError } from '@exodus/hw-common';
|
|
7
|
+
import restrictConcurrency from 'make-concurrent';
|
|
8
|
+
import pDefer from 'p-defer';
|
|
7
9
|
export class HardwareWallets {
|
|
8
10
|
#assetsModule;
|
|
9
11
|
#ledgerDiscovery;
|
|
10
12
|
#logger;
|
|
11
|
-
#userInterface;
|
|
12
13
|
#publicKeyStore;
|
|
14
|
+
#signingRequestAtom;
|
|
13
15
|
#wallet;
|
|
14
16
|
#walletAccountsAtom;
|
|
15
17
|
#walletAccounts;
|
|
16
18
|
#syncedKeysMap = new Map();
|
|
19
|
+
#signingRequest;
|
|
17
20
|
events = new Emitter();
|
|
18
|
-
constructor({ assetsModule, ledgerDiscovery, logger,
|
|
21
|
+
constructor({ assetsModule, ledgerDiscovery, logger, hardwareWalletSigningRequestsAtom, publicKeyStore, wallet, walletAccountsAtom, walletAccounts, }) {
|
|
19
22
|
this.#assetsModule = assetsModule;
|
|
20
23
|
this.#ledgerDiscovery = ledgerDiscovery;
|
|
21
24
|
this.#logger = logger;
|
|
22
|
-
this.#userInterface = userInterface;
|
|
23
25
|
this.#publicKeyStore = publicKeyStore;
|
|
26
|
+
this.#signingRequestAtom = hardwareWalletSigningRequestsAtom;
|
|
24
27
|
this.#wallet = wallet;
|
|
25
28
|
this.#walletAccountsAtom = walletAccountsAtom;
|
|
26
29
|
this.#walletAccounts = walletAccounts;
|
|
@@ -32,66 +35,100 @@ export class HardwareWallets {
|
|
|
32
35
|
}
|
|
33
36
|
throw new NoDeviceFoundError();
|
|
34
37
|
};
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
#updateSigningRequest = async (state) => {
|
|
39
|
+
this.#logger.debug(`Updating signing request state: ${JSON.stringify(state)}`);
|
|
40
|
+
await this.#signingRequestAtom.set(state);
|
|
41
|
+
this.#logger.debug(`Finished updating signing request state: ${JSON.stringify(state)}`);
|
|
38
42
|
};
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
#deleteSigningRequest = async (id) => {
|
|
44
|
+
if (this.#signingRequest?.id === id) {
|
|
45
|
+
this.#signingRequest = undefined;
|
|
46
|
+
await this.#signingRequestAtom.reset();
|
|
47
|
+
this.#logger.debug(`Signing request with id: ${id} has been deleted`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.#logger.warn(`No signing request found for id: ${id}`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
retrySigningRequest = async (id) => {
|
|
54
|
+
const request = this.#signingRequest;
|
|
55
|
+
if (request?.id !== id) {
|
|
56
|
+
this.#logger.warn(`No signing request found for id: ${id}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
this.#logger.debug(`Attempting to get selected device for signing request with id: ${id}`);
|
|
61
|
+
const { device } = await this.#getSelectedDevice();
|
|
62
|
+
this.#logger.debug(`Attempting to sign for signing request with id: ${id}`);
|
|
63
|
+
const result = await request.sign({ device });
|
|
64
|
+
await this.#deleteSigningRequest(id);
|
|
65
|
+
request.resolve(result);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (this.#signingRequest?.id !== id) {
|
|
69
|
+
this.#logger.warn(`Signing request with id: ${id} was cancelled, not retrying`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const _error = error;
|
|
73
|
+
if (['DisconnectedDevice', 'DisconnectedDeviceDuringOperation'].includes(_error.name)) {
|
|
74
|
+
this.#logger.debug(`Device disconnected during signing request, likely due to app opening: ${id}`, _error);
|
|
75
|
+
await delay(300);
|
|
76
|
+
await this.retrySigningRequest(id);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (_error.message.includes('timeout') || _error.name === 'UserRefusedError') {
|
|
80
|
+
await this.cancelSigningRequest(id, false);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await this.#updateSigningRequest({
|
|
84
|
+
id,
|
|
85
|
+
scenario: 'error',
|
|
86
|
+
error: _error,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
cancelSigningRequest = async (id, fromUI) => {
|
|
91
|
+
const request = this.#signingRequest;
|
|
92
|
+
this.#logger.debug(`Cancelling signing request for id: ${id}, fromUI: ${fromUI}`);
|
|
93
|
+
if (request?.id !== id) {
|
|
94
|
+
this.#logger.warn(`No signing request found for id: ${id}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
await this.#deleteSigningRequest(id);
|
|
98
|
+
if (fromUI) {
|
|
99
|
+
this.#logger.debug(`Cancelling signing request on device for id: ${id}`);
|
|
43
100
|
try {
|
|
44
|
-
const approvePromise = this.#requestUserAction({
|
|
45
|
-
scenario,
|
|
46
|
-
baseAssetName,
|
|
47
|
-
});
|
|
48
101
|
const { device } = await this.#getSelectedDevice();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return sign({ device });
|
|
52
|
-
};
|
|
53
|
-
const result = await Promise.race([approvePromise, runSign()]);
|
|
54
|
-
if (typeof result === 'object' && result.tryAgain === false) {
|
|
55
|
-
void device.cancelAction();
|
|
56
|
-
throw new UserRefusedError(false);
|
|
57
|
-
}
|
|
58
|
-
void this.#requestUserAction({ scenario: 'completed' });
|
|
59
|
-
return result;
|
|
102
|
+
await device.cancelAction();
|
|
103
|
+
this.#logger.debug(`Succesfully cancelled signing request on device for id: ${id}`);
|
|
60
104
|
}
|
|
61
105
|
catch (error) {
|
|
62
|
-
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
else if (error.name === 'UserRefusedError') {
|
|
66
|
-
void this.#requestUserAction({ scenario: 'completed' });
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
if (['DisconnectedDevice', 'DisconnectedDeviceDuringOperation'].includes(error.name)) {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const { tryAgain } = await this.#requestUserAction({
|
|
74
|
-
error,
|
|
75
|
-
baseAssetName,
|
|
76
|
-
});
|
|
77
|
-
if (!tryAgain) {
|
|
78
|
-
this.#logger.warn(error);
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (_error) {
|
|
83
|
-
this.#logger.error(_error);
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
finally {
|
|
88
|
-
attempts++;
|
|
89
|
-
await delay(200);
|
|
106
|
+
this.#logger.error(`Failed to cancel signing request on device for id: ${id}`, error);
|
|
90
107
|
}
|
|
91
108
|
}
|
|
92
|
-
|
|
93
|
-
throw new UserRefusedError(false);
|
|
109
|
+
request.reject(new UserRefusedError(!fromUI));
|
|
94
110
|
};
|
|
111
|
+
#signGeneric = restrictConcurrency(async ({ baseAssetName, scenario, sign }) => {
|
|
112
|
+
const id = randomBytes(16).toString('hex');
|
|
113
|
+
this.#logger.debug(`Starting signing request for ${baseAssetName} with scenario: ${scenario} and id: ${id}`);
|
|
114
|
+
const deferred = pDefer();
|
|
115
|
+
this.#signingRequest = {
|
|
116
|
+
id,
|
|
117
|
+
sign: async ({ device }) => {
|
|
118
|
+
await this.#updateSigningRequest({
|
|
119
|
+
id,
|
|
120
|
+
baseAssetName,
|
|
121
|
+
scenario,
|
|
122
|
+
});
|
|
123
|
+
await device.ensureApplicationIsOpened(baseAssetName);
|
|
124
|
+
return sign({ device });
|
|
125
|
+
},
|
|
126
|
+
resolve: deferred.resolve,
|
|
127
|
+
reject: deferred.reject,
|
|
128
|
+
};
|
|
129
|
+
void this.retrySigningRequest(id);
|
|
130
|
+
return deferred.promise;
|
|
131
|
+
});
|
|
95
132
|
signTransaction = async ({ baseAssetName, unsignedTx, walletAccount, multisigData, }) => {
|
|
96
133
|
const baseAsset = this.#assetsModule.getAsset(baseAssetName);
|
|
97
134
|
const accountIndex = walletAccount.index;
|
|
@@ -345,8 +382,8 @@ const hardwareWalletsModuleDefinition = {
|
|
|
345
382
|
'assetsModule',
|
|
346
383
|
'logger',
|
|
347
384
|
'ledgerDiscovery',
|
|
348
|
-
'userInterface',
|
|
349
385
|
'publicKeyStore',
|
|
386
|
+
'hardwareWalletSigningRequestsAtom',
|
|
350
387
|
'wallet',
|
|
351
388
|
'walletAccountsAtom',
|
|
352
389
|
'walletAccounts',
|
|
@@ -81,9 +81,21 @@ export type GenericSignCallback = ({ device }: {
|
|
|
81
81
|
}) => Promise<any>;
|
|
82
82
|
export interface GenericSignParams {
|
|
83
83
|
baseAssetName: string;
|
|
84
|
-
scenario:
|
|
84
|
+
scenario: 'signTransaction' | 'signMessage';
|
|
85
85
|
sign: GenericSignCallback;
|
|
86
86
|
}
|
|
87
|
+
export interface SigningRequestState {
|
|
88
|
+
id: string;
|
|
89
|
+
baseAssetName?: string;
|
|
90
|
+
scenario: 'signTransaction' | 'signMessage' | 'error';
|
|
91
|
+
error?: Error;
|
|
92
|
+
}
|
|
93
|
+
export interface SigningRequest {
|
|
94
|
+
id: string;
|
|
95
|
+
sign: GenericSignCallback;
|
|
96
|
+
resolve: (result: any) => void;
|
|
97
|
+
reject: (error: Error) => void;
|
|
98
|
+
}
|
|
87
99
|
export interface SignTransactionParams {
|
|
88
100
|
baseAssetName: string;
|
|
89
101
|
unsignedTx: UnsignedTransaction;
|
package/lib/plugin/index.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { Atom } from '@exodus/atoms';
|
|
2
2
|
import type { Port } from '../shared/types';
|
|
3
3
|
import type { WalletAccountNameToConnectedAssetNamesMap } from '../atoms/index.js';
|
|
4
|
+
import type { SigningRequestState } from '../module/interfaces.js';
|
|
4
5
|
declare const hardwareWalletsPluginDefinition: {
|
|
5
6
|
readonly id: "hardwareWalletsPlugin";
|
|
6
7
|
readonly type: "plugin";
|
|
7
|
-
readonly factory: ({ hardwareWalletConnectedAssetNamesAtom, port, }: {
|
|
8
|
+
readonly factory: ({ hardwareWalletConnectedAssetNamesAtom, hardwareWalletSigningRequestsAtom, port, }: {
|
|
8
9
|
hardwareWalletConnectedAssetNamesAtom: Atom<WalletAccountNameToConnectedAssetNamesMap>;
|
|
10
|
+
hardwareWalletSigningRequestsAtom: Atom<SigningRequestState>;
|
|
9
11
|
port: Port;
|
|
10
12
|
}) => {
|
|
11
13
|
onUnlock: () => void;
|
|
12
14
|
onLoad: () => void;
|
|
13
15
|
onStop: () => void;
|
|
14
16
|
};
|
|
15
|
-
readonly dependencies: readonly ["hardwareWalletConnectedAssetNamesAtom", "port"];
|
|
17
|
+
readonly dependencies: readonly ["hardwareWalletConnectedAssetNamesAtom", "hardwareWalletSigningRequestsAtom", "port"];
|
|
16
18
|
};
|
|
17
19
|
export default hardwareWalletsPluginDefinition;
|
package/lib/plugin/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { createAtomObserver } from '@exodus/atoms';
|
|
2
|
-
const createHardwareWalletsPlugin = ({ hardwareWalletConnectedAssetNamesAtom, port, }) => {
|
|
2
|
+
const createHardwareWalletsPlugin = ({ hardwareWalletConnectedAssetNamesAtom, hardwareWalletSigningRequestsAtom, port, }) => {
|
|
3
3
|
const observers = [
|
|
4
4
|
createAtomObserver({
|
|
5
5
|
atom: hardwareWalletConnectedAssetNamesAtom,
|
|
6
6
|
port,
|
|
7
7
|
event: 'hardwareWalletConnectedAssetNames',
|
|
8
8
|
}),
|
|
9
|
+
createAtomObserver({
|
|
10
|
+
atom: hardwareWalletSigningRequestsAtom,
|
|
11
|
+
port,
|
|
12
|
+
event: 'hardwareWalletSigningRequests',
|
|
13
|
+
}),
|
|
9
14
|
];
|
|
10
15
|
observers.forEach((observer) => observer.register());
|
|
11
16
|
const start = () => {
|
|
@@ -29,6 +34,10 @@ const hardwareWalletsPluginDefinition = {
|
|
|
29
34
|
id: 'hardwareWalletsPlugin',
|
|
30
35
|
type: 'plugin',
|
|
31
36
|
factory: createHardwareWalletsPlugin,
|
|
32
|
-
dependencies: [
|
|
37
|
+
dependencies: [
|
|
38
|
+
'hardwareWalletConnectedAssetNamesAtom',
|
|
39
|
+
'hardwareWalletSigningRequestsAtom',
|
|
40
|
+
'port',
|
|
41
|
+
],
|
|
33
42
|
};
|
|
34
43
|
export default hardwareWalletsPluginDefinition;
|
package/lib/redux/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SigningRequestState } from '../module/interfaces.js';
|
|
1
2
|
import { type HardwareWalletsState } from './initial-state.js';
|
|
2
3
|
declare const hardwareWalletsReduxDefinition: {
|
|
3
4
|
readonly id: "hardwareWallets";
|
|
@@ -6,14 +7,25 @@ declare const hardwareWalletsReduxDefinition: {
|
|
|
6
7
|
readonly eventReducers: {
|
|
7
8
|
readonly hardwareWalletConnectedAssetNames: (state: HardwareWalletsState, payload: boolean) => {
|
|
8
9
|
walletAccountNameToConnectedAssetNamesMap: boolean;
|
|
10
|
+
signingRequests: SigningRequestState;
|
|
11
|
+
};
|
|
12
|
+
readonly hardwareWalletSigningRequests: (state: HardwareWalletsState, payload: SigningRequestState) => {
|
|
13
|
+
signingRequests: SigningRequestState;
|
|
14
|
+
walletAccountNameToConnectedAssetNamesMap: import("../atoms/index.js").WalletAccountNameToConnectedAssetNamesMap;
|
|
9
15
|
};
|
|
10
16
|
};
|
|
11
|
-
readonly selectorDefinitions: {
|
|
17
|
+
readonly selectorDefinitions: ({
|
|
12
18
|
readonly id: "isAssetNameConnectedForWalletAccount";
|
|
13
19
|
readonly selectorFactory: (selfSelector: any) => any;
|
|
14
20
|
readonly dependencies: readonly [{
|
|
15
21
|
readonly selector: string;
|
|
16
22
|
}];
|
|
17
|
-
}
|
|
23
|
+
} | {
|
|
24
|
+
readonly id: "getSigningRequests";
|
|
25
|
+
readonly resultFunction: (self: HardwareWalletsState) => SigningRequestState;
|
|
26
|
+
readonly dependencies: readonly [{
|
|
27
|
+
readonly selector: string;
|
|
28
|
+
}];
|
|
29
|
+
})[];
|
|
18
30
|
};
|
|
19
31
|
export default hardwareWalletsReduxDefinition;
|
package/lib/redux/index.js
CHANGED
|
@@ -10,6 +10,10 @@ const hardwareWalletsReduxDefinition = {
|
|
|
10
10
|
...state,
|
|
11
11
|
walletAccountNameToConnectedAssetNamesMap: payload,
|
|
12
12
|
}),
|
|
13
|
+
hardwareWalletSigningRequests: (state, payload) => ({
|
|
14
|
+
...state,
|
|
15
|
+
signingRequests: payload,
|
|
16
|
+
}),
|
|
13
17
|
},
|
|
14
18
|
selectorDefinitions,
|
|
15
19
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { WalletAccountNameToConnectedAssetNamesMap } from '../atoms/index.js';
|
|
2
|
+
import type { SigningRequestState } from '../module/interfaces.js';
|
|
2
3
|
export type HardwareWalletsState = {
|
|
3
4
|
walletAccountNameToConnectedAssetNamesMap: WalletAccountNameToConnectedAssetNamesMap;
|
|
5
|
+
signingRequests: SigningRequestState;
|
|
4
6
|
};
|
|
5
7
|
declare const initialState: HardwareWalletsState;
|
|
6
8
|
export default initialState;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HardwareWalletsState } from '../initial-state.js';
|
|
2
|
+
declare const assetDataSelectorDefinition: {
|
|
3
|
+
readonly id: "getSigningRequests";
|
|
4
|
+
readonly resultFunction: (self: HardwareWalletsState) => import("../../module/interfaces.js").SigningRequestState;
|
|
5
|
+
readonly dependencies: readonly [{
|
|
6
|
+
readonly selector: string;
|
|
7
|
+
}];
|
|
8
|
+
};
|
|
9
|
+
export default assetDataSelectorDefinition;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MY_STATE } from '@exodus/redux-dependency-injection';
|
|
2
|
+
const resultFunction = (self) => self.signingRequests;
|
|
3
|
+
const assetDataSelectorDefinition = {
|
|
4
|
+
id: 'getSigningRequests',
|
|
5
|
+
resultFunction,
|
|
6
|
+
dependencies: [{ selector: MY_STATE }],
|
|
7
|
+
};
|
|
8
|
+
export default assetDataSelectorDefinition;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
declare const hardwareWalletsSelectors: {
|
|
1
|
+
declare const hardwareWalletsSelectors: ({
|
|
2
2
|
readonly id: "isAssetNameConnectedForWalletAccount";
|
|
3
3
|
readonly selectorFactory: (selfSelector: any) => any;
|
|
4
4
|
readonly dependencies: readonly [{
|
|
5
5
|
readonly selector: string;
|
|
6
6
|
}];
|
|
7
|
-
}
|
|
7
|
+
} | {
|
|
8
|
+
readonly id: "getSigningRequests";
|
|
9
|
+
readonly resultFunction: (self: import("../initial-state.js").HardwareWalletsState) => import("../../module/interfaces.js").SigningRequestState;
|
|
10
|
+
readonly dependencies: readonly [{
|
|
11
|
+
readonly selector: string;
|
|
12
|
+
}];
|
|
13
|
+
})[];
|
|
8
14
|
export default hardwareWalletsSelectors;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import isAssetNameConnectedForWalletAccountSelectorDefinition from './isAssetNameConnectedForWalletAccount.js';
|
|
2
|
-
|
|
2
|
+
import getSigningRequests from './getSigningRequests.js';
|
|
3
|
+
const hardwareWalletsSelectors = [
|
|
4
|
+
isAssetNameConnectedForWalletAccountSelectorDefinition,
|
|
5
|
+
getSigningRequests,
|
|
6
|
+
];
|
|
3
7
|
export default hardwareWalletsSelectors;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/index.ts","../src/types.d.ts","../src/api/index.ts","../src/atoms/hardwareWalletSigningRequestsAtom.ts","../src/atoms/index.ts","../src/module/hardware-wallets.ts","../src/module/index.ts","../src/module/interfaces.ts","../src/plugin/index.ts","../src/redux/id.ts","../src/redux/index.ts","../src/redux/initial-state.ts","../src/redux/selectors/getSigningRequests.ts","../src/redux/selectors/index.ts","../src/redux/selectors/isAssetNameConnectedForWalletAccount.ts","../src/shared/types.d.ts"],"version":"5.8.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/hardware-wallets",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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": {
|
|
@@ -37,19 +37,20 @@
|
|
|
37
37
|
"@exodus/redux-dependency-injection": "^4.0.0",
|
|
38
38
|
"@exodus/wild-emitter": "^1.1.0",
|
|
39
39
|
"delay": "^5.0.0",
|
|
40
|
+
"make-concurrent": "^5.4.0",
|
|
40
41
|
"minimalistic-assert": "^1.0.1",
|
|
42
|
+
"p-defer": "^4.0.1",
|
|
41
43
|
"reselect": "^3.0.1"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@exodus/dependency-types": "^2.1.1",
|
|
45
47
|
"@exodus/key-identifier": "^1.3.0",
|
|
46
48
|
"@exodus/logger": "^1.2.3",
|
|
47
|
-
"@exodus/public-key-provider": "^4.
|
|
48
|
-
"p-defer": "^4.0.1",
|
|
49
|
+
"@exodus/public-key-provider": "^4.2.0",
|
|
49
50
|
"redux": "^4.2.1"
|
|
50
51
|
},
|
|
51
52
|
"publishConfig": {
|
|
52
53
|
"access": "public"
|
|
53
54
|
},
|
|
54
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "7fb146fc50144206709f1de7d08b346191ef5a13"
|
|
55
56
|
}
|