@onekeyfe/hd-core 1.2.0-alpha.23 → 1.2.0-alpha.24

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 (131) hide show
  1. package/__tests__/DeviceCommands.test.ts +35 -0
  2. package/__tests__/connectSettings.test.ts +5 -47
  3. package/__tests__/device-lifecycle-events.test.ts +59 -0
  4. package/__tests__/firmware-update/firmware-update-v2-download-before-boot.test.ts +30 -39
  5. package/__tests__/open-wallet-session.test.ts +192 -129
  6. package/__tests__/protocol-v2-ping.test.ts +28 -0
  7. package/__tests__/protocol-v2.test.ts +338 -376
  8. package/__tests__/public-pro2-api-boundary.test.ts +22 -0
  9. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  10. package/dist/api/FirmwareUpdateV2.d.ts +0 -11
  11. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  12. package/dist/api/FirmwareUpdateV3.d.ts +3 -7
  13. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  14. package/dist/api/FirmwareUpdateV4.d.ts +5 -21
  15. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  16. package/dist/api/OpenWalletSession.d.ts +1 -8
  17. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  18. package/dist/api/device/DeviceUpdateBootloader.d.ts +1 -5
  19. package/dist/api/device/DeviceUpdateBootloader.d.ts.map +1 -1
  20. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +0 -7
  21. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  22. package/dist/api/firmware/getBinary.d.ts +0 -7
  23. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  24. package/dist/api/firmware/updateBootloader.d.ts +0 -2
  25. package/dist/api/firmware/updateBootloader.d.ts.map +1 -1
  26. package/dist/api/firmware/uploadFirmware.d.ts +1 -9
  27. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  28. package/dist/api/index.d.ts +1 -0
  29. package/dist/api/index.d.ts.map +1 -1
  30. package/dist/api/protocol-v2/Ping.d.ts +2 -0
  31. package/dist/api/protocol-v2/Ping.d.ts.map +1 -1
  32. package/dist/api/stellar/StellarSignTransaction.d.ts +1 -1
  33. package/dist/core/index.d.ts.map +1 -1
  34. package/dist/data-manager/DataManager.d.ts +0 -1
  35. package/dist/data-manager/DataManager.d.ts.map +1 -1
  36. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  37. package/dist/device/Device.d.ts.map +1 -1
  38. package/dist/device/DeviceCommands.d.ts.map +1 -1
  39. package/dist/index.d.ts +89 -273
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +783 -2443
  42. package/dist/inject.d.ts.map +1 -1
  43. package/dist/lowLevelInject.d.ts.map +1 -1
  44. package/dist/protocols/protocol-v2/unlockRetry.d.ts +2 -3
  45. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  46. package/dist/protocols/protocol-v2/walletSession.d.ts +12 -0
  47. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  48. package/dist/topLevelInject.d.ts.map +1 -1
  49. package/dist/types/api/checkAllFirmwareRelease.d.ts +0 -3
  50. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  51. package/dist/types/api/deviceUpdateBootloader.d.ts +0 -6
  52. package/dist/types/api/deviceUpdateBootloader.d.ts.map +1 -1
  53. package/dist/types/api/export.d.ts +1 -4
  54. package/dist/types/api/export.d.ts.map +1 -1
  55. package/dist/types/api/firmwareUpdate.d.ts +0 -69
  56. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  57. package/dist/types/api/index.d.ts +4 -10
  58. package/dist/types/api/index.d.ts.map +1 -1
  59. package/dist/types/api/openWalletSession.d.ts +0 -12
  60. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  61. package/dist/types/api/protocolV2.d.ts +4 -0
  62. package/dist/types/api/protocolV2.d.ts.map +1 -1
  63. package/dist/types/settings.d.ts +0 -13
  64. package/dist/types/settings.d.ts.map +1 -1
  65. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  66. package/dist/utils/patch.d.ts +1 -1
  67. package/dist/utils/patch.d.ts.map +1 -1
  68. package/package.json +4 -4
  69. package/src/api/CheckAllFirmwareRelease.ts +3 -23
  70. package/src/api/FirmwareUpdateV2.ts +120 -292
  71. package/src/api/FirmwareUpdateV3.ts +55 -256
  72. package/src/api/FirmwareUpdateV4.ts +297 -757
  73. package/src/api/OpenWalletSession.ts +23 -86
  74. package/src/api/device/DeviceUpdateBootloader.ts +6 -122
  75. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +0 -50
  76. package/src/api/firmware/updateBootloader.ts +4 -19
  77. package/src/api/firmware/uploadFirmware.ts +22 -140
  78. package/src/api/index.ts +1 -0
  79. package/src/api/protocol-v2/Ping.ts +32 -1
  80. package/src/core/index.ts +39 -7
  81. package/src/data/messages/messages-protocol-v2.json +33 -1
  82. package/src/data-manager/DataManager.ts +33 -33
  83. package/src/data-manager/connectSettings.ts +0 -11
  84. package/src/device/Device.ts +7 -9
  85. package/src/device/DeviceCommands.ts +19 -0
  86. package/src/index.ts +0 -5
  87. package/src/inject.ts +4 -22
  88. package/src/lowLevelInject.ts +1 -5
  89. package/src/protocols/protocol-v2/unlockRetry.ts +20 -3
  90. package/src/protocols/protocol-v2/walletSession.ts +26 -8
  91. package/src/topLevelInject.ts +1 -5
  92. package/src/types/api/checkAllFirmwareRelease.ts +0 -3
  93. package/src/types/api/deviceUpdateBootloader.ts +0 -6
  94. package/src/types/api/export.ts +0 -18
  95. package/src/types/api/firmwareUpdate.ts +0 -76
  96. package/src/types/api/index.ts +3 -14
  97. package/src/types/api/openWalletSession.ts +0 -19
  98. package/src/types/api/protocolV2.ts +10 -0
  99. package/src/types/settings.ts +0 -13
  100. package/src/utils/deviceInfoUtils.ts +15 -3
  101. package/__tests__/firmware-update/check-all-firmware-release.test.ts +0 -86
  102. package/__tests__/firmware-update/firmware-artifact-source.test.ts +0 -119
  103. package/__tests__/firmware-update/firmware-host-binding.test.ts +0 -70
  104. package/__tests__/firmware-update/firmware-update-capabilities.test.ts +0 -13
  105. package/__tests__/firmware-update/firmware-update-plan.test.ts +0 -326
  106. package/__tests__/firmware-update/firmware-update-prepared-plan.test.ts +0 -213
  107. package/__tests__/firmware-update/firmware-upload-source.test.ts +0 -70
  108. package/dist/api/firmware/FirmwareArtifactSource.d.ts +0 -25
  109. package/dist/api/firmware/FirmwareArtifactSource.d.ts.map +0 -1
  110. package/dist/api/firmware/FirmwareHostBinding.d.ts +0 -6
  111. package/dist/api/firmware/FirmwareHostBinding.d.ts.map +0 -1
  112. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts +0 -3
  113. package/dist/api/firmware/FirmwareUpdateCapabilities.d.ts.map +0 -1
  114. package/dist/api/firmware/FirmwareUpdatePlan.d.ts +0 -23
  115. package/dist/api/firmware/FirmwareUpdatePlan.d.ts.map +0 -1
  116. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts +0 -30
  117. package/dist/api/firmware/FirmwareUpdatePreparedPlan.d.ts.map +0 -1
  118. package/dist/types/api/firmwareUpdateCapabilities.d.ts +0 -9
  119. package/dist/types/api/firmwareUpdateCapabilities.d.ts.map +0 -1
  120. package/dist/types/api/firmwareUpdatePlan.d.ts +0 -27
  121. package/dist/types/api/firmwareUpdatePlan.d.ts.map +0 -1
  122. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts +0 -42
  123. package/dist/types/api/firmwareUpdatePreparedPlan.d.ts.map +0 -1
  124. package/src/api/firmware/FirmwareArtifactSource.ts +0 -278
  125. package/src/api/firmware/FirmwareHostBinding.ts +0 -100
  126. package/src/api/firmware/FirmwareUpdateCapabilities.ts +0 -9
  127. package/src/api/firmware/FirmwareUpdatePlan.ts +0 -621
  128. package/src/api/firmware/FirmwareUpdatePreparedPlan.ts +0 -392
  129. package/src/types/api/firmwareUpdateCapabilities.ts +0 -9
  130. package/src/types/api/firmwareUpdatePlan.ts +0 -36
  131. package/src/types/api/firmwareUpdatePreparedPlan.ts +0 -53
@@ -60,10 +60,25 @@ describe('openWalletSession', () => {
60
60
  deviceWalletSessionStore.clear();
61
61
  });
62
62
 
63
+ test.each([{ useEmptyPassphrase: true }, { initSession: true }, {}])(
64
+ 'requires the explicit mode in the new public API: %p',
65
+ legacyParams => {
66
+ const method = new OpenWalletSession({
67
+ payload: {
68
+ method: 'openWalletSession',
69
+ connectId: 'connect-id',
70
+ ...legacyParams,
71
+ },
72
+ });
73
+
74
+ expect(() => method.init()).toThrow('Parameter [mode] is required');
75
+ }
76
+ );
77
+
63
78
  test('keeps the legacy getPassphraseState App flow working on Protocol V2', async () => {
64
79
  const typedCall = jest
65
80
  .fn()
66
- .mockResolvedValueOnce({ message: { eventless_wallet_session: true } })
81
+ .mockResolvedValueOnce({ message: { version: 2 } })
67
82
  .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
68
83
  .mockResolvedValueOnce({
69
84
  message: {
@@ -71,7 +86,7 @@ describe('openWalletSession', () => {
71
86
  session_id: 'pro2-wallet-session',
72
87
  },
73
88
  });
74
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'legacy app secret' });
89
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
75
90
  const method = new GetPassphraseState({
76
91
  payload: {
77
92
  method: 'getPassphraseState',
@@ -84,18 +99,15 @@ describe('openWalletSession', () => {
84
99
 
85
100
  await expect(method.run()).resolves.toBe('pro2-wallet-state');
86
101
  expect(promptPassphrase).toHaveBeenCalledWith(
87
- expect.objectContaining({ deviceOnly: true }),
88
- expect.anything()
89
- );
90
- expect(typedCall).toHaveBeenNthCalledWith(
91
- 2,
92
- 'DeviceSessionAskPassphrase',
93
- 'Success',
94
- {},
95
- { timeoutMs: 120_000 }
102
+ {
103
+ existsAttachPinUser: false,
104
+ source: 'wallet-session-coordinator',
105
+ reason: 'open-wallet',
106
+ },
107
+ { cancelDeviceOnReject: false }
96
108
  );
109
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {});
97
110
  expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
98
- expect(JSON.stringify(typedCall.mock.calls)).not.toContain('legacy app secret');
99
111
  });
100
112
 
101
113
  test('keeps Legacy Protocol V1 getPassphraseState parameterless', async () => {
@@ -150,9 +162,7 @@ describe('openWalletSession', () => {
150
162
  payload: {
151
163
  method: 'openWalletSession',
152
164
  connectId: 'connect-id',
153
- useEmptyPassphrase: true,
154
- initSession: true,
155
- passphraseState: 'stale-hidden-state',
165
+ mode: 'standard',
156
166
  },
157
167
  });
158
168
  method.init();
@@ -181,19 +191,27 @@ describe('openWalletSession', () => {
181
191
  expect(typedCall).toHaveBeenCalledWith('GetPassphraseState', 'PassphraseState', {
182
192
  passphrase_state: undefined,
183
193
  });
184
- expect(device.clearInternalState).toHaveBeenCalled();
194
+ expect(device.clearInternalState).not.toHaveBeenCalled();
185
195
  });
186
196
 
187
- test('uses the compatibility form to select a hidden wallet on Protocol V2', async () => {
188
- const typedCall = jest.fn().mockResolvedValue({
189
- message: {
190
- btc_test_address: 'hidden-state',
191
- session_id: 'hidden-session',
192
- },
193
- });
194
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
197
+ test('uses the explicit mode to select a hidden wallet on Protocol V2', async () => {
198
+ const typedCall = jest
199
+ .fn()
200
+ .mockResolvedValueOnce({ message: { version: 2 } })
201
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
202
+ .mockResolvedValueOnce({
203
+ message: {
204
+ btc_test_address: 'hidden-state',
205
+ session_id: 'hidden-session',
206
+ },
207
+ });
208
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
195
209
  const method = new OpenWalletSession({
196
- payload: { method: 'openWalletSession', connectId: 'connect-id' },
210
+ payload: {
211
+ method: 'openWalletSession',
212
+ connectId: 'connect-id',
213
+ mode: 'select-hidden',
214
+ },
197
215
  });
198
216
  method.init();
199
217
  method.device = createDevice({ typedCall, promptPassphrase }) as any;
@@ -203,19 +221,9 @@ describe('openWalletSession', () => {
203
221
  walletType: 'hidden',
204
222
  passphraseState: 'hidden-state',
205
223
  });
206
- expect(promptPassphrase).toHaveBeenCalledWith(
207
- expect.objectContaining({ deviceOnly: true }),
208
- expect.anything()
209
- );
210
- expect(typedCall).toHaveBeenNthCalledWith(
211
- 2,
212
- 'DeviceSessionAskPassphrase',
213
- 'Success',
214
- {},
215
- { timeoutMs: 120_000 }
216
- );
224
+ expect(promptPassphrase).toHaveBeenCalled();
225
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {});
217
226
  expect(typedCall).toHaveBeenNthCalledWith(3, 'DeviceSessionGet', 'DeviceSession', {});
218
- expect(JSON.stringify(typedCall.mock.calls)).not.toContain('hidden secret');
219
227
  });
220
228
 
221
229
  test('classifies the selected Pro2 wallet from the post-unlock device state', async () => {
@@ -229,12 +237,16 @@ describe('openWalletSession', () => {
229
237
  session_id: 'hidden-session-after-unlock',
230
238
  },
231
239
  });
232
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
240
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
233
241
  const method = new OpenWalletSession({
234
242
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
235
243
  });
236
244
  method.init();
237
- const device = createDevice({ passphraseProtection: false, typedCall, promptPassphrase });
245
+ const device = createDevice({
246
+ passphraseProtection: false,
247
+ typedCall,
248
+ promptPassphrase,
249
+ });
238
250
  device.features.unlocked = false;
239
251
  device.getCurrentPassphraseProtection = () => device.features.passphraseProtection;
240
252
  device.unlockDevice = jest.fn().mockImplementation(() => {
@@ -256,25 +268,26 @@ describe('openWalletSession', () => {
256
268
  expect(promptPassphrase).toHaveBeenCalledTimes(1);
257
269
  });
258
270
 
259
- test('uses legacy initSession to replace a hidden-wallet session on Protocol V2', async () => {
260
- const typedCall = jest.fn().mockResolvedValue({
261
- message: {
262
- btc_test_address: 'new-hidden-state',
263
- session_id: 'new-hidden-session',
264
- },
265
- });
266
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'new hidden secret' });
271
+ test('select-hidden starts a fresh hidden-wallet session on Protocol V2', async () => {
272
+ const typedCall = jest
273
+ .fn()
274
+ .mockResolvedValueOnce({ message: { version: 2 } })
275
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
276
+ .mockResolvedValueOnce({
277
+ message: {
278
+ btc_test_address: 'new-hidden-state',
279
+ session_id: 'new-hidden-session',
280
+ },
281
+ });
282
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
267
283
  const method = new OpenWalletSession({
268
284
  payload: {
269
285
  method: 'openWalletSession',
270
286
  connectId: 'connect-id',
271
- initSession: true,
272
- deviceId: 'device-1',
273
- passphraseState: 'stale-hidden-state',
287
+ mode: 'select-hidden',
274
288
  },
275
289
  });
276
290
  method.init();
277
- deviceWalletSessionStore.set('device-1', 'stale-hidden-state', 'stale-hidden-session');
278
291
  method.device = createDevice({ typedCall, promptPassphrase }) as any;
279
292
 
280
293
  await expect(method.run()).resolves.toEqual({
@@ -288,35 +301,29 @@ describe('openWalletSession', () => {
288
301
  expect(typedCall).toHaveBeenCalledWith('ProtocolInfoRequest', 'ProtocolInfo', {
289
302
  eventless_wallet_session: true,
290
303
  });
291
- expect(typedCall).toHaveBeenCalledWith(
292
- 'DeviceSessionAskPassphrase',
293
- 'Success',
294
- {},
295
- {
296
- timeoutMs: 120_000,
297
- }
298
- );
304
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
299
305
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
300
306
  expect(promptPassphrase).toHaveBeenCalled();
301
- expect(deviceWalletSessionStore.get('device-1', 'stale-hidden-state')).toBeUndefined();
302
307
  expect(deviceWalletSessionStore.get('device-1', 'new-hidden-state')).toBe('new-hidden-session');
303
308
  });
304
309
 
305
- test('does not clear another device wallet through legacy initSession', async () => {
306
- const typedCall = jest.fn().mockResolvedValue({
307
- message: {
308
- btc_test_address: 'current-device-state',
309
- session_id: 'current-device-session',
310
- },
311
- });
312
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
310
+ test('select-hidden does not clear another device wallet', async () => {
311
+ const typedCall = jest
312
+ .fn()
313
+ .mockResolvedValueOnce({ message: { version: 2 } })
314
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
315
+ .mockResolvedValueOnce({
316
+ message: {
317
+ btc_test_address: 'current-device-state',
318
+ session_id: 'current-device-session',
319
+ },
320
+ });
321
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
313
322
  const method = new OpenWalletSession({
314
323
  payload: {
315
324
  method: 'openWalletSession',
316
325
  connectId: 'connect-id',
317
- initSession: true,
318
- deviceId: 'other-device',
319
- passphraseState: 'other-device-state',
326
+ mode: 'select-hidden',
320
327
  },
321
328
  });
322
329
  method.init();
@@ -333,19 +340,22 @@ describe('openWalletSession', () => {
333
340
  );
334
341
  });
335
342
 
336
- test('uses a complete legacy wallet binding to resume on Protocol V2', async () => {
337
- const typedCall = jest.fn().mockResolvedValue({
338
- message: {
339
- btc_test_address: 'hidden-state',
340
- session_id: 'renewed-session',
341
- },
342
- });
343
+ test('uses an explicit wallet binding to resume on Protocol V2', async () => {
344
+ const typedCall = jest
345
+ .fn()
346
+ .mockResolvedValueOnce({ message: { version: 2 } })
347
+ .mockResolvedValueOnce({
348
+ message: {
349
+ btc_test_address: 'hidden-state',
350
+ session_id: 'renewed-session',
351
+ },
352
+ });
343
353
  const promptPassphrase = jest.fn();
344
354
  const method = new OpenWalletSession({
345
355
  payload: {
346
356
  method: 'openWalletSession',
347
357
  connectId: 'connect-id',
348
- initSession: false,
358
+ mode: 'resume-hidden',
349
359
  deviceId: 'device-1',
350
360
  passphraseState: 'hidden-state',
351
361
  },
@@ -405,7 +415,7 @@ describe('openWalletSession', () => {
405
415
  });
406
416
 
407
417
  test.each([{ initSession: 'true' }, { useEmptyPassphrase: 'false' }])(
408
- 'rejects a non-boolean legacy flag: %o',
418
+ 'rejects legacy flags without an explicit mode: %o',
409
419
  params => {
410
420
  const method = new OpenWalletSession({
411
421
  payload: {
@@ -533,12 +543,15 @@ describe('openWalletSession', () => {
533
543
  });
534
544
 
535
545
  test('rejects a standard-wallet request when Protocol V2 is unlocked by Attach PIN', async () => {
536
- const typedCall = jest.fn().mockResolvedValue({
537
- message: {
538
- btc_test_address: 'attach-wallet-state',
539
- session_id: 'attach-wallet-session',
540
- },
541
- });
546
+ const typedCall = jest
547
+ .fn()
548
+ .mockResolvedValueOnce({ message: { version: 2 } })
549
+ .mockResolvedValueOnce({
550
+ message: {
551
+ btc_test_address: 'attach-wallet-state',
552
+ session_id: 'attach-wallet-session',
553
+ },
554
+ });
542
555
  const method = new OpenWalletSession({
543
556
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'standard' },
544
557
  });
@@ -555,13 +568,17 @@ describe('openWalletSession', () => {
555
568
  });
556
569
 
557
570
  test('selects a hidden wallet and returns the CLI compatibility session', async () => {
558
- const typedCall = jest.fn().mockResolvedValue({
559
- message: {
560
- btc_test_address: 'hidden-state',
561
- session_id: 'hidden-session',
562
- },
563
- });
564
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
571
+ const typedCall = jest
572
+ .fn()
573
+ .mockResolvedValueOnce({ message: { version: 2 } })
574
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
575
+ .mockResolvedValueOnce({
576
+ message: {
577
+ btc_test_address: 'hidden-state',
578
+ session_id: 'hidden-session',
579
+ },
580
+ });
581
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
565
582
  const method = new OpenWalletSession({
566
583
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
567
584
  });
@@ -578,14 +595,7 @@ describe('openWalletSession', () => {
578
595
  sessionId: 'hidden-session',
579
596
  resumed: false,
580
597
  });
581
- expect(typedCall).toHaveBeenCalledWith(
582
- 'DeviceSessionAskPassphrase',
583
- 'Success',
584
- {},
585
- {
586
- timeoutMs: 120_000,
587
- }
588
- );
598
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
589
599
  expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
590
600
  expect(promptPassphrase).toHaveBeenCalled();
591
601
  expect(device.passphraseState).toBeUndefined();
@@ -593,9 +603,13 @@ describe('openWalletSession', () => {
593
603
  });
594
604
 
595
605
  test('selects on-device passphrase entry before getting the prepared session', async () => {
596
- const typedCall = jest.fn().mockResolvedValue({
597
- message: { btc_test_address: 'device-state', session_id: 'device-session' },
598
- });
606
+ const typedCall = jest
607
+ .fn()
608
+ .mockResolvedValueOnce({ message: { version: 2 } })
609
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
610
+ .mockResolvedValueOnce({
611
+ message: { btc_test_address: 'device-state', session_id: 'device-session' },
612
+ });
599
613
  const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
600
614
  const method = new OpenWalletSession({
601
615
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
@@ -607,21 +621,56 @@ describe('openWalletSession', () => {
607
621
  walletType: 'hidden',
608
622
  passphraseState: 'device-state',
609
623
  });
610
- expect(typedCall).toHaveBeenCalledWith(
611
- 'DeviceSessionAskPassphrase',
612
- 'Success',
613
- {},
624
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionAskPassphrase', 'Success', {});
625
+ expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
626
+ });
627
+
628
+ test('forwards a host passphrase to Pro2 firmware', async () => {
629
+ const typedCall = jest
630
+ .fn()
631
+ .mockResolvedValueOnce({ message: { version: 2 } })
632
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
633
+ .mockResolvedValueOnce({
634
+ message: { btc_test_address: 'host-state', session_id: 'host-session' },
635
+ });
636
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'host hidden wallet' });
637
+ const method = new OpenWalletSession({
638
+ payload: {
639
+ method: 'openWalletSession',
640
+ connectId: 'connect-id',
641
+ mode: 'select-hidden',
642
+ },
643
+ });
644
+ method.init();
645
+ method.device = createDevice({
646
+ typedCall,
647
+ promptPassphrase,
648
+ }) as any;
649
+
650
+ await expect(method.run()).resolves.toMatchObject({
651
+ walletType: 'hidden',
652
+ passphraseState: 'host-state',
653
+ });
654
+ expect(promptPassphrase).toHaveBeenCalledWith(
614
655
  {
615
- timeoutMs: 120_000,
616
- }
656
+ existsAttachPinUser: false,
657
+ source: 'wallet-session-coordinator',
658
+ reason: 'open-wallet',
659
+ },
660
+ { cancelDeviceOnReject: false }
617
661
  );
618
- expect(typedCall).toHaveBeenCalledWith('DeviceSessionGet', 'DeviceSession', {});
662
+ expect(typedCall).toHaveBeenNthCalledWith(2, 'DeviceSessionAskPassphrase', 'Success', {
663
+ passphrase: 'host hidden wallet',
664
+ });
619
665
  });
620
666
 
621
667
  test('selects Attach PIN only when the device reports an existing binding', async () => {
622
- const typedCall = jest.fn().mockResolvedValue({
623
- message: { btc_test_address: 'attach-state', session_id: 'attach-session' },
624
- });
668
+ const typedCall = jest
669
+ .fn()
670
+ .mockResolvedValueOnce({ message: { version: 2 } })
671
+ .mockResolvedValueOnce({
672
+ message: { btc_test_address: 'attach-state', session_id: 'attach-session' },
673
+ });
625
674
  const promptPassphrase = jest.fn().mockResolvedValue({ attachPinOnDevice: true });
626
675
  const method = new OpenWalletSession({
627
676
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
@@ -636,7 +685,11 @@ describe('openWalletSession', () => {
636
685
  passphraseState: 'attach-state',
637
686
  });
638
687
  expect(promptPassphrase).toHaveBeenCalledWith(
639
- expect.objectContaining({ existsAttachPinUser: true, deviceOnly: true }),
688
+ {
689
+ existsAttachPinUser: true,
690
+ source: 'wallet-session-coordinator',
691
+ reason: 'open-wallet',
692
+ },
640
693
  { cancelDeviceOnReject: false }
641
694
  );
642
695
  expect(device.unlockDevice).toHaveBeenCalledWith(DeviceSessionPinType.AttachToPin);
@@ -655,8 +708,12 @@ describe('openWalletSession', () => {
655
708
  ])(
656
709
  'rejects an incomplete Protocol V2 hidden-wallet response missing $missingField',
657
710
  async ({ message }) => {
658
- const typedCall = jest.fn().mockResolvedValue({ message });
659
- const promptPassphrase = jest.fn().mockResolvedValue({ passphrase: 'hidden secret' });
711
+ const typedCall = jest
712
+ .fn()
713
+ .mockResolvedValueOnce({ message: { version: 2 } })
714
+ .mockResolvedValueOnce({ message: { message: 'passphrase prepared' } })
715
+ .mockResolvedValueOnce({ message });
716
+ const promptPassphrase = jest.fn().mockResolvedValue({ passphraseOnDevice: true });
660
717
  const method = new OpenWalletSession({
661
718
  payload: { method: 'openWalletSession', connectId: 'connect-id', mode: 'select-hidden' },
662
719
  });
@@ -674,12 +731,15 @@ describe('openWalletSession', () => {
674
731
  );
675
732
 
676
733
  test('resumes a known hidden wallet without prompting or device selection', async () => {
677
- const typedCall = jest.fn().mockResolvedValue({
678
- message: {
679
- btc_test_address: 'hidden-state',
680
- session_id: 'renewed-session',
681
- },
682
- });
734
+ const typedCall = jest
735
+ .fn()
736
+ .mockResolvedValueOnce({ message: { version: 2 } })
737
+ .mockResolvedValueOnce({
738
+ message: {
739
+ btc_test_address: 'hidden-state',
740
+ session_id: 'renewed-session',
741
+ },
742
+ });
683
743
  const promptPassphrase = jest.fn();
684
744
  const method = new OpenWalletSession({
685
745
  payload: {
@@ -709,12 +769,15 @@ describe('openWalletSession', () => {
709
769
  });
710
770
 
711
771
  test('validates a locked Protocol V2 resume binding after deviceId refresh', async () => {
712
- const typedCall = jest.fn().mockResolvedValue({
713
- message: {
714
- btc_test_address: 'hidden-state',
715
- session_id: 'renewed-session',
716
- },
717
- });
772
+ const typedCall = jest
773
+ .fn()
774
+ .mockResolvedValueOnce({ message: { version: 2 } })
775
+ .mockResolvedValueOnce({
776
+ message: {
777
+ btc_test_address: 'hidden-state',
778
+ session_id: 'renewed-session',
779
+ },
780
+ });
718
781
  const method = new OpenWalletSession({
719
782
  payload: {
720
783
  method: 'openWalletSession',
@@ -0,0 +1,28 @@
1
+ import { validateProtocolV2PingMessage } from '../src/api/protocol-v2/Ping';
2
+
3
+ jest.mock('../src/data/config', () => ({
4
+ DEFAULT_DOMAIN: 'https://example.com/',
5
+ getSDKVersion: () => '0.0.0-test',
6
+ }));
7
+
8
+ describe('Protocol V2 diagnostic Ping validation', () => {
9
+ test('accepts messages up to the firmware UTF-8 byte limit', () => {
10
+ expect(validateProtocolV2PingMessage('x'.repeat(63))).toBe('x'.repeat(63));
11
+ expect(validateProtocolV2PingMessage('测'.repeat(21))).toBe('测'.repeat(21));
12
+ });
13
+
14
+ test('rejects messages larger than the firmware UTF-8 byte limit', () => {
15
+ expect(() => validateProtocolV2PingMessage('x'.repeat(64))).toThrow(
16
+ 'Protocol V2 Ping message must not exceed 63 UTF-8 bytes.'
17
+ );
18
+ expect(() => validateProtocolV2PingMessage('测'.repeat(22))).toThrow(
19
+ 'Protocol V2 Ping message must not exceed 63 UTF-8 bytes.'
20
+ );
21
+ });
22
+
23
+ test('rejects non-string messages at the runtime API boundary', () => {
24
+ expect(() => validateProtocolV2PingMessage(64)).toThrow(
25
+ 'Protocol V2 Ping message must be a string.'
26
+ );
27
+ });
28
+ });