@onekeyfe/hd-core 1.2.0-alpha.34 → 1.2.0-alpha.35

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 (65) hide show
  1. package/__tests__/DeviceCommands.test.ts +84 -0
  2. package/__tests__/device-settings.test.ts +3 -0
  3. package/__tests__/device-wallet-session-store.test.ts +80 -7
  4. package/__tests__/evmSignTransaction.test.ts +69 -0
  5. package/__tests__/get-device-state.test.ts +78 -30
  6. package/__tests__/open-wallet-session.test.ts +186 -51
  7. package/__tests__/protocol-v2.test.ts +490 -112
  8. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  9. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  10. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  11. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  12. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  13. package/dist/api/evm/protocol.d.ts +3 -0
  14. package/dist/api/evm/protocol.d.ts.map +1 -0
  15. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
  16. package/dist/device/Device.d.ts +3 -2
  17. package/dist/device/Device.d.ts.map +1 -1
  18. package/dist/device/DeviceCommands.d.ts.map +1 -1
  19. package/dist/device/DeviceWalletSessionStore.d.ts +0 -1
  20. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  21. package/dist/index.d.ts +8 -6
  22. package/dist/index.js +606 -641
  23. package/dist/protocols/protocol-v2/features.d.ts +17 -6
  24. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  25. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  26. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  27. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
  28. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  29. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  30. package/dist/protocols/protocol-v2/walletSession.d.ts +1 -0
  31. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  32. package/dist/types/api/openWalletSession.d.ts +1 -1
  33. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  34. package/dist/types/api/protocolV2.d.ts +2 -2
  35. package/dist/types/api/protocolV2.d.ts.map +1 -1
  36. package/dist/types/device.d.ts +2 -1
  37. package/dist/types/device.d.ts.map +1 -1
  38. package/dist/utils/patch.d.ts +1 -1
  39. package/dist/utils/patch.d.ts.map +1 -1
  40. package/package.json +4 -4
  41. package/src/api/FirmwareUpdateV4.ts +26 -8
  42. package/src/api/GetPassphraseState.ts +11 -0
  43. package/src/api/OpenWalletSession.ts +15 -7
  44. package/src/api/evm/EVMGetAddress.ts +2 -2
  45. package/src/api/evm/EVMGetPublicKey.ts +2 -2
  46. package/src/api/evm/EVMSignMessage.ts +2 -2
  47. package/src/api/evm/EVMSignTransaction.ts +2 -2
  48. package/src/api/evm/EVMSignTypedData.ts +35 -45
  49. package/src/api/evm/EVMVerifyMessage.ts +2 -2
  50. package/src/api/evm/protocol.ts +6 -0
  51. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
  52. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  53. package/src/data/messages/messages-protocol-v2.json +393 -485
  54. package/src/device/Device.ts +63 -37
  55. package/src/device/DeviceCommands.ts +12 -2
  56. package/src/device/DeviceWalletSessionStore.ts +0 -14
  57. package/src/deviceProfile/buildDeviceFeatures.ts +1 -1
  58. package/src/protocols/protocol-v2/features.ts +58 -16
  59. package/src/protocols/protocol-v2/index.ts +5 -0
  60. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
  61. package/src/protocols/protocol-v2/unlockRetry.ts +18 -7
  62. package/src/protocols/protocol-v2/walletSession.ts +35 -35
  63. package/src/types/api/openWalletSession.ts +1 -1
  64. package/src/types/api/protocolV2.ts +2 -2
  65. package/src/types/device.ts +2 -0
@@ -108,7 +108,7 @@ describe('openWalletSession', () => {
108
108
  session_id: 'pro2-wallet-session',
109
109
  },
110
110
  });
111
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
111
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
112
112
  const method = new GetPassphraseState({
113
113
  payload: {
114
114
  method: 'getPassphraseState',
@@ -130,7 +130,8 @@ describe('openWalletSession', () => {
130
130
  { cancelDeviceOnReject: false }
131
131
  );
132
132
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
133
- on_device: true,
133
+ passphrase: 'host hidden wallet',
134
+ on_device: false,
134
135
  });
135
136
  expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
136
137
  });
@@ -208,7 +209,7 @@ describe('openWalletSession', () => {
208
209
  protocol: 'V1',
209
210
  walletType: 'standard',
210
211
  deviceId: 'device-1',
211
- passphraseState: 'main-wallet-state',
212
+ passphraseState: null,
212
213
  resumed: false,
213
214
  });
214
215
  expect(method.payload.useEmptyPassphrase).toBe(true);
@@ -229,7 +230,7 @@ describe('openWalletSession', () => {
229
230
  session_id: 'hidden-session',
230
231
  },
231
232
  });
232
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
233
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
233
234
  const method = new OpenWalletSession({
234
235
  payload: {
235
236
  method: 'openWalletSession',
@@ -247,11 +248,47 @@ describe('openWalletSession', () => {
247
248
  });
248
249
  expect(promptPassphrase).toHaveBeenCalled();
249
250
  expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
250
- on_device: true,
251
+ passphrase: 'host hidden wallet',
252
+ on_device: false,
251
253
  });
252
254
  expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
253
255
  });
254
256
 
257
+ test('refreshes Pro2 status and unlocks before selecting a hidden wallet when locked', async () => {
258
+ const typedCall = jest
259
+ .fn()
260
+ .mockResolvedValueOnce({ message: { version: 2 } })
261
+ .mockResolvedValueOnce({ message: {} })
262
+ .mockResolvedValueOnce({
263
+ message: {
264
+ btc_test_address: 'hidden-state-after-preflight',
265
+ session_id: 'hidden-session-after-preflight',
266
+ },
267
+ });
268
+ const method = new OpenWalletSession({
269
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
270
+ });
271
+ method.init();
272
+ const device = createDevice({
273
+ typedCall,
274
+ promptPassphrase: jest.fn().mockResolvedValue({ passphraseOnDevice: true }),
275
+ });
276
+ device.getDeviceState = jest.fn().mockResolvedValue({
277
+ identity: { deviceId: 'device-1' },
278
+ status: { unlocked: false, passphraseProtection: true },
279
+ });
280
+ method.device = device as any;
281
+
282
+ await expect(method.run()).resolves.toMatchObject({
283
+ walletType: 'hidden',
284
+ passphraseState: 'hidden-state-after-preflight',
285
+ });
286
+ expect(device.unlockDevice).toHaveBeenCalledTimes(1);
287
+ expect(device.unlockDevice.mock.invocationCallOrder[0]).toBeLessThan(
288
+ typedCall.mock.invocationCallOrder[0]
289
+ );
290
+ });
291
+
255
292
  test('classifies the selected Pro2 wallet from the post-unlock device state', async () => {
256
293
  const typedCall = jest
257
294
  .fn()
@@ -263,7 +300,7 @@ describe('openWalletSession', () => {
263
300
  session_id: 'hidden-session-after-unlock',
264
301
  },
265
302
  });
266
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
303
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
267
304
  const method = new OpenWalletSession({
268
305
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
269
306
  });
@@ -275,10 +312,23 @@ describe('openWalletSession', () => {
275
312
  });
276
313
  device.features.unlocked = false;
277
314
  device.getCurrentPassphraseProtection = () => device.features.passphraseProtection;
278
- device.unlockDevice = jest.fn().mockImplementation(() => {
279
- device.features.unlocked = true;
280
- device.features.passphraseProtection = true;
281
- return Promise.resolve(device.features);
315
+ device.commands.typedCall = jest.fn((request: string, ...args: unknown[]) => {
316
+ if (request === 'DeviceStatusGet') {
317
+ return {
318
+ message: {
319
+ device_id: 'device-1',
320
+ unlocked: true,
321
+ passphrase_enabled: true,
322
+ },
323
+ };
324
+ }
325
+ return typedCall(request, ...args);
326
+ });
327
+ device.updateProtocolV2Status = jest.fn((status: Record<string, unknown>) => {
328
+ device.features.unlocked = status.unlocked ?? device.features.unlocked;
329
+ device.features.passphraseProtection =
330
+ status.passphrase_enabled ?? device.features.passphraseProtection;
331
+ return device.features;
282
332
  });
283
333
  method.device = device as any;
284
334
 
@@ -293,6 +343,35 @@ describe('openWalletSession', () => {
293
343
  expect(promptPassphrase).toHaveBeenCalledTimes(1);
294
344
  });
295
345
 
346
+ test('rejects a Pro2 standard-wallet fallback after selecting a hidden wallet', async () => {
347
+ const typedCall = jest
348
+ .fn()
349
+ .mockResolvedValueOnce({ message: { version: 2 } })
350
+ .mockResolvedValueOnce({ message: {} })
351
+ .mockResolvedValueOnce({
352
+ message: {
353
+ btc_test_address: 'standard-wallet-state',
354
+ session_id: 'standard-wallet-session',
355
+ },
356
+ });
357
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
358
+ const method = new OpenWalletSession({
359
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
360
+ });
361
+ method.init();
362
+ const device = createDevice({
363
+ passphraseProtection: false,
364
+ typedCall,
365
+ promptPassphrase,
366
+ });
367
+ method.device = device as any;
368
+
369
+ await expect(method.run()).rejects.toMatchObject({
370
+ errorCode: HardwareErrorCode.DeviceNotOpenedPassphrase,
371
+ });
372
+ expect(device.clearInternalState).toHaveBeenCalled();
373
+ });
374
+
296
375
  test('select-hidden starts a fresh hidden-wallet session on Protocol V2', async () => {
297
376
  const typedCall = jest
298
377
  .fn()
@@ -304,7 +383,7 @@ describe('openWalletSession', () => {
304
383
  session_id: 'new-hidden-session',
305
384
  },
306
385
  });
307
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
386
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
308
387
  const method = new OpenWalletSession({
309
388
  payload: {
310
389
  method: 'openWalletSession',
@@ -326,7 +405,8 @@ describe('openWalletSession', () => {
326
405
  eventless_wallet_session: true,
327
406
  });
328
407
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
329
- on_device: true,
408
+ passphrase: 'host hidden wallet',
409
+ on_device: false,
330
410
  });
331
411
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
332
412
  expect(promptPassphrase).toHaveBeenCalled();
@@ -344,7 +424,7 @@ describe('openWalletSession', () => {
344
424
  session_id: 'current-device-session',
345
425
  },
346
426
  });
347
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
427
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
348
428
  const method = new OpenWalletSession({
349
429
  payload: {
350
430
  method: 'openWalletSession',
@@ -569,7 +649,7 @@ describe('openWalletSession', () => {
569
649
  protocol: 'V2',
570
650
  walletType: 'standard',
571
651
  deviceId: 'device-1',
572
- passphraseState: 'main-wallet-state',
652
+ passphraseState: null,
573
653
  resumed: false,
574
654
  });
575
655
  expect(typedCall).toHaveBeenCalledTimes(2);
@@ -602,7 +682,7 @@ describe('openWalletSession', () => {
602
682
  .fn()
603
683
  .mockResolvedValueOnce({
604
684
  identity: { deviceId: undefined },
605
- status: { passphraseProtection: true },
685
+ status: { unlocked: false, passphraseProtection: true },
606
686
  })
607
687
  .mockResolvedValueOnce({
608
688
  identity: { deviceId: 'device-1' },
@@ -618,7 +698,7 @@ describe('openWalletSession', () => {
618
698
  protocol: 'V2',
619
699
  walletType: 'standard',
620
700
  deviceId: 'device-1',
621
- passphraseState: 'main-wallet-state',
701
+ passphraseState: null,
622
702
  resumed: false,
623
703
  });
624
704
  expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
@@ -649,7 +729,7 @@ describe('openWalletSession', () => {
649
729
 
650
730
  await expect(method.run()).resolves.toMatchObject({
651
731
  walletType: 'standard',
652
- passphraseState: 'main-wallet-state',
732
+ passphraseState: null,
653
733
  });
654
734
  expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
655
735
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
@@ -666,7 +746,7 @@ describe('openWalletSession', () => {
666
746
  session_id: 'hidden-session',
667
747
  },
668
748
  });
669
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
749
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
670
750
  const method = new OpenWalletSession({
671
751
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
672
752
  });
@@ -683,7 +763,8 @@ describe('openWalletSession', () => {
683
763
  resumed: false,
684
764
  });
685
765
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
686
- on_device: true,
766
+ passphrase: 'host hidden wallet',
767
+ on_device: false,
687
768
  });
688
769
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
689
770
  expect(promptPassphrase).toHaveBeenCalled();
@@ -691,7 +772,7 @@ describe('openWalletSession', () => {
691
772
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('hidden-session');
692
773
  });
693
774
 
694
- test('gets the prepared session after on-device passphrase entry', async () => {
775
+ test('gets the prepared session after host passphrase entry', async () => {
695
776
  const typedCall = jest
696
777
  .fn()
697
778
  .mockResolvedValueOnce({ message: { version: 2 } })
@@ -699,7 +780,7 @@ describe('openWalletSession', () => {
699
780
  .mockResolvedValueOnce({
700
781
  message: { btc_test_address: 'device-state', session_id: 'device-session' },
701
782
  });
702
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
783
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
703
784
  const method = new OpenWalletSession({
704
785
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
705
786
  });
@@ -711,11 +792,37 @@ describe('openWalletSession', () => {
711
792
  passphraseState: 'device-state',
712
793
  });
713
794
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {
714
- on_device: true,
795
+ passphrase: 'host hidden wallet',
796
+ on_device: false,
715
797
  });
716
798
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
717
799
  });
718
800
 
801
+ test('selects an on-device passphrase wallet with an explicit on_device request', async () => {
802
+ const typedCall = jest
803
+ .fn()
804
+ .mockResolvedValueOnce({ message: { version: 2 } })
805
+ .mockResolvedValueOnce({ message: {} })
806
+ .mockResolvedValueOnce({
807
+ message: { btc_test_address: 'device-state', session_id: 'device-session' },
808
+ });
809
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
810
+ const method = new OpenWalletSession({
811
+ payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
812
+ });
813
+ method.init();
814
+ method.device = createDevice({ typedCall, promptPassphrase }) as any;
815
+
816
+ await expect(method.run()).resolves.toMatchObject({
817
+ walletType: 'hidden',
818
+ passphraseState: 'device-state',
819
+ });
820
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
821
+ on_device: true,
822
+ });
823
+ expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
824
+ });
825
+
719
826
  test('forwards a host passphrase to Pro2 firmware', async () => {
720
827
  const typedCall = jest
721
828
  .fn()
@@ -910,7 +1017,7 @@ describe('openWalletSession', () => {
910
1017
  .fn()
911
1018
  .mockResolvedValueOnce({ message: { version: 2 } })
912
1019
  .mockRejectedValueOnce(new Error(message));
913
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
1020
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
914
1021
  const method = new OpenWalletSession({
915
1022
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
916
1023
  });
@@ -963,7 +1070,7 @@ describe('openWalletSession', () => {
963
1070
  expect(typedCall).toHaveBeenCalledTimes(1);
964
1071
  });
965
1072
 
966
- test('unlocks and retries hidden-wallet preparation when the device is locked', async () => {
1073
+ test('does not unlock or retry when wallet preparation reports a late DeviceLocked', async () => {
967
1074
  const lockedError = { errorCode: HardwareErrorCode.DeviceLocked };
968
1075
  const typedCall = jest
969
1076
  .fn()
@@ -973,7 +1080,7 @@ describe('openWalletSession', () => {
973
1080
  .mockResolvedValueOnce({
974
1081
  message: { btc_test_address: 'hidden-state', session_id: 'hidden-session' },
975
1082
  });
976
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
1083
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
977
1084
  const method = new OpenWalletSession({
978
1085
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
979
1086
  });
@@ -981,12 +1088,9 @@ describe('openWalletSession', () => {
981
1088
  const device = createDevice({ typedCall, promptPassphrase });
982
1089
  method.device = device as any;
983
1090
 
984
- await expect(method.run()).resolves.toMatchObject({
985
- walletType: 'hidden',
986
- passphraseState: 'hidden-state',
987
- });
988
- expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.Main);
989
- expect(typedCall).toHaveBeenCalledTimes(4);
1091
+ await expect(method.run()).rejects.toBe(lockedError);
1092
+ expect(device.unlockDevice).not.toHaveBeenCalled();
1093
+ expect(typedCall).toHaveBeenCalledTimes(2);
990
1094
  });
991
1095
 
992
1096
  test('does not fall back when wallet preparation is rejected', async () => {
@@ -995,7 +1099,7 @@ describe('openWalletSession', () => {
995
1099
  .fn()
996
1100
  .mockResolvedValueOnce({ message: { version: 2 } })
997
1101
  .mockRejectedValueOnce(invalidSessionError);
998
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
1102
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
999
1103
  const method = new OpenWalletSession({
1000
1104
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
1001
1105
  });
@@ -1026,7 +1130,7 @@ describe('openWalletSession', () => {
1026
1130
  .mockResolvedValueOnce({ message: { version: 2 } })
1027
1131
  .mockResolvedValueOnce({ message: {} })
1028
1132
  .mockResolvedValueOnce({ message });
1029
- const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
1133
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
1030
1134
  const method = new OpenWalletSession({
1031
1135
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
1032
1136
  });
@@ -1107,7 +1211,7 @@ describe('openWalletSession', () => {
1107
1211
  .fn()
1108
1212
  .mockResolvedValueOnce({
1109
1213
  identity: { deviceId: undefined },
1110
- status: { passphraseProtection: true },
1214
+ status: { unlocked: false, passphraseProtection: true },
1111
1215
  })
1112
1216
  .mockResolvedValueOnce({
1113
1217
  identity: { deviceId: 'device-1' },
@@ -1183,13 +1287,23 @@ describe('openWalletSession', () => {
1183
1287
  expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBeUndefined();
1184
1288
  });
1185
1289
 
1186
- test('does not fall back to wallet selection when a cached resume is invalid', async () => {
1187
- const typedCall = jest.fn().mockRejectedValue(
1188
- Object.assign(new Error('Invalid session'), {
1189
- errorCode: HardwareErrorCode.WalletSessionInvalid,
1190
- })
1191
- );
1192
- const promptPassphrase = jest.fn();
1290
+ test('reselects the expected hidden wallet when a cached Protocol V2 session is invalid', async () => {
1291
+ const typedCall = jest
1292
+ .fn()
1293
+ .mockResolvedValueOnce({ message: { version: 2 } })
1294
+ .mockRejectedValueOnce(
1295
+ Object.assign(new Error('Invalid session'), {
1296
+ errorCode: HardwareErrorCode.WalletSessionInvalid,
1297
+ })
1298
+ )
1299
+ .mockResolvedValueOnce({ message: {} })
1300
+ .mockResolvedValueOnce({
1301
+ message: {
1302
+ btc_test_address: 'hidden-state',
1303
+ session_id: 'renewed-session',
1304
+ },
1305
+ });
1306
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
1193
1307
  const method = new OpenWalletSession({
1194
1308
  payload: {
1195
1309
  method: 'openWalletSession',
@@ -1203,15 +1317,32 @@ describe('openWalletSession', () => {
1203
1317
  deviceWalletSessionStore.set('device-1', 'hidden-state', 'expired-session');
1204
1318
  method.device = createDevice({ typedCall, promptPassphrase }) as any;
1205
1319
 
1206
- await expect(method.run()).rejects.toMatchObject({
1207
- errorCode: HardwareErrorCode.WalletSessionInvalid,
1320
+ await expect(method.run()).resolves.toEqual({
1321
+ protocol: 'V2',
1322
+ walletType: 'hidden',
1323
+ deviceId: 'device-1',
1324
+ passphraseState: 'hidden-state',
1325
+ resumed: false,
1208
1326
  });
1209
- expect(promptPassphrase).not.toHaveBeenCalled();
1327
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {
1328
+ session_id: 'expired-session',
1329
+ });
1330
+ expect(promptPassphrase).toHaveBeenCalledTimes(1);
1331
+ expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('renewed-session');
1210
1332
  });
1211
1333
 
1212
- test('rejects resume-hidden when the SDK store has no matching session', async () => {
1213
- const typedCall = jest.fn();
1214
- const promptPassphrase = jest.fn();
1334
+ test('selects the expected hidden wallet when Protocol V2 has no cached session', async () => {
1335
+ const typedCall = jest
1336
+ .fn()
1337
+ .mockResolvedValueOnce({ message: { version: 2 } })
1338
+ .mockResolvedValueOnce({ message: {} })
1339
+ .mockResolvedValueOnce({
1340
+ message: {
1341
+ btc_test_address: 'hidden-state',
1342
+ session_id: 'new-session',
1343
+ },
1344
+ });
1345
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
1215
1346
  const method = new OpenWalletSession({
1216
1347
  payload: {
1217
1348
  method: 'openWalletSession',
@@ -1224,10 +1355,14 @@ describe('openWalletSession', () => {
1224
1355
  method.init();
1225
1356
  method.device = createDevice({ typedCall, promptPassphrase }) as any;
1226
1357
 
1227
- await expect(method.run()).rejects.toMatchObject({
1228
- errorCode: HardwareErrorCode.WalletSessionInvalid,
1358
+ await expect(method.run()).resolves.toEqual({
1359
+ protocol: 'V2',
1360
+ walletType: 'hidden',
1361
+ deviceId: 'device-1',
1362
+ passphraseState: 'hidden-state',
1363
+ resumed: false,
1229
1364
  });
1230
- expect(typedCall).not.toHaveBeenCalled();
1231
- expect(promptPassphrase).not.toHaveBeenCalled();
1365
+ expect(promptPassphrase).toHaveBeenCalledTimes(1);
1366
+ expect(deviceWalletSessionStore.get('device-1', 'hidden-state')).toBe('new-session');
1232
1367
  });
1233
1368
  });