@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 +12 -0
- package/lib/api/index.d.ts +1 -1
- package/lib/atoms/index.d.ts +0 -1
- package/lib/atoms/index.js +0 -1
- package/lib/index.d.ts +2 -9
- package/lib/index.js +1 -4
- package/lib/module/hardware-wallets.d.ts +5 -5
- package/lib/module/hardware-wallets.js +34 -14
- package/lib/module/interfaces.d.ts +2 -2
- package/package.json +3 -3
- package/lib/atoms/hardwareWalletPairingPromptAtom.d.ts +0 -6
- package/lib/atoms/hardwareWalletPairingPromptAtom.js +0 -9
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
|
package/lib/api/index.d.ts
CHANGED
|
@@ -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>;
|
package/lib/atoms/index.d.ts
CHANGED
|
@@ -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[]>;
|
package/lib/atoms/index.js
CHANGED
|
@@ -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", "
|
|
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,
|
|
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,
|
|
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", "
|
|
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,
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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
|
|
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.#
|
|
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
|
+
"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.
|
|
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": "
|
|
56
|
+
"gitHead": "7c1b8b88571d94cec15b9512e5301922ac1b6017"
|
|
57
57
|
}
|