@onekeyfe/hd-core 1.1.27-alpha.30 → 1.1.27-alpha.32

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 (111) hide show
  1. package/__tests__/evmSignTransaction.test.ts +1 -1
  2. package/__tests__/evmSignTypedData.test.ts +1 -1
  3. package/__tests__/protocol-v2.test.ts +503 -37
  4. package/dist/api/DirList.d.ts.map +1 -1
  5. package/dist/api/DirMake.d.ts.map +1 -1
  6. package/dist/api/DirRemove.d.ts.map +1 -1
  7. package/dist/api/FileDelete.d.ts.map +1 -1
  8. package/dist/api/FileRead.d.ts.map +1 -1
  9. package/dist/api/FileWrite.d.ts.map +1 -1
  10. package/dist/api/GetPassphraseState.d.ts +6 -1
  11. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  12. package/dist/api/PathInfo.d.ts.map +1 -1
  13. package/dist/api/conflux/ConfluxSignTransaction.d.ts.map +1 -1
  14. package/dist/api/dynex/DnxGetAddress.d.ts.map +1 -1
  15. package/dist/api/dynex/DnxSignTransaction.d.ts.map +1 -1
  16. package/dist/api/helpers/batchGetPublickeys.d.ts.map +1 -1
  17. package/dist/api/helpers/filesystemValidation.d.ts +7 -0
  18. package/dist/api/helpers/filesystemValidation.d.ts.map +1 -0
  19. package/dist/api/index.d.ts +5 -5
  20. package/dist/api/index.d.ts.map +1 -1
  21. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +7 -0
  22. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -0
  23. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts +7 -0
  24. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts.map +1 -0
  25. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +6 -0
  26. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -0
  27. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +6 -0
  28. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +1 -0
  29. package/dist/api/protocol-v2/DeviceReboot.d.ts +7 -0
  30. package/dist/api/protocol-v2/DeviceReboot.d.ts.map +1 -0
  31. package/dist/api/protocol-v2/helpers.d.ts +19 -19
  32. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  33. package/dist/core/index.d.ts.map +1 -1
  34. package/dist/data-manager/DataManager.d.ts +4 -3
  35. package/dist/data-manager/DataManager.d.ts.map +1 -1
  36. package/dist/data-manager/MessagesConfig.d.ts +2 -2
  37. package/dist/data-manager/MessagesConfig.d.ts.map +1 -1
  38. package/dist/data-manager/TransportManager.d.ts +3 -3
  39. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  40. package/dist/device/Device.d.ts.map +1 -1
  41. package/dist/index.d.ts +37 -27
  42. package/dist/index.js +385 -236
  43. package/dist/protocols/protocol-v2/features.d.ts +2 -1
  44. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  45. package/dist/protocols/protocol-v2/firmware.d.ts +7 -7
  46. package/dist/types/api/getPassphraseState.d.ts +10 -1
  47. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  48. package/dist/types/api/index.d.ts +7 -6
  49. package/dist/types/api/index.d.ts.map +1 -1
  50. package/dist/types/api/protocolV2.d.ts +16 -16
  51. package/dist/types/api/protocolV2.d.ts.map +1 -1
  52. package/dist/utils/deviceFeaturesUtils.d.ts +5 -3
  53. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  54. package/dist/utils/patch.d.ts +1 -1
  55. package/dist/utils/patch.d.ts.map +1 -1
  56. package/package.json +4 -4
  57. package/src/api/DirList.ts +6 -2
  58. package/src/api/DirMake.ts +2 -1
  59. package/src/api/DirRemove.ts +2 -1
  60. package/src/api/FileDelete.ts +2 -1
  61. package/src/api/FileRead.ts +12 -5
  62. package/src/api/FileWrite.ts +19 -7
  63. package/src/api/FirmwareUpdateV4.ts +13 -13
  64. package/src/api/GetPassphraseState.ts +18 -2
  65. package/src/api/PathInfo.ts +2 -1
  66. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +15 -0
  67. package/src/api/conflux/ConfluxSignTransaction.ts +5 -2
  68. package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
  69. package/src/api/device/DeviceRebootToBootloader.ts +4 -4
  70. package/src/api/dynex/DnxGetAddress.ts +7 -0
  71. package/src/api/dynex/DnxSignTransaction.ts +7 -0
  72. package/src/api/evm/EVMGetAddress.ts +1 -1
  73. package/src/api/evm/EVMGetPublicKey.ts +1 -1
  74. package/src/api/evm/EVMSignMessage.ts +1 -1
  75. package/src/api/evm/EVMSignTransaction.ts +1 -1
  76. package/src/api/evm/EVMSignTypedData.ts +6 -6
  77. package/src/api/evm/EVMVerifyMessage.ts +1 -1
  78. package/src/api/helpers/batchGetPublickeys.ts +4 -2
  79. package/src/api/helpers/filesystemValidation.ts +51 -0
  80. package/src/api/index.ts +5 -5
  81. package/src/api/protocol-v2/{DevFirmwareUpdate.ts → DeviceFirmwareUpdate.ts} +5 -4
  82. package/src/api/protocol-v2/{DevGetDeviceInfo.ts → DeviceGetDeviceInfo.ts} +3 -3
  83. package/src/api/protocol-v2/{DevGetFirmwareUpdateStatus.ts → DeviceGetFirmwareUpdateStatus.ts} +3 -3
  84. package/src/api/protocol-v2/{DevGetOnboardingStatus.ts → DeviceGetOnboardingStatus.ts} +3 -3
  85. package/src/api/protocol-v2/{DevReboot.ts → DeviceReboot.ts} +3 -3
  86. package/src/api/protocol-v2/helpers.ts +68 -45
  87. package/src/api/tron/TronSignMessage.ts +1 -1
  88. package/src/api/xrp/XrpSignTransaction.ts +1 -1
  89. package/src/core/index.ts +13 -1
  90. package/src/data/messages/{messages-pro2.json → messages-protocol-v2.json} +67 -63
  91. package/src/data-manager/DataManager.ts +9 -8
  92. package/src/data-manager/MessagesConfig.ts +14 -14
  93. package/src/data-manager/TransportManager.ts +13 -13
  94. package/src/device/Device.ts +7 -3
  95. package/src/inject.ts +9 -9
  96. package/src/protocols/protocol-v2/features.ts +39 -41
  97. package/src/protocols/protocol-v2/firmware.ts +16 -16
  98. package/src/types/api/getPassphraseState.ts +15 -2
  99. package/src/types/api/index.ts +11 -10
  100. package/src/types/api/protocolV2.ts +27 -27
  101. package/src/utils/deviceFeaturesUtils.ts +53 -19
  102. package/dist/api/protocol-v2/DevFirmwareUpdate.d.ts +0 -7
  103. package/dist/api/protocol-v2/DevFirmwareUpdate.d.ts.map +0 -1
  104. package/dist/api/protocol-v2/DevGetDeviceInfo.d.ts +0 -7
  105. package/dist/api/protocol-v2/DevGetDeviceInfo.d.ts.map +0 -1
  106. package/dist/api/protocol-v2/DevGetFirmwareUpdateStatus.d.ts +0 -6
  107. package/dist/api/protocol-v2/DevGetFirmwareUpdateStatus.d.ts.map +0 -1
  108. package/dist/api/protocol-v2/DevGetOnboardingStatus.d.ts +0 -6
  109. package/dist/api/protocol-v2/DevGetOnboardingStatus.d.ts.map +0 -1
  110. package/dist/api/protocol-v2/DevReboot.d.ts +0 -7
  111. package/dist/api/protocol-v2/DevReboot.d.ts.map +0 -1
@@ -1,19 +1,31 @@
1
1
  import JSZip from 'jszip';
2
- import { DevRebootType } from '@onekeyfe/hd-transport';
2
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
3
+ import { DeviceRebootType } from '@onekeyfe/hd-transport';
3
4
 
5
+ import ConfluxSignTransaction from '../src/api/conflux/ConfluxSignTransaction';
6
+ import DnxGetAddress from '../src/api/dynex/DnxGetAddress';
7
+ import DnxSignTransaction from '../src/api/dynex/DnxSignTransaction';
8
+ import DirList from '../src/api/DirList';
4
9
  import FileRead from '../src/api/FileRead';
5
10
  import FileWrite from '../src/api/FileWrite';
6
- import DevFirmwareUpdate from '../src/api/protocol-v2/DevFirmwareUpdate';
7
- import DevGetOnboardingStatus from '../src/api/protocol-v2/DevGetOnboardingStatus';
11
+ import DeviceFirmwareUpdate from '../src/api/protocol-v2/DeviceFirmwareUpdate';
12
+ import DeviceGetOnboardingStatus from '../src/api/protocol-v2/DeviceGetOnboardingStatus';
8
13
  import FirmwareUpdateV3 from '../src/api/FirmwareUpdateV3';
9
14
  import FirmwareUpdateV4 from '../src/api/FirmwareUpdateV4';
10
15
  import GetOnekeyFeatures from '../src/api/GetOnekeyFeatures';
16
+ import { batchGetPublickeys } from '../src/api/helpers/batchGetPublickeys';
11
17
  import KaspaGetAddress from '../src/api/kaspa/KaspaGetAddress';
18
+ import TronSignMessage from '../src/api/tron/TronSignMessage';
19
+ import XrpSignTransaction from '../src/api/xrp/XrpSignTransaction';
12
20
  import { DataManager } from '../src/data-manager';
13
21
  import { Device } from '../src/device/Device';
14
22
  import { UI_REQUEST } from '../src/events/ui-request';
15
23
  import { getProtocolV2Features, normalizeProtocolV2Features } from '../src/protocols/protocol-v2';
16
24
  import { shouldSkipMethodSupportCheck } from '../src/utils';
25
+ import {
26
+ getPassphraseState,
27
+ getPassphraseStateWithRefreshDeviceInfo,
28
+ } from '../src/utils/deviceFeaturesUtils';
17
29
 
18
30
  import type { DeviceCommands } from '../src/device/DeviceCommands';
19
31
 
@@ -110,6 +122,113 @@ describe('Protocol V2 feature adapter', () => {
110
122
  expect(features.ble_enable).toBe(true);
111
123
  });
112
124
 
125
+ test('uses GetPassphraseState payloads compatible with Pro1 passphrase flow', async () => {
126
+ const features = normalizeProtocolV2Features(descriptor as any);
127
+ const typedCall = jest.fn().mockResolvedValue({
128
+ type: 'PassphraseState',
129
+ message: {
130
+ passphrase_state: 'state-1',
131
+ session_id: 'session-1',
132
+ unlocked_attach_pin: false,
133
+ },
134
+ });
135
+ const commands = { typedCall } as unknown as DeviceCommands;
136
+
137
+ await getPassphraseState(features, commands, {
138
+ expectPassphraseState: 'state-1',
139
+ });
140
+ expect(typedCall).toHaveBeenLastCalledWith('GetPassphraseState', 'PassphraseState', {
141
+ passphrase_state: 'state-1',
142
+ });
143
+
144
+ await getPassphraseState(features, commands, {
145
+ expectPassphraseState: 'state-2',
146
+ allowCreateAttachPin: true,
147
+ });
148
+ expect(typedCall).toHaveBeenLastCalledWith('GetPassphraseState', 'PassphraseState', {
149
+ passphrase_state: 'state-2',
150
+ allow_create_attach_pin: true,
151
+ });
152
+
153
+ await getPassphraseState(features, commands, {
154
+ onlyMainPin: true,
155
+ });
156
+ expect(typedCall).toHaveBeenLastCalledWith('GetPassphraseState', 'PassphraseState', {
157
+ _only_main_pin: true,
158
+ });
159
+ });
160
+
161
+ test('stores Pro2 passphrase sessions without selecting them implicitly', async () => {
162
+ const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
163
+ const typedCall = jest.fn().mockResolvedValue({
164
+ type: 'PassphraseState',
165
+ message: {
166
+ passphrase_state: 'state-auto',
167
+ session_id: 'session-auto',
168
+ unlocked_attach_pin: false,
169
+ },
170
+ });
171
+
172
+ (device as any).features = normalizeProtocolV2Features({
173
+ ...descriptor,
174
+ protocolType: 'V2',
175
+ } as any);
176
+ (device as any).commands = { typedCall };
177
+
178
+ await expect(getPassphraseStateWithRefreshDeviceInfo(device)).resolves.toMatchObject({
179
+ passphraseState: 'state-auto',
180
+ newSession: 'session-auto',
181
+ });
182
+
183
+ expect(device.passphraseState).toBeUndefined();
184
+ expect(device.features?.passphrase_protection).toBe(true);
185
+ expect(device.features?.session_id).toBe('session-auto');
186
+ expect(device.getInternalState()).toBeUndefined();
187
+ device.passphraseState = 'state-auto';
188
+ expect(device.getInternalState()).toBe('session-auto');
189
+ expect(typedCall).toHaveBeenLastCalledWith('GetPassphraseState', 'PassphraseState', {
190
+ passphrase_state: undefined,
191
+ });
192
+ });
193
+
194
+ test('does not mark Pro2 passphrase enabled from a main PIN session alone', async () => {
195
+ const device = Device.fromDescriptor({ ...descriptor, protocolType: 'V2' } as any);
196
+ const typedCall = jest.fn().mockResolvedValue({
197
+ type: 'PassphraseState',
198
+ message: {
199
+ session_id: 'main-pin-session',
200
+ unlocked_attach_pin: false,
201
+ },
202
+ });
203
+
204
+ (device as any).features = normalizeProtocolV2Features(
205
+ {
206
+ ...descriptor,
207
+ protocolType: 'V2',
208
+ } as any,
209
+ {
210
+ status: {
211
+ passphrase_protection: false,
212
+ },
213
+ }
214
+ );
215
+ (device as any).commands = { typedCall };
216
+
217
+ await expect(
218
+ getPassphraseStateWithRefreshDeviceInfo(device, { onlyMainPin: true })
219
+ ).resolves.toMatchObject({
220
+ passphraseState: undefined,
221
+ newSession: 'main-pin-session',
222
+ });
223
+
224
+ expect(device.features?.passphrase_protection).toBe(false);
225
+ expect(device.features?.session_id).toBe('main-pin-session');
226
+ expect(device.getInternalState()).toBeUndefined();
227
+ expect(typedCall).toHaveBeenLastCalledWith('GetPassphraseState', 'PassphraseState', {
228
+ _only_main_pin: true,
229
+ });
230
+ });
231
+
113
232
  test('marks fallback features as unavailable when DeviceInfo is missing', () => {
114
233
  const features = normalizeProtocolV2Features(descriptor as any);
115
234
 
@@ -131,10 +250,45 @@ describe('Protocol V2 feature adapter', () => {
131
250
  expect(shouldSkipMethodSupportCheck(features)).toBe(true);
132
251
  });
133
252
 
134
- test('initializes Protocol V2 features with Ping only while DeviceInfo is unsupported', async () => {
253
+ test('initializes Protocol V2 features from DeviceGetDeviceInfo after Ping', async () => {
254
+ const commands = {
255
+ typedCall: jest
256
+ .fn()
257
+ .mockResolvedValueOnce({ type: 'Success', message: { message: 'pong' } })
258
+ .mockResolvedValueOnce({
259
+ type: 'DeviceInfo',
260
+ message: {
261
+ hw: { serial_no: 'PR2SERIAL' },
262
+ status: {
263
+ init_states: true,
264
+ passphrase_protection: true,
265
+ },
266
+ },
267
+ }),
268
+ };
269
+
270
+ const features = await getProtocolV2Features({
271
+ commands: commands as unknown as DeviceCommands,
272
+ descriptor: descriptor as any,
273
+ });
274
+
275
+ expect(features.device_id).toBe('PR2SERIAL');
276
+ expect(features.initialized).toBe(true);
277
+ expect(features.passphrase_protection).toBe(true);
278
+ expect(commands.typedCall).toHaveBeenNthCalledWith(1, 'Ping', 'Success', { message: 'init' });
279
+ expect(commands.typedCall).toHaveBeenNthCalledWith(2, 'DeviceGetDeviceInfo', 'DeviceInfo', {
280
+ targets: expect.objectContaining({ status: true }),
281
+ types: expect.objectContaining({ specific: true }),
282
+ });
283
+ });
284
+
285
+ test('falls back to descriptor features when Protocol V2 DeviceGetDeviceInfo fails', async () => {
135
286
  const onDeviceInfoError = jest.fn();
136
287
  const commands = {
137
- typedCall: jest.fn().mockResolvedValueOnce({ type: 'Success', message: { message: 'pong' } }),
288
+ typedCall: jest
289
+ .fn()
290
+ .mockResolvedValueOnce({ type: 'Success', message: { message: 'pong' } })
291
+ .mockRejectedValueOnce(new Error('DeviceInfo not supported')),
138
292
  };
139
293
 
140
294
  const features = await getProtocolV2Features({
@@ -144,9 +298,8 @@ describe('Protocol V2 feature adapter', () => {
144
298
  });
145
299
 
146
300
  expect(features.device_id).toBe('usb-path');
147
- expect(commands.typedCall).toHaveBeenNthCalledWith(1, 'Ping', 'Success', { message: 'init' });
148
- expect(commands.typedCall).toHaveBeenCalledTimes(1);
149
- expect(onDeviceInfoError).not.toHaveBeenCalled();
301
+ expect(features.passphrase_protection).toBeNull();
302
+ expect(onDeviceInfoError).toHaveBeenCalledWith(expect.any(Error));
150
303
  });
151
304
 
152
305
  test('does not block method-level legacy version checks on Protocol V2', async () => {
@@ -182,6 +335,35 @@ describe('Protocol V2 feature adapter', () => {
182
335
  expect(typedCall).toHaveBeenCalledWith('KaspaGetAddress', 'KaspaAddress', expect.any(Object));
183
336
  });
184
337
 
338
+ test('does not block legacy batch public key support checks on Protocol V2', async () => {
339
+ const paths = [{ address_n: [0x8000002c, 0x80000000, 0x80000000] }] as any;
340
+ const typedCall = jest.fn().mockResolvedValue({
341
+ type: 'EcdsaPublicKeys',
342
+ message: {
343
+ root_fingerprint: 123,
344
+ public_keys: [],
345
+ hd_nodes: [{}],
346
+ },
347
+ });
348
+ const device = {
349
+ originalDescriptor: { protocolType: 'V2' },
350
+ features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
351
+ commands: { typedCall },
352
+ };
353
+
354
+ await expect(
355
+ batchGetPublickeys(device as any, paths, 'secp256k1', 0, { includeNode: true })
356
+ ).resolves.toMatchObject({
357
+ root_fingerprint: 123,
358
+ hd_nodes: [{}],
359
+ });
360
+ expect(typedCall).toHaveBeenCalledWith('BatchGetPublickeys', 'EcdsaPublicKeys', {
361
+ paths,
362
+ ecdsa_curve_name: 'secp256k1',
363
+ include_node: true,
364
+ });
365
+ });
366
+
185
367
  test('returns Protocol V2 oneKey fields without calling legacy OnekeyGetFeatures', async () => {
186
368
  const method = new GetOnekeyFeatures({
187
369
  id: 1,
@@ -224,15 +406,25 @@ describe('Protocol V2 feature adapter', () => {
224
406
  } as any);
225
407
  const typedCall = jest
226
408
  .fn()
227
- .mockResolvedValueOnce({ type: 'Success', message: { message: 'init' } });
409
+ .mockResolvedValueOnce({ type: 'Success', message: { message: 'init' } })
410
+ .mockResolvedValueOnce({
411
+ type: 'DeviceInfo',
412
+ message: {
413
+ hw: { serial_no: 'PR2SERIAL' },
414
+ status: {
415
+ passphrase_protection: true,
416
+ },
417
+ },
418
+ });
228
419
 
229
420
  (device as any).commands = { typedCall };
230
421
 
231
422
  await device.initialize();
232
423
  await device.initialize();
233
424
 
234
- expect(device.features?.device_id).toBe('usb-path');
235
- expect(typedCall).toHaveBeenCalledTimes(1);
425
+ expect(device.features?.device_id).toBe('PR2SERIAL');
426
+ expect(device.features?.passphrase_protection).toBe(true);
427
+ expect(typedCall).toHaveBeenCalledTimes(2);
236
428
  expect(typedCall).toHaveBeenNthCalledWith(
237
429
  1,
238
430
  'Ping',
@@ -242,6 +434,139 @@ describe('Protocol V2 feature adapter', () => {
242
434
  timeoutMs: 10000,
243
435
  }
244
436
  );
437
+ expect(typedCall).toHaveBeenNthCalledWith(
438
+ 2,
439
+ 'DeviceGetDeviceInfo',
440
+ 'DeviceInfo',
441
+ {
442
+ targets: expect.objectContaining({ status: true }),
443
+ types: expect.objectContaining({ specific: true }),
444
+ },
445
+ {
446
+ timeoutMs: 10000,
447
+ }
448
+ );
449
+ });
450
+ });
451
+
452
+ describe('API compatibility handling', () => {
453
+ test('returns a typed unsupported error for Tron sign message V1 before device binding', () => {
454
+ const method = new TronSignMessage({
455
+ id: 1,
456
+ payload: {
457
+ method: 'tronSignMessage',
458
+ path: "m/44'/195'/0'/0/0",
459
+ messageHex: '0x1234',
460
+ messageType: 'V1',
461
+ },
462
+ });
463
+
464
+ expect(() => method.init()).toThrow(
465
+ expect.objectContaining({
466
+ errorCode: HardwareErrorCode.DeviceNotSupportMethod,
467
+ })
468
+ );
469
+ });
470
+
471
+ test('accepts string XRP payment amount values', () => {
472
+ const method = new XrpSignTransaction({
473
+ id: 1,
474
+ payload: {
475
+ method: 'xrpSignTransaction',
476
+ path: "m/44'/144'/0'/0/0",
477
+ transaction: {
478
+ fee: '100000',
479
+ flags: 2147483648,
480
+ sequence: 25,
481
+ maxLedgerVersion: 8820051,
482
+ payment: {
483
+ amount: '100000000',
484
+ destination: 'rBKz5MC2iXdoS3XgnNSYmF69K1Yo4NS3Ws',
485
+ },
486
+ },
487
+ },
488
+ });
489
+
490
+ expect(() => method.init()).not.toThrow();
491
+ expect(method.params.payment?.amount).toBe('100000000');
492
+ });
493
+
494
+ test('accepts Conflux base32 recipient addresses without hex formatting them', () => {
495
+ const to = 'cfx:aak2rra2njvd77ezwjvx04kkds9fzagfe6ku8scz91';
496
+ const method = new ConfluxSignTransaction({
497
+ id: 1,
498
+ payload: {
499
+ method: 'confluxSignTransaction',
500
+ path: "m/44'/503'/0'/0/0",
501
+ transaction: {
502
+ to,
503
+ value: '0x0',
504
+ data: '0x',
505
+ chainId: 1,
506
+ nonce: '0x00',
507
+ epochHeight: '0x00',
508
+ gasLimit: '0x5208',
509
+ storageLimit: '0x5208',
510
+ gasPrice: '0xbebc200',
511
+ },
512
+ },
513
+ });
514
+
515
+ expect(() => method.init()).not.toThrow();
516
+ expect(method.formattedTx?.to).toBe(to);
517
+ });
518
+
519
+ test('returns a typed unsupported error for Dynex signing on Protocol V2', async () => {
520
+ const method = new DnxSignTransaction({
521
+ id: 1,
522
+ payload: {
523
+ method: 'dnxSignTransaction',
524
+ path: "m/44'/29538'/0'/0/0",
525
+ inputs: [
526
+ {
527
+ prevIndex: 1,
528
+ globalIndex: 1,
529
+ txPubkey: '00',
530
+ prevOutPubkey: '00',
531
+ amount: '1',
532
+ },
533
+ ],
534
+ toAddress: 'dnx-address',
535
+ amount: '1',
536
+ fee: '1',
537
+ },
538
+ });
539
+
540
+ method.init();
541
+ (method as any).device = {
542
+ originalDescriptor: { protocolType: 'V2' },
543
+ features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
544
+ };
545
+
546
+ await expect(method.run()).rejects.toMatchObject({
547
+ errorCode: HardwareErrorCode.DeviceNotSupportMethod,
548
+ });
549
+ });
550
+
551
+ test('returns a typed unsupported error for Dynex address on Protocol V2', async () => {
552
+ const method = new DnxGetAddress({
553
+ id: 1,
554
+ payload: {
555
+ method: 'dnxGetAddress',
556
+ path: "m/44'/29538'/0'/0/0",
557
+ showOnOneKey: false,
558
+ },
559
+ });
560
+
561
+ method.init();
562
+ (method as any).device = {
563
+ originalDescriptor: { protocolType: 'V2' },
564
+ features: normalizeProtocolV2Features({ ...descriptor, protocolType: 'V2' } as any),
565
+ };
566
+
567
+ await expect(method.run()).rejects.toMatchObject({
568
+ errorCode: HardwareErrorCode.DeviceNotSupportMethod,
569
+ });
245
570
  });
246
571
  });
247
572
 
@@ -272,6 +597,9 @@ describe('Protocol V2 firmware update targets', () => {
272
597
  if (name === 'Ping') {
273
598
  return Promise.resolve({ type: 'Success', message: { message: 'init' } });
274
599
  }
600
+ if (name === 'DeviceGetDeviceInfo') {
601
+ return Promise.resolve({ type: 'DeviceInfo', message: {} });
602
+ }
275
603
  return Promise.reject(new Error(`unexpected call ${name}`));
276
604
  });
277
605
  const commands = { typedCall };
@@ -294,7 +622,17 @@ describe('Protocol V2 firmware update targets', () => {
294
622
  { message: 'init' },
295
623
  { timeoutMs: 5000 }
296
624
  );
297
- expect(typedCall).toHaveBeenCalledTimes(1);
625
+ expect(typedCall).toHaveBeenNthCalledWith(
626
+ 2,
627
+ 'DeviceGetDeviceInfo',
628
+ 'DeviceInfo',
629
+ {
630
+ targets: expect.objectContaining({ status: true }),
631
+ types: expect.objectContaining({ specific: true }),
632
+ },
633
+ { timeoutMs: 5000 }
634
+ );
635
+ expect(typedCall).toHaveBeenCalledTimes(2);
298
636
  expect(typedCall).not.toHaveBeenCalledWith('Initialize', 'Features', {});
299
637
  expect(versions).toEqual({
300
638
  bootloaderVersion: '0.0.0',
@@ -346,7 +684,7 @@ describe('Protocol V2 firmware update targets', () => {
346
684
  ],
347
685
  bootloaderBinary: null,
348
686
  });
349
- expect((method as any).protocolV2Reboot).not.toHaveBeenCalledWith(DevRebootType.Bootloader);
687
+ expect((method as any).protocolV2Reboot).not.toHaveBeenCalledWith(DeviceRebootType.Bootloader);
350
688
  expect(method.postTipMessage).not.toHaveBeenCalledWith('AutoRebootToBootloader');
351
689
  });
352
690
 
@@ -365,7 +703,7 @@ describe('Protocol V2 firmware update targets', () => {
365
703
  await (method as any).exitProtocolV2BootloaderToNormal();
366
704
 
367
705
  expect(method.postTipMessage).toHaveBeenCalledWith('SwitchFirmwareReconnectDevice');
368
- expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DevRebootType.Normal);
706
+ expect((method as any).protocolV2Reboot).toHaveBeenCalledWith(DeviceRebootType.Normal);
369
707
  });
370
708
 
371
709
  test('treats iOS BLE RxError 6 during Protocol V2 reboot as expected disconnect', async () => {
@@ -385,7 +723,7 @@ describe('Protocol V2 firmware update targets', () => {
385
723
  getCommands: () => ({ typedCall }),
386
724
  };
387
725
 
388
- await expect((method as any).protocolV2Reboot(DevRebootType.Normal)).resolves.toEqual({
726
+ await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
389
727
  message: 'Device rebooted successfully',
390
728
  });
391
729
  });
@@ -405,7 +743,7 @@ describe('Protocol V2 firmware update targets', () => {
405
743
  getCommands: () => ({ typedCall }),
406
744
  };
407
745
 
408
- await expect((method as any).protocolV2Reboot(DevRebootType.Normal)).resolves.toEqual({
746
+ await expect((method as any).protocolV2Reboot(DeviceRebootType.Normal)).resolves.toEqual({
409
747
  message: 'Device rebooted successfully',
410
748
  });
411
749
  });
@@ -425,7 +763,7 @@ describe('Protocol V2 firmware update targets', () => {
425
763
  )
426
764
  )
427
765
  .mockResolvedValueOnce({
428
- type: 'DevFirmwareUpdateStatus',
766
+ type: 'DeviceFirmwareUpdateStatus',
429
767
  message: {
430
768
  targets: [{ target_id: 2, status: 0 }],
431
769
  },
@@ -446,7 +784,7 @@ describe('Protocol V2 firmware update targets', () => {
446
784
  expect(typedCall).toHaveBeenCalledTimes(2);
447
785
  });
448
786
 
449
- test('passes resource, bootloader, BLE, SE and app files to DevFirmwareUpdate targets', async () => {
787
+ test('passes resource, bootloader, BLE, SE and app files to DeviceFirmwareUpdate targets', async () => {
450
788
  const resourceZip = new JSZip();
451
789
  resourceZip.file('icons/home.png', new Uint8Array([1, 2, 3]));
452
790
  const resourceBinary = await resourceZip.generateAsync({ type: 'arraybuffer' });
@@ -499,10 +837,10 @@ describe('Protocol V2 firmware update targets', () => {
499
837
  expect((method as any).protocolV2StartFirmwareUpdate).toHaveBeenCalledWith({
500
838
  targets: [
501
839
  { target_id: 10, path: 'vol1:res/' },
502
- { target_id: 1, path: 'vol1:bootloader.bin' },
503
- { target_id: 2, path: 'vol1:ble-firmware.bin' },
504
- { target_id: 3, path: 'vol1:se1-firmware.bin' },
505
- { target_id: 0, path: 'vol1:firmware.bin' },
840
+ { target_id: 2, path: 'vol1:bootloader.bin' },
841
+ { target_id: 5, path: 'vol1:ble-firmware.bin' },
842
+ { target_id: 6, path: 'vol1:se1-firmware.bin' },
843
+ { target_id: 3, path: 'vol1:firmware.bin' },
506
844
  ],
507
845
  });
508
846
  expect((method as any).waitForProtocolV2FirmwareUpdateComplete).toHaveBeenCalled();
@@ -621,10 +959,10 @@ describe('Protocol V2 firmware update targets', () => {
621
959
  });
622
960
 
623
961
  const callOptions = typedCall.mock.calls[0][3];
624
- expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DevFirmwareUpdateStatus']);
625
- expect(callOptions.intermediateTypes).toEqual(['DevFirmwareInstallProgress']);
962
+ expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
963
+ expect(callOptions.intermediateTypes).toEqual(['DeviceFirmwareInstallProgress']);
626
964
  callOptions.onIntermediateResponse({
627
- type: 'DevFirmwareInstallProgress',
965
+ type: 'DeviceFirmwareInstallProgress',
628
966
  message: { target_id: 0, progress: 42 },
629
967
  });
630
968
 
@@ -639,7 +977,7 @@ describe('Protocol V2 firmware update targets', () => {
639
977
  },
640
978
  });
641
979
  const typedCall = jest.fn().mockResolvedValue({
642
- type: 'DevFirmwareUpdateStatus',
980
+ type: 'DeviceFirmwareUpdateStatus',
643
981
  message: { targets: [{ target_id: 0, status: 1 }] },
644
982
  });
645
983
 
@@ -653,24 +991,25 @@ describe('Protocol V2 firmware update targets', () => {
653
991
  targets: [{ target_id: 0, path: 'vol1:firmware.bin' }],
654
992
  });
655
993
 
656
- expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DevFirmwareUpdateStatus']);
994
+ expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
657
995
  expect(method.postTipMessage).toHaveBeenCalledWith('FirmwareUpdating');
658
996
  });
659
997
  });
660
998
 
661
999
  describe('Protocol V2 firmware update method', () => {
662
- test('returns DevFirmwareUpdateStatus from low-level update trigger', async () => {
663
- const method = new DevFirmwareUpdate({
1000
+ test('returns DeviceFirmwareUpdateStatus from low-level update trigger', async () => {
1001
+ const method = new DeviceFirmwareUpdate({
664
1002
  id: 1,
665
1003
  payload: {
666
- method: 'devFirmwareUpdate',
1004
+ method: 'deviceFirmwareUpdate',
1005
+ targetId: 3,
667
1006
  path: 'vol0:firmware.bin',
668
1007
  },
669
1008
  });
670
1009
  method.init();
671
1010
 
672
1011
  const typedCall = jest.fn().mockResolvedValue({
673
- type: 'DevFirmwareUpdateStatus',
1012
+ type: 'DeviceFirmwareUpdateStatus',
674
1013
  message: { targets: [{ target_id: 0, status: 1 }] },
675
1014
  });
676
1015
 
@@ -681,22 +1020,71 @@ describe('Protocol V2 firmware update method', () => {
681
1020
  await expect(method.run()).resolves.toEqual({
682
1021
  targets: [{ target_id: 0, status: 1 }],
683
1022
  });
684
- expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DevFirmwareUpdateStatus']);
1023
+ expect(typedCall.mock.calls[0][1]).toEqual(['Success', 'DeviceFirmwareUpdateStatus']);
1024
+ expect(typedCall.mock.calls[0][2]).toEqual({
1025
+ targets: [{ target_id: 3, path: 'vol0:firmware.bin' }],
1026
+ });
1027
+ });
1028
+
1029
+ test('rejects missing or invalid firmware targets before transport call', async () => {
1030
+ const typedCall = jest.fn();
1031
+ const method = new DeviceFirmwareUpdate({
1032
+ id: 1,
1033
+ payload: {
1034
+ method: 'deviceFirmwareUpdate',
1035
+ path: 'vol0:firmware.bin',
1036
+ },
1037
+ });
1038
+ method.init();
1039
+ (method as any).device = {
1040
+ commands: { typedCall },
1041
+ };
1042
+
1043
+ await expect(method.run()).rejects.toMatchObject({
1044
+ errorCode: HardwareErrorCode.CallMethodInvalidParameter,
1045
+ });
1046
+ expect(typedCall).not.toHaveBeenCalled();
1047
+ });
1048
+
1049
+ test('accepts targetId alias inside firmware targets', async () => {
1050
+ const typedCall = jest.fn().mockResolvedValue({ message: {} });
1051
+ const method = new DeviceFirmwareUpdate({
1052
+ id: 1,
1053
+ payload: {
1054
+ method: 'deviceFirmwareUpdate',
1055
+ targets: [
1056
+ {
1057
+ target_id: undefined,
1058
+ targetId: 3,
1059
+ path: 'vol0:firmware.bin',
1060
+ },
1061
+ ],
1062
+ } as any,
1063
+ });
1064
+ method.init();
1065
+ (method as any).device = {
1066
+ commands: { typedCall },
1067
+ };
1068
+
1069
+ await method.run();
1070
+ expect(typedCall.mock.calls[0][2]).toEqual({
1071
+ targets: [{ target_id: 3, path: 'vol0:firmware.bin' }],
1072
+ });
685
1073
  });
686
1074
  });
687
1075
 
688
1076
  describe('Protocol V2 onboarding status method', () => {
689
- test('returns DevOnboardingStatus from low-level status query', async () => {
690
- const method = new DevGetOnboardingStatus({
1077
+ test('returns DeviceOnboardingStatus from low-level status query', async () => {
1078
+ const method = new DeviceGetOnboardingStatus({
691
1079
  id: 1,
692
1080
  payload: {
693
- method: 'devGetOnboardingStatus',
1081
+ method: 'deviceGetOnboardingStatus',
694
1082
  },
695
1083
  });
696
1084
  method.init();
697
1085
 
698
1086
  const typedCall = jest.fn().mockResolvedValue({
699
- type: 'DevOnboardingStatus',
1087
+ type: 'DeviceOnboardingStatus',
700
1088
  message: {
701
1089
  page_index: 2,
702
1090
  page_count: 5,
@@ -713,11 +1101,45 @@ describe('Protocol V2 onboarding status method', () => {
713
1101
  page_count: 5,
714
1102
  page_name: 'backup',
715
1103
  });
716
- expect(typedCall).toHaveBeenCalledWith('DevGetOnboardingStatus', 'DevOnboardingStatus', {});
1104
+ expect(typedCall).toHaveBeenCalledWith(
1105
+ 'DeviceGetOnboardingStatus',
1106
+ 'DeviceOnboardingStatus',
1107
+ {}
1108
+ );
717
1109
  });
718
1110
  });
719
1111
 
720
1112
  describe('Protocol V2 file write method', () => {
1113
+ test('rejects invalid write parameters before transport call', () => {
1114
+ expect(() => {
1115
+ const method = new FileWrite({
1116
+ id: 1,
1117
+ payload: {
1118
+ method: 'fileWrite',
1119
+ path: 'vol1:test.bin',
1120
+ offset: -1,
1121
+ data: new Uint8Array([1]),
1122
+ },
1123
+ });
1124
+ method.init();
1125
+ }).toThrow(
1126
+ expect.objectContaining({ errorCode: HardwareErrorCode.CallMethodInvalidParameter })
1127
+ );
1128
+
1129
+ expect(() => {
1130
+ const method = new FileWrite({
1131
+ id: 1,
1132
+ payload: {
1133
+ method: 'fileWrite',
1134
+ path: 'vol1:test.bin',
1135
+ } as any,
1136
+ });
1137
+ method.init();
1138
+ }).toThrow(
1139
+ expect.objectContaining({ errorCode: HardwareErrorCode.CallMethodInvalidParameter })
1140
+ );
1141
+ });
1142
+
721
1143
  test('uses demo-aligned overwrite and append defaults', async () => {
722
1144
  const typedCall = jest.fn().mockResolvedValue({ message: { processed_byte: 1 } });
723
1145
  const method = new FileWrite({
@@ -860,6 +1282,50 @@ describe('Protocol V2 file write method', () => {
860
1282
  });
861
1283
 
862
1284
  describe('Protocol V2 file read method', () => {
1285
+ test('rejects invalid read and directory parameters before transport call', () => {
1286
+ expect(() => {
1287
+ const method = new FileRead({
1288
+ id: 1,
1289
+ payload: {
1290
+ method: 'fileRead',
1291
+ path: '',
1292
+ offset: 0,
1293
+ },
1294
+ });
1295
+ method.init();
1296
+ }).toThrow(
1297
+ expect.objectContaining({ errorCode: HardwareErrorCode.CallMethodInvalidParameter })
1298
+ );
1299
+
1300
+ expect(() => {
1301
+ const method = new FileRead({
1302
+ id: 1,
1303
+ payload: {
1304
+ method: 'fileRead',
1305
+ path: 'vol1:test.bin',
1306
+ totalSize: -1,
1307
+ },
1308
+ });
1309
+ method.init();
1310
+ }).toThrow(
1311
+ expect.objectContaining({ errorCode: HardwareErrorCode.CallMethodInvalidParameter })
1312
+ );
1313
+
1314
+ expect(() => {
1315
+ const method = new DirList({
1316
+ id: 1,
1317
+ payload: {
1318
+ method: 'dirList',
1319
+ path: 'vol1:',
1320
+ depth: -1,
1321
+ },
1322
+ });
1323
+ method.init();
1324
+ }).toThrow(
1325
+ expect.objectContaining({ errorCode: HardwareErrorCode.CallMethodInvalidParameter })
1326
+ );
1327
+ });
1328
+
863
1329
  test('reads full file in chunks when read length is 0', async () => {
864
1330
  const firstChunk = new Uint8Array(64).fill(1);
865
1331
  const typedCall = jest