@onekeyfe/hd-core 1.2.0-alpha.58 → 1.2.0-alpha.59

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.
Files changed (91) hide show
  1. package/__tests__/DeviceCommands.test.ts +5 -15
  2. package/__tests__/device-pool-state.test.ts +22 -0
  3. package/__tests__/device-state-mapper.test.ts +12 -0
  4. package/__tests__/device-utils.test.ts +48 -1
  5. package/__tests__/deviceSettings.test.ts +10 -0
  6. package/__tests__/deviceUploadNft.test.ts +4 -28
  7. package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
  8. package/__tests__/protocol-v2-resources.test.ts +28 -13
  9. package/__tests__/protocol-v2-unlock-policy.test.ts +34 -0
  10. package/__tests__/protocol-v2.test.ts +159 -137
  11. package/__tests__/tron-sign-message-init.test.ts +2 -2
  12. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  13. package/dist/api/FirmwareUpdateV4.d.ts +4 -3
  14. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  15. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  16. package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
  17. package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
  18. package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
  19. package/dist/api/polkadot/networks.d.ts +1 -1
  20. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  21. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  22. package/dist/api/solana/SolSignMessage.d.ts +1 -1
  23. package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
  24. package/dist/api/solana/SolSignTransaction.d.ts +2 -2
  25. package/dist/api/sui/SuiGetAddress.d.ts +1 -1
  26. package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
  27. package/dist/api/sui/SuiSignMessage.d.ts +1 -1
  28. package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
  29. package/dist/api/ton/TonGetAddress.d.ts +1 -1
  30. package/dist/api/ton/TonSignMessage.d.ts +1 -1
  31. package/dist/api/ton/TonSignProof.d.ts +1 -1
  32. package/dist/api/tron/TronSignMessage.d.ts +2 -2
  33. package/dist/api/tron/TronSignTransaction.d.ts +1 -1
  34. package/dist/data-manager/DataManager.d.ts +7 -5
  35. package/dist/data-manager/DataManager.d.ts.map +1 -1
  36. package/dist/device/Device.d.ts.map +1 -1
  37. package/dist/device/DeviceCommands.d.ts.map +1 -1
  38. package/dist/device/DevicePool.d.ts.map +1 -1
  39. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
  40. package/dist/index.d.ts +44 -34
  41. package/dist/index.js +231 -168
  42. package/dist/protocols/protocol-v2/resources.d.ts +2 -2
  43. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  44. package/dist/types/api/checkAllFirmwareRelease.d.ts +1 -1
  45. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  46. package/dist/types/api/firmwareUpdate.d.ts +3 -2
  47. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  48. package/dist/types/device.d.ts +2 -2
  49. package/dist/types/device.d.ts.map +1 -1
  50. package/dist/types/settings.d.ts +22 -14
  51. package/dist/types/settings.d.ts.map +1 -1
  52. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  53. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  54. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  55. package/dist/utils/deviceSettings.d.ts.map +1 -1
  56. package/package.json +4 -4
  57. package/src/api/CheckAllFirmwareRelease.ts +7 -5
  58. package/src/api/FirmwareUpdateV4.ts +115 -102
  59. package/src/api/OpenWalletSession.ts +1 -0
  60. package/src/api/kaspa/KaspaGetAddress.ts +1 -1
  61. package/src/api/kaspa/KaspaSignTransaction.ts +1 -1
  62. package/src/api/polkadot/networks.ts +3 -3
  63. package/src/api/protocol-v2/DeviceUploadNft.ts +7 -12
  64. package/src/api/protocol-v2/helpers.ts +0 -1
  65. package/src/api/solana/SolSignMessage.ts +1 -1
  66. package/src/api/solana/SolSignOffchainMessage.ts +1 -1
  67. package/src/api/solana/SolSignTransaction.ts +2 -2
  68. package/src/api/sui/SuiGetAddress.ts +1 -1
  69. package/src/api/sui/SuiGetPublicKey.ts +1 -1
  70. package/src/api/sui/SuiSignMessage.ts +1 -1
  71. package/src/api/sui/SuiSignTransaction.ts +1 -1
  72. package/src/api/ton/TonGetAddress.ts +1 -1
  73. package/src/api/ton/TonSignMessage.ts +1 -1
  74. package/src/api/ton/TonSignProof.ts +1 -1
  75. package/src/api/tron/TronSignMessage.ts +2 -2
  76. package/src/api/tron/TronSignTransaction.ts +1 -1
  77. package/src/data-manager/DataManager.ts +28 -9
  78. package/src/device/Device.ts +15 -6
  79. package/src/device/DeviceCommands.ts +13 -10
  80. package/src/device/DevicePool.ts +17 -8
  81. package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
  82. package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
  83. package/src/protocols/protocol-v2/resources.ts +44 -13
  84. package/src/types/api/checkAllFirmwareRelease.ts +1 -1
  85. package/src/types/api/firmwareUpdate.ts +5 -5
  86. package/src/types/device.ts +12 -3
  87. package/src/types/settings.ts +30 -21
  88. package/src/utils/deviceFeaturesCompat.ts +5 -0
  89. package/src/utils/deviceFeaturesUtils.ts +6 -3
  90. package/src/utils/deviceInfoUtils.ts +2 -0
  91. package/src/utils/deviceSettings.ts +3 -0
@@ -363,14 +363,9 @@ describe('DeviceCommands failure mapping', () => {
363
363
  }
364
364
  );
365
365
 
366
- it.each([
367
- ['SignTx', 'Rejected by the user'],
368
- ['EthereumSignTypedDataOneKey', ''],
369
- ['SolanaSignTx', 'Localized cancellation message'],
370
- ['GetAddress', 'Address confirmation rejected'],
371
- ] as const)(
372
- 'maps Protocol V2 cancellation subcode 1 for %s regardless of message',
373
- async (callType, message) => {
366
+ it.each(['SignTx', 'EthereumSignTypedDataOneKey', 'SolanaSignTx', 'GetAddress'] as const)(
367
+ 'does not treat domain-ambiguous Protocol V2 subcode 1 for %s as cancellation',
368
+ async callType => {
374
369
  const commands = createCommands();
375
370
 
376
371
  await expect(
@@ -380,18 +375,13 @@ describe('DeviceCommands failure mapping', () => {
380
375
  message: {
381
376
  code: 'Failure_ProcessError',
382
377
  subcode: 1,
383
- message,
378
+ message: 'Domain-specific failure',
384
379
  },
385
380
  } as any,
386
381
  callType
387
382
  )
388
383
  ).rejects.toMatchObject({
389
- errorCode: HardwareErrorCode.ActionCancelled,
390
- params: {
391
- failureCode: 'Failure_ProcessError',
392
- subcode: 1,
393
- firmwareMessage: message,
394
- },
384
+ errorCode: HardwareErrorCode.RuntimeError,
395
385
  });
396
386
  }
397
387
  );
@@ -38,6 +38,28 @@ describe('DevicePool state lifecycle', () => {
38
38
  expect(getDeviceState).toHaveBeenNthCalledWith(2, { refreshSections: ['settings'] });
39
39
  });
40
40
 
41
+ test('actively re-detects a cached device when protocol detection is forced', async () => {
42
+ const descriptor = { path: 'cached-path', protocolType: 'V1' } as any;
43
+ const device = Device.fromDescriptor(descriptor);
44
+ const acquire = jest.spyOn(device, 'acquire').mockImplementation(() => {
45
+ device.originalDescriptor.protocolType = 'V2';
46
+ return Promise.resolve(true);
47
+ });
48
+ const initialize = jest.spyOn(device, 'initialize').mockResolvedValue(undefined);
49
+ const release = jest.spyOn(device, 'release').mockResolvedValue(undefined);
50
+ DevicePool.devicesCache = { 'cached-id': device };
51
+
52
+ const result = await DevicePool.getDevices([descriptor], 'cached-id', {
53
+ forceProtocolDetection: true,
54
+ });
55
+
56
+ expect(acquire).toHaveBeenCalledWith(undefined, { forceProtocolDetection: true });
57
+ expect(initialize).toHaveBeenCalledWith({ forceProtocolDetection: true });
58
+ expect(release).toHaveBeenCalledTimes(1);
59
+ expect(device.originalDescriptor.protocolType).toBe('V2');
60
+ expect(result.deviceList).toEqual([device]);
61
+ });
62
+
41
63
  test('keeps a discovered device when its Protocol V2 label cannot be read', async () => {
42
64
  const labelError = new Error('settings unavailable');
43
65
  const getDeviceState = jest
@@ -145,6 +145,18 @@ describe('DeviceStateMapper', () => {
145
145
  expect(patch.status?.unlocked).toBeUndefined();
146
146
  });
147
147
 
148
+ test('maps the Protocol V2 Neo device type without treating it as Pro2', () => {
149
+ const patch = mapProtocolV2DeviceInfoToState({
150
+ hw: { Device_type: DeviceType.NEO, serial_no: 'NEO-SERIAL-1' },
151
+ });
152
+
153
+ expect(patch.identity).toMatchObject({
154
+ deviceType: EDeviceType.Neo,
155
+ model: 'neo',
156
+ serialNo: 'NEO-SERIAL-1',
157
+ });
158
+ });
159
+
148
160
  test('maps the hardware model independently from Protocol V2', () => {
149
161
  const pro = mapProtocolV2DeviceInfoToState({
150
162
  protocol_version: 2,
@@ -1,10 +1,13 @@
1
1
  import { EDeviceType, EFirmwareType } from '@onekeyfe/hd-shared';
2
+ import { DeviceType } from '@onekeyfe/hd-transport';
2
3
 
3
4
  import {
4
5
  getDeviceSerialNo,
5
6
  getDeviceType,
7
+ getDeviceTypeByBleName,
6
8
  getDeviceUUID,
7
9
  getFirmwareType,
10
+ getMethodVersionRange,
8
11
  } from '../src/utils/deviceInfoUtils';
9
12
  import {
10
13
  getDeviceBLEFirmwareVersion,
@@ -85,7 +88,7 @@ describe('device feature selectors', () => {
85
88
  test('reads normalized Protocol V2 features through the same public selectors', () => {
86
89
  const features = buildProtocolV2FeaturesPayload({
87
90
  deviceInfo: {
88
- hw: { serial_no: 'P2-001' },
91
+ hw: { Device_type: DeviceType.PRO2, serial_no: 'P2-001' },
89
92
  fw: {
90
93
  application: { version: '5.0.0' },
91
94
  bootloader: { version: '2.0.0' },
@@ -104,4 +107,48 @@ describe('device feature selectors', () => {
104
107
  expect(getDeviceBoardloaderVersion(features)).toEqual([1, 0, 0]);
105
108
  expect(getDeviceBLEFirmwareVersion(features)).toEqual([3, 0, 0]);
106
109
  });
110
+
111
+ test('preserves the real Neo type in Protocol V2 compatibility features', () => {
112
+ const features = buildProtocolV2FeaturesPayload({
113
+ deviceInfo: {
114
+ hw: { Device_type: DeviceType.NEO, serial_no: 'NEO-001' },
115
+ },
116
+ });
117
+
118
+ expect(features.deviceType).toBe(EDeviceType.Neo);
119
+ expect(features.onekey_device_type).toBe('NEO');
120
+ expect(getDeviceType(features)).toBe(EDeviceType.Neo);
121
+ });
122
+
123
+ test('recognizes Neo discovery names before initialization', () => {
124
+ expect(getDeviceTypeByBleName('OneKey Neo 1234')).toBe(EDeviceType.Neo);
125
+ expect(getDeviceTypeByBleName('Neo 1234')).toBe(EDeviceType.Neo);
126
+ });
127
+
128
+ test.each([EDeviceType.Pro2, EDeviceType.Neo])(
129
+ 'uses the Pro2 product model as the chain capability fallback for %s',
130
+ deviceType => {
131
+ const checkedTypes: string[] = [];
132
+ const versionRange = getMethodVersionRange({ deviceType } as PROTO.Features, type => {
133
+ checkedTypes.push(type);
134
+ return type === 'model_pro2' ? { min: '1.0.0' } : undefined;
135
+ });
136
+
137
+ expect(versionRange).toEqual({ min: '1.0.0' });
138
+ expect(checkedTypes).toEqual([deviceType, 'model_pro2']);
139
+ }
140
+ );
141
+
142
+ test('prefers a Neo-specific chain range over the shared Pro2 product model', () => {
143
+ const versionRange = getMethodVersionRange(
144
+ { deviceType: EDeviceType.Neo } as PROTO.Features,
145
+ type => {
146
+ if (type === EDeviceType.Neo) return { min: '2.0.0' };
147
+ if (type === 'model_pro2') return { min: '1.0.0' };
148
+ return undefined;
149
+ }
150
+ );
151
+
152
+ expect(versionRange).toEqual({ min: '2.0.0' });
153
+ });
107
154
  });
@@ -72,6 +72,16 @@ describe('Pro 2 device settings options', () => {
72
72
  ]);
73
73
  });
74
74
 
75
+ test('uses the shared Protocol V2 settings model for Neo', () => {
76
+ expect(getLanguageConfig(EDeviceType.Neo)).toEqual(getLanguageConfig(EDeviceType.Pro2));
77
+ expect(getAutoLockOptions(EDeviceType.Neo, 'V2')).toEqual(
78
+ getAutoLockOptions(EDeviceType.Pro2, 'V2')
79
+ );
80
+ expect(getAutoShutDownOptions(EDeviceType.Neo, 'V2')).toEqual(
81
+ getAutoShutDownOptions(EDeviceType.Pro2, 'V2')
82
+ );
83
+ });
84
+
75
85
  test('keeps the legacy and Protocol V2 never values protocol-specific', () => {
76
86
  expect(getAutoLockOptions(EDeviceType.Pro, 'V1').slice(-1)[0]).toEqual({
77
87
  label: 'Never',
@@ -160,30 +160,24 @@ describe('DeviceUploadNft', () => {
160
160
  });
161
161
  });
162
162
 
163
- test('retries one timed-out NftUpdate with the same basename', async () => {
163
+ test('does not retry a timed-out NftUpdate', async () => {
164
164
  const timeout = Object.assign(new Error('Protocol V2 response timeout'), {
165
165
  code: 'response-timeout',
166
166
  });
167
- let updateCalls = 0;
168
167
  const typedCall = jest.fn((request: string, _response: string, params: any) => {
169
168
  if (request === 'FilesystemDirList') {
170
169
  return { message: { path: 'vol1:/nft', child_files: '' } };
171
170
  }
172
171
  if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
173
- if (request === 'NftUpdate') {
174
- updateCalls += 1;
175
- if (updateCalls === 1) throw timeout;
176
- return { message: { message: 'NFT updated' } };
177
- }
172
+ if (request === 'NftUpdate') throw timeout;
178
173
  throw new Error(`Unexpected request: ${request}`);
179
174
  });
180
175
  const method = createMethod({ typedCall });
181
176
 
182
- await expect(method.run()).resolves.toMatchObject({ nftUpdated: true });
177
+ await expect(method.run()).rejects.toBe(timeout);
183
178
 
184
179
  const updateRequests = typedCall.mock.calls.filter(call => call[0] === 'NftUpdate');
185
- expect(updateRequests).toHaveLength(2);
186
- expect(updateRequests[1]).toEqual(updateRequests[0]);
180
+ expect(updateRequests).toHaveLength(1);
187
181
  });
188
182
 
189
183
  test('does not retry a non-timeout NftUpdate failure', async () => {
@@ -201,24 +195,6 @@ describe('DeviceUploadNft', () => {
201
195
  expect(typedCall.mock.calls.filter(call => call[0] === 'NftUpdate')).toHaveLength(1);
202
196
  });
203
197
 
204
- test('propagates a second NftUpdate timeout after one retry', async () => {
205
- const timeout = Object.assign(new Error('Protocol V2 response timeout'), {
206
- code: 'response-timeout',
207
- });
208
- const typedCall = jest.fn((request: string, _response: string, params: any) => {
209
- if (request === 'FilesystemDirList') {
210
- return { message: { path: 'vol1:/nft', child_files: '' } };
211
- }
212
- if (request === 'FilesystemFileWrite') return fileWriteSuccess(params);
213
- if (request === 'NftUpdate') throw timeout;
214
- throw new Error(`Unexpected request: ${request}`);
215
- });
216
- const method = createMethod({ typedCall });
217
-
218
- await expect(method.run()).rejects.toBe(timeout);
219
- expect(typedCall.mock.calls.filter(call => call[0] === 'NftUpdate')).toHaveLength(2);
220
- });
221
-
222
198
  test('does not publish when a file write fails', async () => {
223
199
  const typedCall = jest.fn((request: string) => {
224
200
  if (request === 'FilesystemDirList') {
@@ -6,7 +6,7 @@ jest.mock('../src/data/config', () => ({
6
6
  DEFAULT_DOMAIN: 'https://jssdk.onekey.so/1.0.0/',
7
7
  }));
8
8
 
9
- describe('Kaspa Pro2 useTweak capability', () => {
9
+ describe('Kaspa Pro2 product-family useTweak capability', () => {
10
10
  test.each([
11
11
  ['get address', KaspaGetAddress],
12
12
  ['sign transaction', KaspaSignTransaction],
@@ -14,7 +14,7 @@ describe('Kaspa Pro2 useTweak capability', () => {
14
14
  const method = new Method({ id: 1, payload: {} } as never);
15
15
 
16
16
  expect(method.getUseTweakVersionRange()).toMatchObject({
17
- pro2: { min: '0.0.0' },
17
+ model_pro2: { min: '0.0.0' },
18
18
  });
19
19
  });
20
20
  });
@@ -56,12 +56,17 @@ const resources: IProtocolV2Resource[] = PROTOCOL_V2_RESOURCE_TYPES.map((type, i
56
56
 
57
57
  const bootResources: IProtocolV2BootResources = {
58
58
  required: false,
59
- target: 'CRATE',
60
- url: 'https://example.com/boot-resources.crate.okpkg',
61
- size: 1234,
62
- fileHash: 'ab'.repeat(32),
63
- payloadHash: 'cd'.repeat(64),
64
- headerHash: 'ef'.repeat(64),
59
+ target: 'RES',
60
+ manifestUrl: 'https://example.com/manifest.json',
61
+ files: [
62
+ {
63
+ name: 'bootloader_crest.bin',
64
+ url: 'https://example.com/assets/bootloader_crest.bin',
65
+ devicePath: 'vol0:/assets/loaders/boot.staging/graphics/bootloader_crest.bin',
66
+ size: 1234,
67
+ fileHash: 'ab'.repeat(32),
68
+ },
69
+ ],
65
70
  };
66
71
 
67
72
  const createSettings = (configFetcher: ConnectSettings['configFetcher']): ConnectSettings =>
@@ -119,7 +124,7 @@ describe('Pro2 resource configuration', () => {
119
124
  ).toThrow('headerHash');
120
125
  });
121
126
 
122
- test('requires boot resources to remain optional and use a CRATE package', () => {
127
+ test('requires boot resources to remain optional and use RES file entries', () => {
123
128
  expect(() =>
124
129
  parseProtocolV2Resources({
125
130
  stable: resources,
@@ -129,9 +134,18 @@ describe('Pro2 resource configuration', () => {
129
134
  expect(() =>
130
135
  parseProtocolV2Resources({
131
136
  stable: resources,
132
- boot: { ...bootResources, target: 'RESC' },
137
+ boot: { ...bootResources, target: 'INVALID' },
133
138
  })
134
- ).toThrow('expected CRATE');
139
+ ).toThrow('expected RES');
140
+ expect(() =>
141
+ parseProtocolV2Resources({
142
+ stable: resources,
143
+ boot: {
144
+ ...bootResources,
145
+ files: [{ ...bootResources.files[0], devicePath: '../outside.bin' }],
146
+ },
147
+ })
148
+ ).toThrow('devicePath');
135
149
  });
136
150
 
137
151
  test('downloads nothing when all resource identities match', () => {
@@ -280,17 +294,18 @@ describe('Pro2 resource configuration', () => {
280
294
  expect(DataManager.getProtocolV2BootResources()).toBeUndefined();
281
295
  });
282
296
 
283
- test('still blocks firmware mutation when the refreshed Pro2 resources are invalid', async () => {
297
+ test('only blocks resource mutation when the refreshed Pro2 resources are invalid', async () => {
284
298
  const remoteConfig = createRemoteConfig();
285
299
  (remoteConfig.pro2 as { resources?: unknown }).resources = { stable: [] };
286
300
  const settings = createSettings(jest.fn().mockResolvedValue(remoteConfig));
287
301
  DataManager.settings = settings;
288
302
 
289
- await expect(DataManager.forceReloadData()).rejects.toMatchObject({
290
- errorCode: HardwareErrorCode.NetworkError,
303
+ await expect(DataManager.forceReloadData()).resolves.toBeUndefined();
304
+ await expect(DataManager.forceReloadData({ requireResources: true })).rejects.toMatchObject({
305
+ errorCode: HardwareErrorCode.FirmwareUpdateDownloadFailed,
291
306
  message: expect.stringContaining('Invalid Pro2 resources config'),
292
307
  });
293
- expect(DataManager.lastCheckTimestamp).toBe(0);
308
+ expect(DataManager.lastCheckTimestamp).toBeGreaterThan(0);
294
309
  });
295
310
 
296
311
  test('does not advance the cache timestamp when refresh fails', async () => {
@@ -2,6 +2,7 @@ import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
2
 
3
3
  import ConfluxSignMessageCIP23 from '../src/api/conflux/ConfluxSignMessageCIP23';
4
4
  import DeviceLock from '../src/api/device/DeviceLock';
5
+ import OpenWalletSession from '../src/api/OpenWalletSession';
5
6
  import { runMethodWithUnlockPolicy } from '../src/protocols/protocol-v2/unlockPolicyRunner';
6
7
 
7
8
  jest.mock('../src/data/config', () => ({
@@ -33,6 +34,39 @@ describe('Protocol V2 unlock semantics', () => {
33
34
  expect(method.unlockPolicy).toBe('none');
34
35
  });
35
36
 
37
+ test('pre-unlocks openWalletSession before opening or restoring a wallet session', async () => {
38
+ const method = new OpenWalletSession({
39
+ id: 1,
40
+ payload: { method: 'openWalletSession', mode: 'standard' },
41
+ });
42
+ method.init();
43
+ const run = jest.fn().mockResolvedValue({ walletType: 'standard' });
44
+ method.run = run as any;
45
+ const features = { unlocked: false };
46
+ const device = {
47
+ features,
48
+ commands: {
49
+ typedCall: jest.fn().mockResolvedValue({ message: { unlocked: false } }),
50
+ },
51
+ isProtocolV2: () => true,
52
+ isBootloader: () => false,
53
+ isRomloader: () => false,
54
+ updateProtocolV2Status: jest.fn(() => features),
55
+ unlockDevice: jest.fn().mockImplementation(() => {
56
+ features.unlocked = true;
57
+ return Promise.resolve(features);
58
+ }),
59
+ };
60
+
61
+ await expect(runMethodWithUnlockPolicy(method, device as any)).resolves.toEqual({
62
+ walletType: 'standard',
63
+ });
64
+
65
+ expect(method.unlockPolicy).toBe('unlock-before-run');
66
+ expect(device.unlockDevice).toHaveBeenCalledTimes(1);
67
+ expect(run).toHaveBeenCalledTimes(1);
68
+ });
69
+
36
70
  test('pre-unlocks an unregistered wallet business method', async () => {
37
71
  const features = { unlocked: false };
38
72
  const method = {