@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
@@ -46,9 +46,9 @@ import type { TypedResponseMessage } from '../device/DeviceCommands';
46
46
  import type {
47
47
  Features,
48
48
  IFirmwareReleaseInfo,
49
- IProtocolV2BootResources,
50
49
  IProtocolV2FirmwareComponent,
51
50
  IProtocolV2Resource,
51
+ IProtocolV2ResourceFile,
52
52
  IVersionArray,
53
53
  } from '../types';
54
54
 
@@ -77,7 +77,6 @@ const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
77
77
  const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
78
78
  const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
79
79
  const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
80
- const PROTOCOL_V2_BOOT_RESOURCES_FILE_NAME = 'boot_resources.crate.okpkg';
81
80
 
82
81
  const getProtocolV2DeviceTransferProgress = (
83
82
  bytesBeforeChunk: number,
@@ -112,7 +111,7 @@ type ProtocolV2FirmwareUpdateStartResponse = TypedResponseMessage<'Success'>;
112
111
 
113
112
  type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
114
113
  type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
115
- kind: ProtocolV2RemoteComponentTarget['kind'] | 'boot_resources';
114
+ kind: ProtocolV2RemoteComponentTarget['kind'];
116
115
  };
117
116
  type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
118
117
  path: string;
@@ -125,7 +124,7 @@ type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
125
124
  onTransferredBytes?: (transferredBytes: number) => void;
126
125
  };
127
126
 
128
- /** RESC bundle okpkg written independently to devicePath through FileWrite. */
127
+ /** Protocol V2 resource file written independently to devicePath through FileWrite. */
129
128
  type ProtocolV2ResourceBundleBinary = {
130
129
  name: string;
131
130
  binary: ArrayBuffer;
@@ -371,19 +370,30 @@ export const isProtocolV2FirmwareFingerprintValid = (
371
370
 
372
371
  export const assertProtocolV2ReconnectIdentity = (
373
372
  expectedSerialNumber?: string,
374
- actualSerialNumber?: string
373
+ actualSerialNumber?: string,
374
+ expectedPath?: string,
375
+ actualPath?: string
375
376
  ) => {
376
- // Some Pro2 loader builds omit serial_no. Reconnect still uses the same BLE
377
- // descriptor or requires a uniquely enumerated USB device before this check.
378
- if (!expectedSerialNumber || !actualSerialNumber) {
377
+ if (expectedSerialNumber && actualSerialNumber) {
378
+ if (actualSerialNumber !== expectedSerialNumber) {
379
+ throw ERRORS.TypedError(
380
+ HardwareErrorCode.DeviceNotFound,
381
+ `Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
382
+ );
383
+ }
379
384
  return;
380
385
  }
381
- if (actualSerialNumber !== expectedSerialNumber) {
382
- throw ERRORS.TypedError(
383
- HardwareErrorCode.DeviceNotFound,
384
- `Protocol V2 reconnect physical identity mismatch: expected ${expectedSerialNumber}, received ${actualSerialNumber}`
385
- );
386
+
387
+ if (expectedPath && actualPath && expectedPath === actualPath) {
388
+ return;
386
389
  }
390
+
391
+ throw ERRORS.TypedError(
392
+ HardwareErrorCode.DeviceNotFound,
393
+ `Protocol V2 reconnect physical identity unavailable: expected path ${
394
+ expectedPath ?? 'unknown'
395
+ }, received ${actualPath ?? 'unknown'}`
396
+ );
387
397
  };
388
398
 
389
399
  /**
@@ -397,6 +407,8 @@ export const assertProtocolV2ReconnectIdentity = (
397
407
  export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
398
408
  private protocolV2ExpectedSerialNumber?: string;
399
409
 
410
+ private protocolV2ExpectedPath?: string;
411
+
400
412
  getSupportedProtocols() {
401
413
  return ['V2'] as const;
402
414
  }
@@ -427,7 +439,6 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
427
439
  { name: 'chunkSize', type: 'number' },
428
440
  { name: 'forcedUpdateRes', type: 'boolean' },
429
441
  { name: 'bootloaderBinary', type: 'buffer' },
430
- { name: 'bootResourcesBinary', type: 'buffer' },
431
442
  { name: 'romloaderBinary', type: 'buffer' },
432
443
  { name: 'applicationP1Binary', type: 'buffer' },
433
444
  { name: 'applicationP2Binary', type: 'buffer' },
@@ -439,14 +450,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
439
450
  { name: 'firmwareType', type: 'string' },
440
451
  { name: 'targetsToUpdate', type: 'array', allowEmpty: true },
441
452
  { name: 'platform', type: 'string' },
442
- { name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
453
+ { name: 'resourceFiles', type: 'array', allowEmpty: true },
443
454
  ]);
444
455
 
445
456
  this.params = {
446
457
  chunkSize: payload.chunkSize,
447
458
  forcedUpdateRes: payload.forcedUpdateRes,
448
459
  bootloaderBinary: payload.bootloaderBinary,
449
- bootResourcesBinary: payload.bootResourcesBinary,
450
460
  romloaderBinary: payload.romloaderBinary,
451
461
  applicationP1Binary: payload.applicationP1Binary,
452
462
  applicationP2Binary: payload.applicationP2Binary,
@@ -455,7 +465,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
455
465
  se02Binary: payload.se02Binary,
456
466
  se03Binary: payload.se03Binary,
457
467
  se04Binary: payload.se04Binary,
458
- resourceBundleFiles: payload.resourceBundleFiles,
468
+ resourceFiles: payload.resourceFiles,
459
469
  firmwareType: payload.firmwareType,
460
470
  targetsToUpdate: payload.targetsToUpdate,
461
471
  platform: payload.platform,
@@ -490,26 +500,24 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
490
500
  const deviceFirmwareType = getFirmwareType(deviceFeatures);
491
501
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
492
502
  const needsRemoteResources =
493
- !this.params.resourceBundleFiles?.length &&
494
- !!this.params.targetsToUpdate?.includes('resource');
503
+ !this.params.resourceFiles?.length && !!this.params.targetsToUpdate?.includes('resource');
495
504
 
496
505
  let fwBinaryMap: ProtocolV2TargetBinary[] = [];
497
506
  let bootloaderBinary: ArrayBuffer | null = null;
498
- let bootResourcesInstallItem: ProtocolV2InstallItem | undefined;
499
507
  let installItems: ProtocolV2InstallItem[] | undefined;
500
508
  let resourceBundles: ProtocolV2ResourceBundleBinary[] | undefined;
501
509
  try {
502
510
  this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
503
- resourceBundles = this.prepareExplicitProtocolV2ResourceBundles();
511
+ resourceBundles = this.prepareExplicitProtocolV2ResourceFiles();
504
512
  fwBinaryMap = this.collectExplicitTargetBinaries();
505
513
  bootloaderBinary = this.prepareBootloaderBinary();
506
514
  const needsRemoteFirmware = !this.hasExplicitProtocolV2Payload(fwBinaryMap);
507
- const needsRemoteBootResources =
508
- !this.params.bootResourcesBinary &&
509
- !!this.params.targetsToUpdate?.includes('boot_resources');
515
+ const needsRemoteBootResources = !!this.params.targetsToUpdate?.includes('boot_resources');
510
516
  if (needsRemoteFirmware || needsRemoteResources || needsRemoteBootResources) {
511
517
  // Remote updates must use a freshly fetched config before any reboot or file write.
512
- await DataManager.forceReloadData();
518
+ await DataManager.forceReloadData({
519
+ requireResources: needsRemoteResources || needsRemoteBootResources,
520
+ });
513
521
  }
514
522
  if (needsRemoteFirmware) {
515
523
  const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
@@ -520,12 +528,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
520
528
  fwBinaryMap = remoteBinaries.fwBinaryMap;
521
529
  installItems = remoteBinaries.installItems;
522
530
  }
523
- bootResourcesInstallItem = await this.prepareProtocolV2BootResources();
524
- if (bootResourcesInstallItem) {
525
- installItems = [
526
- bootResourcesInstallItem,
527
- ...(installItems ?? this.buildProtocolV2InstallItems({ bootloaderBinary, fwBinaryMap })),
528
- ];
531
+ const bootResourceFiles = await this.prepareProtocolV2BootResources();
532
+ if (bootResourceFiles?.length) {
533
+ resourceBundles = [...(resourceBundles ?? []), ...bootResourceFiles];
529
534
  }
530
535
  if (!needsRemoteResources) {
531
536
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
@@ -554,7 +559,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
554
559
 
555
560
  if (needsRemoteResources) {
556
561
  try {
557
- resourceBundles = await this.prepareProtocolV2ResourceBundles();
562
+ const stableResources = await this.prepareProtocolV2ResourceBundles();
563
+ resourceBundles = [...(resourceBundles ?? []), ...(stableResources ?? [])];
558
564
  this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
559
565
  } catch (err) {
560
566
  if (enteredBootloader) {
@@ -607,7 +613,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
607
613
  private assertProtocolV2DeviceInfoIdentity(deviceInfo: ProtocolV2DeviceInfo) {
608
614
  assertProtocolV2ReconnectIdentity(
609
615
  this.protocolV2ExpectedSerialNumber,
610
- this.getProtocolV2SerialNumber(deviceInfo)
616
+ this.getProtocolV2SerialNumber(deviceInfo),
617
+ this.protocolV2ExpectedPath,
618
+ this.device.originalDescriptor.path
611
619
  );
612
620
  }
613
621
 
@@ -627,8 +635,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
627
635
  private async captureProtocolV2PhysicalIdentity() {
628
636
  const deviceInfo = await this.requestProtocolV2PhysicalIdentity();
629
637
  const serialNumber = this.getProtocolV2SerialNumber(deviceInfo);
630
- assertProtocolV2ReconnectIdentity(serialNumber, serialNumber);
638
+ const path = this.device.originalDescriptor.path?.trim() || undefined;
639
+ assertProtocolV2ReconnectIdentity(serialNumber, serialNumber, path, path);
631
640
  this.protocolV2ExpectedSerialNumber = serialNumber;
641
+ this.protocolV2ExpectedPath = path;
632
642
  }
633
643
 
634
644
  private async verifyProtocolV2ReconnectIdentity() {
@@ -643,9 +653,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
643
653
 
644
654
  private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
645
655
  return (
646
- !!this.params.resourceBundleFiles?.length ||
656
+ !!this.params.resourceFiles?.length ||
647
657
  !!this.params.bootloaderBinary ||
648
- !!this.params.bootResourcesBinary ||
649
658
  fwBinaryMap.length > 0
650
659
  );
651
660
  }
@@ -795,68 +804,68 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
795
804
  };
796
805
  }
797
806
 
798
- private validateProtocolV2BootResourcesBinary(
799
- binary: ArrayBuffer,
800
- resource?: IProtocolV2BootResources
801
- ) {
802
- if (resource && !isProtocolV2ResourceFileValid(binary, resource)) {
803
- throw new Error('Pro2 boot resources file verification failed');
804
- }
805
- const header = parseProtocolV2OkppHeader(toProtocolV2Bytes(binary));
806
- if (!header || header.type !== 'CRAT') {
807
- throw new Error('Invalid Pro2 boot resources CRATE header');
808
- }
809
- if (resource) {
810
- const expectedPayloadHash = normalizeProtocolV2Hex(resource.payloadHash);
811
- const expectedHeaderHash = normalizeProtocolV2Hex(resource.headerHash);
812
- if (header.payloadHash !== expectedPayloadHash) {
813
- throw new Error('Pro2 boot resources payload hash mismatch');
814
- }
815
- if (header.headerHash !== expectedHeaderHash) {
816
- throw new Error('Pro2 boot resources header hash mismatch');
817
- }
818
- }
807
+ private getProtocolV2DeviceType(): EDeviceType.Pro2 | EDeviceType.Neo {
808
+ const deviceType = this.device.getCurrentDeviceType();
809
+ if (deviceType === EDeviceType.Pro2 || deviceType === EDeviceType.Neo) return deviceType;
810
+ throw new Error(`Unsupported Protocol V2 device type: ${deviceType}`);
819
811
  }
820
812
 
821
- private async prepareProtocolV2BootResources(): Promise<ProtocolV2InstallItem | undefined> {
822
- let binary = this.params.bootResourcesBinary;
823
- let resource: IProtocolV2BootResources | undefined;
824
-
825
- if (!binary) {
826
- if (!this.params.targetsToUpdate?.includes('boot_resources')) {
827
- return undefined;
828
- }
829
- resource = DataManager.getProtocolV2BootResources();
830
- if (!resource) {
831
- throw new Error('Missing Pro2 boot resources configuration');
813
+ private async prepareProtocolV2BootResources(): Promise<
814
+ ProtocolV2ResourceBundleBinary[] | undefined
815
+ > {
816
+ if (!this.params.targetsToUpdate?.includes('boot_resources')) return undefined;
817
+ const resource = DataManager.getProtocolV2BootResources(this.getProtocolV2DeviceType());
818
+ if (!resource) throw new Error('Missing Protocol V2 boot resources configuration');
819
+
820
+ const files: ProtocolV2ResourceBundleBinary[] = [];
821
+ for (const file of resource.files) {
822
+ Log.log(`[FirmwareUpdateV4] downloading boot resource ${file.devicePath}`);
823
+ const { binary } = await getSysResourceBinary(file.url);
824
+ if (!isProtocolV2ResourceFileValid(binary, file)) {
825
+ throw new Error(`Boot resource file verification failed: ${file.devicePath}`);
832
826
  }
833
- Log.log('[FirmwareUpdateV4] downloading Pro2 boot resources CRATE');
834
- ({ binary } = await getSysResourceBinary(resource.url));
827
+ files.push({
828
+ name: file.name ?? file.devicePath.split('/').pop() ?? file.devicePath,
829
+ binary,
830
+ devicePath: file.devicePath,
831
+ });
835
832
  }
836
-
837
- this.validateProtocolV2BootResourcesBinary(binary, resource);
838
- return {
839
- fileName: PROTOCOL_V2_BOOT_RESOURCES_FILE_NAME,
840
- binary,
841
- targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
842
- kind: 'boot_resources',
843
- };
833
+ return files;
844
834
  }
845
835
 
846
- private prepareExplicitProtocolV2ResourceBundles(): ProtocolV2ResourceBundleBinary[] | undefined {
847
- if (!this.params.resourceBundleFiles?.length) return undefined;
836
+ private prepareExplicitProtocolV2ResourceFiles(): ProtocolV2ResourceBundleBinary[] | undefined {
837
+ const files = this.params.resourceFiles ?? [];
838
+ if (!files?.length) return undefined;
848
839
 
849
- return this.params.resourceBundleFiles.map((file, index) => {
840
+ const prepared = files.map((file, index) => {
850
841
  const devicePath = validateProtocolV2FilesystemPath(
851
842
  file.devicePath,
852
- `resourceBundleFiles[${index}].devicePath`
843
+ `resourceFiles[${index}].devicePath`
853
844
  );
845
+ const descriptor = file as typeof file &
846
+ Pick<Partial<IProtocolV2ResourceFile>, 'size' | 'fileHash'>;
847
+ if (descriptor.size !== undefined && descriptor.size !== file.binary.byteLength) {
848
+ throw new Error(`resourceFiles[${index}] size mismatch`);
849
+ }
850
+ if (
851
+ descriptor.fileHash &&
852
+ !isProtocolV2ResourceFileValid(file.binary, {
853
+ size: file.binary.byteLength,
854
+ fileHash: descriptor.fileHash,
855
+ })
856
+ ) {
857
+ throw new Error(`resourceFiles[${index}] SHA-256 mismatch`);
858
+ }
854
859
  return {
855
860
  name: devicePath.split('/').pop() ?? devicePath,
856
861
  binary: file.binary,
857
862
  devicePath,
858
863
  };
859
864
  });
865
+ if (new Set(prepared.map(file => file.devicePath)).size !== prepared.length) {
866
+ throw new Error('resourceFiles contain duplicate devicePath values');
867
+ }
868
+ return prepared;
860
869
  }
861
870
 
862
871
  private async prepareProtocolV2ResourceBundles(): Promise<
@@ -866,7 +875,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
866
875
  return undefined;
867
876
  }
868
877
 
869
- const resources = DataManager.getProtocolV2Resources();
878
+ const resources = DataManager.getProtocolV2Resources(this.getProtocolV2DeviceType());
870
879
  if (!resources?.length) {
871
880
  throw new Error('Missing Pro2 stable resource configuration');
872
881
  }
@@ -886,7 +895,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
886
895
  forced: this.params.forcedUpdateRes,
887
896
  });
888
897
  Log.log(
889
- `[FirmwareUpdateV4] Pro2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
898
+ `[FirmwareUpdateV4] Protocol V2 resource plan mode=bootloader-recovery status=${plan.status} count=${plan.resources.length}`
890
899
  );
891
900
 
892
901
  const bundles: ProtocolV2ResourceBundleBinary[] = [];
@@ -926,7 +935,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
926
935
  let processedSize = 0;
927
936
  for (const bundle of bundles) {
928
937
  Log.log(
929
- `[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
938
+ `[FirmwareUpdateV4] syncing resource ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
930
939
  );
931
940
  processedSize = await this.protocolV2CommonUpdateProcess({
932
941
  payload: bundle.binary,
@@ -938,7 +947,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
938
947
 
939
948
  const elapsedMs = Date.now() - transferStartTime;
940
949
  Log.log(
941
- `[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
950
+ `[FirmwareUpdateV4] resource sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
942
951
  elapsedMs / 1000
943
952
  ).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
944
953
  );
@@ -956,7 +965,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
956
965
  }
957
966
 
958
967
  private isProtocolV2RomloaderMode() {
959
- if (!this.device.isProtocolV2() || this.device.getCurrentDeviceType() !== EDeviceType.Pro2) {
968
+ const deviceType = this.device.getCurrentDeviceType();
969
+ if (
970
+ !this.device.isProtocolV2() ||
971
+ (deviceType !== EDeviceType.Pro2 && deviceType !== EDeviceType.Neo)
972
+ ) {
960
973
  return false;
961
974
  }
962
975
  if (typeof this.device.isRomloader === 'function') {
@@ -1112,9 +1125,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1112
1125
  totalSize = resourceTransfer.totalSize;
1113
1126
  }
1114
1127
 
1115
- // Skip staging and installation when the update contains RESC bundles only.
1128
+ // Skip staging and installation when the update contains resource files only.
1116
1129
  if (orderedInstallItems.length === 0) {
1117
- Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
1130
+ Log.log('[FirmwareUpdateV4] no firmware targets to install (resource files only)');
1118
1131
  if (totalSize > 0) {
1119
1132
  this.postProgressMessage(100, 'transferData');
1120
1133
  }
@@ -1537,9 +1550,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1537
1550
  DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
1538
1551
  devicesDescriptor.length === 1
1539
1552
  ) {
1553
+ const descriptor = devicesDescriptor[0];
1554
+ if (!this.protocolV2ExpectedSerialNumber && descriptor.path !== this.protocolV2ExpectedPath) {
1555
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1556
+ }
1540
1557
  this.device.updateDescriptor(
1541
1558
  {
1542
- ...devicesDescriptor[0],
1559
+ ...descriptor,
1543
1560
  protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
1544
1561
  },
1545
1562
  true
@@ -1556,18 +1573,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
1556
1573
  connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
1557
1574
  });
1558
1575
  const expectedSerialNumber = this.protocolV2ExpectedSerialNumber?.trim();
1559
- const identityMatch = expectedSerialNumber
1560
- ? deviceList.find(
1561
- candidate => candidate.getCurrentSerialNo?.().trim() === expectedSerialNumber
1562
- )
1563
- : undefined;
1564
- const singleCandidate = deviceList.length === 1 ? deviceList.at(0) : undefined;
1565
- const singleCandidateSerialNumber = singleCandidate?.getCurrentSerialNo?.().trim();
1566
- const reconnectDevice =
1567
- identityMatch ??
1568
- (singleCandidate && (!expectedSerialNumber || !singleCandidateSerialNumber)
1569
- ? singleCandidate
1570
- : undefined);
1576
+ const expectedPath = this.protocolV2ExpectedPath?.trim();
1577
+ const reconnectDevice = deviceList.find(candidate => {
1578
+ const candidateSerialNumber = candidate.getCurrentSerialNo?.().trim();
1579
+ if (expectedSerialNumber && candidateSerialNumber) {
1580
+ return candidateSerialNumber === expectedSerialNumber;
1581
+ }
1582
+ return !!expectedPath && candidate.getConnectId() === expectedPath;
1583
+ });
1571
1584
  if (!reconnectDevice) {
1572
1585
  throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
1573
1586
  }
@@ -84,6 +84,7 @@ export default class OpenWalletSession extends BaseMethod<OpenWalletSessionParam
84
84
 
85
85
  init() {
86
86
  this.useDevicePassphraseState = false;
87
+ this.unlockPolicy = 'unlock-before-run';
87
88
  this.skipForceUpdateCheck = true;
88
89
  this.params = normalizeParams(this.payload as unknown as Record<string, unknown>);
89
90
  this.payload.useEmptyPassphrase = this.params.mode === OpenWalletSessionMode.Standard;
@@ -67,7 +67,7 @@ export default class KaspaGetAddress extends BaseMethod<HardwareKaspaGetAddress[
67
67
  model_classic1s: {
68
68
  min: '3.12.0',
69
69
  },
70
- pro2: {
70
+ model_pro2: {
71
71
  min: '0.0.0',
72
72
  },
73
73
  };
@@ -190,7 +190,7 @@ export default class KaspaSignTransaction extends BaseMethod<KaspaSignTransactio
190
190
  model_classic1s: {
191
191
  min: '3.12.0',
192
192
  },
193
- pro2: {
193
+ model_pro2: {
194
194
  min: '0.0.0',
195
195
  },
196
196
  };
@@ -23,7 +23,7 @@ const baseVersionRange = {
23
23
  model_touch: {
24
24
  min: '4.3.0',
25
25
  },
26
- pro2: {
26
+ model_pro2: {
27
27
  min: '0.0.0',
28
28
  },
29
29
  };
@@ -36,7 +36,7 @@ const specialVersionRange: Record<string, DeviceFirmwareRange> = {
36
36
  model_touch: {
37
37
  min: '4.7.0',
38
38
  },
39
- pro2: {
39
+ model_pro2: {
40
40
  min: '0.0.0',
41
41
  },
42
42
  },
@@ -47,7 +47,7 @@ const specialVersionRange: Record<string, DeviceFirmwareRange> = {
47
47
  model_touch: {
48
48
  min: '4.9.0',
49
49
  },
50
- pro2: {
50
+ model_pro2: {
51
51
  min: '0.0.0',
52
52
  },
53
53
  },
@@ -17,7 +17,7 @@ import {
17
17
  } from '../../utils/pro2Nft';
18
18
  import { BaseMethod } from '../BaseMethod';
19
19
  import { invalidParameter } from '../helpers/filesystemValidation';
20
- import { isProtocolV2ResponseTimeout, writeProtocolV2File } from '../helpers/protocolV2FileWrite';
20
+ import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
21
21
 
22
22
  export type DeviceUploadNftParams = {
23
23
  image: Pro2NftImage;
@@ -115,17 +115,12 @@ export default class DeviceUploadNft extends BaseMethod<DeviceUploadNftParams> {
115
115
  }
116
116
 
117
117
  private async updateNft(basename: string) {
118
- const params = { file_name_no_ext: basename };
119
- const options = { timeoutMs: this.params.timeoutMs };
120
- try {
121
- return await this.device.commands.typedCall('NftUpdate', 'Success', params, options);
122
- } catch (error) {
123
- if (!isProtocolV2ResponseTimeout(error)) {
124
- throw error;
125
- }
126
- this.throwIfAborted();
127
- return this.device.commands.typedCall('NftUpdate', 'Success', params, options);
128
- }
118
+ return this.device.commands.typedCall(
119
+ 'NftUpdate',
120
+ 'Success',
121
+ { file_name_no_ext: basename },
122
+ { timeoutMs: this.params.timeoutMs }
123
+ );
129
124
  }
130
125
 
131
126
  async run(): Promise<DeviceUploadNftResponse> {
@@ -138,7 +138,6 @@ export function normalizeRebootType(value: RebootTypeInput | undefined): DeviceR
138
138
 
139
139
  // 与 firmware-pro2 的 proto_target_to_firmware_target 安装白名单保持一致。
140
140
  const INSTALLABLE_FIRMWARE_TARGET_IDS = new Set<number>([
141
- ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
142
141
  ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
143
142
  ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1,
144
143
  ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2,
@@ -34,7 +34,7 @@ export default class SolSignMessage extends BaseMethod<HardwareSolSignUnsafeMess
34
34
 
35
35
  getVersionRange() {
36
36
  return {
37
- pro2: {
37
+ model_pro2: {
38
38
  min: '0.0.0',
39
39
  },
40
40
  pro: {
@@ -40,7 +40,7 @@ export default class SolSignOffchainMessage extends BaseMethod<HardwareSolSignOf
40
40
 
41
41
  getVersionRange() {
42
42
  return {
43
- pro2: {
43
+ model_pro2: {
44
44
  min: '0.0.0',
45
45
  },
46
46
  pro: {
@@ -47,7 +47,7 @@ export default class SolSignTransaction extends BaseMethod<HardwareSolanaSignTx[
47
47
  getVersionRange() {
48
48
  if (this.existsVersionedTx()) {
49
49
  return {
50
- pro2: {
50
+ model_pro2: {
51
51
  min: '0.0.0',
52
52
  },
53
53
  model_mini: {
@@ -60,7 +60,7 @@ export default class SolSignTransaction extends BaseMethod<HardwareSolanaSignTx[
60
60
  }
61
61
 
62
62
  return {
63
- pro2: {
63
+ model_pro2: {
64
64
  min: '0.0.0',
65
65
  },
66
66
  classic: {
@@ -52,7 +52,7 @@ export default class SuiGetAddress extends BaseMethod<HardwareSuiGetAddress[]> {
52
52
 
53
53
  getVersionRange() {
54
54
  return {
55
- pro2: {
55
+ model_pro2: {
56
56
  min: '0.0.0',
57
57
  },
58
58
  model_mini: {
@@ -44,7 +44,7 @@ export default class SuiGetPublicKey extends BaseMethod<any> {
44
44
 
45
45
  getVersionRange() {
46
46
  return {
47
- pro2: {
47
+ model_pro2: {
48
48
  min: '0.0.0',
49
49
  },
50
50
  model_mini: {
@@ -34,7 +34,7 @@ export default class SuiSignMessage extends BaseMethod<HardwareSuiSignMessage> {
34
34
 
35
35
  getVersionRange() {
36
36
  return {
37
- pro2: {
37
+ model_pro2: {
38
38
  min: '0.0.0',
39
39
  },
40
40
  model_mini: {
@@ -43,7 +43,7 @@ export default class SuiSignTransaction extends BaseMethod<SuiSignTx> {
43
43
 
44
44
  getVersionRange() {
45
45
  return {
46
- pro2: {
46
+ model_pro2: {
47
47
  min: '0.0.0',
48
48
  },
49
49
  model_mini: {
@@ -61,7 +61,7 @@ export default class TonGetAddress extends BaseMethod<HardwareTonGetAddress[]> {
61
61
 
62
62
  getVersionRange() {
63
63
  return {
64
- pro2: {
64
+ model_pro2: {
65
65
  min: '0.0.0',
66
66
  },
67
67
  model_touch: {
@@ -89,7 +89,7 @@ export default class TonSignMessage extends BaseMethod<HardwareTonSignMessage> {
89
89
 
90
90
  getVersionRange() {
91
91
  return {
92
- pro2: {
92
+ model_pro2: {
93
93
  min: '0.0.0',
94
94
  },
95
95
  model_touch: {
@@ -48,7 +48,7 @@ export default class TonSignProof extends BaseMethod<HardwareTonSignProof> {
48
48
 
49
49
  getVersionRange() {
50
50
  return {
51
- pro2: {
51
+ model_pro2: {
52
52
  min: '0.0.0',
53
53
  },
54
54
  model_touch: {
@@ -46,7 +46,7 @@ export default class TronSignMessage extends BaseMethod<HardwareTronSignMessage>
46
46
 
47
47
  getVersionRange() {
48
48
  return {
49
- pro2: {
49
+ model_pro2: {
50
50
  min: '0.0.0',
51
51
  },
52
52
  model_mini: {
@@ -57,7 +57,7 @@ export default class TronSignMessage extends BaseMethod<HardwareTronSignMessage>
57
57
 
58
58
  getMessageV2VersionRange() {
59
59
  return {
60
- pro2: {
60
+ model_pro2: {
61
61
  min: '0.0.0',
62
62
  },
63
63
  pro: {
@@ -152,7 +152,7 @@ export default class TronSignTransaction extends BaseMethod<TronSignTx> {
152
152
 
153
153
  getVersionRange() {
154
154
  return {
155
- pro2: {
155
+ model_pro2: {
156
156
  min: '0.0.0',
157
157
  },
158
158
  model_mini: {