@onekeyfe/hd-core 1.2.0-alpha.184 → 1.2.0-alpha.186

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.
@@ -51,6 +51,7 @@ import {
51
51
  getProtocolV2UnknownErrorText,
52
52
  isProtocolV2DeviceDisconnectedError,
53
53
  } from './protocol-v2/helpers';
54
+ import { isProtocolV2ResponseTimeout } from './helpers/protocolV2FileWrite';
54
55
  import {
55
56
  openFirmwareByteSource,
56
57
  readFirmwareByteSourceFully,
@@ -62,6 +63,7 @@ import {
62
63
  assertFirmwareUpdatePreparedPlanDeviceIdentity,
63
64
  validateFirmwareUpdatePreparedPlan,
64
65
  } from './firmware/FirmwareUpdatePreparedPlan';
66
+
65
67
  import type {
66
68
  FirmwareArtifactReference,
67
69
  FirmwareUpdateV4Params,
@@ -91,6 +93,7 @@ const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
91
93
  const PROTOCOL_V2_FIRMWARE_STATUS_RESPONSE_TIMEOUT = 15 * 1000;
92
94
  const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
93
95
  const PROTOCOL_V2_INSTALL_STATUS_INITIAL_DELAY = 1000;
96
+ const PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS = 4;
94
97
  const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
95
98
  const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
96
99
  const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
@@ -189,6 +192,11 @@ const getProtocolV2DeviceTransferProgress = (
189
192
  type ProtocolV2FirmwareUpdateStatusTarget = {
190
193
  target_id: number | string;
191
194
  status?: number | string;
195
+ progress_percent?: number;
196
+ phase_info?: {
197
+ phase: number | string;
198
+ progress_percent: number;
199
+ };
192
200
  payload_version?: number;
193
201
  path?: string;
194
202
  };
@@ -409,6 +417,18 @@ const normalizeProtocolV2TargetStatus = (
409
417
  return undefined;
410
418
  };
411
419
 
420
+ const PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE = new Map<
421
+ number | string,
422
+ 'prepare' | 'install' | 'verify'
423
+ >([
424
+ [0, 'prepare'],
425
+ [1, 'install'],
426
+ [2, 'verify'],
427
+ ['FW_MGMT_UPDATER_PHASE_PREPARE', 'prepare'],
428
+ ['FW_MGMT_UPDATER_PHASE_INSTALL', 'install'],
429
+ ['FW_MGMT_UPDATER_PHASE_VERIFY', 'verify'],
430
+ ]);
431
+
412
432
  const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
413
433
 
414
434
  const versionArrayToNumber = (version?: IVersionArray) => {
@@ -466,6 +486,11 @@ const toProtocolV2FiniteNumber = (value: unknown) => {
466
486
  return undefined;
467
487
  };
468
488
 
489
+ const normalizeProtocolV2ProgressPercent = (value: unknown) => {
490
+ const progress = toProtocolV2FiniteNumber(value);
491
+ return progress === undefined ? undefined : Math.min(100, Math.max(0, progress));
492
+ };
493
+
469
494
  export const isProtocolV2FirmwareFingerprintValid = (
470
495
  binary: ArrayBuffer | Uint8Array,
471
496
  fingerprint: string | undefined
@@ -2310,6 +2335,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2310
2335
  for (const target of matchingTargets) {
2311
2336
  const targetId = normalizeProtocolV2TargetId(target.target_id);
2312
2337
  const pathConflictsWithCurrentInstall =
2338
+ targetId !== undefined &&
2313
2339
  Boolean(target.path) &&
2314
2340
  Boolean(resolvedExpectedPaths.get(targetId)) &&
2315
2341
  target.path !== resolvedExpectedPaths.get(targetId) &&
@@ -2373,16 +2399,54 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2373
2399
  }
2374
2400
 
2375
2401
  if (expectedTargetIds.size > 0 && matchingTargets.length > 0) {
2376
- const hasInProgressTarget = matchingTargets.some(target =>
2377
- isProtocolV2TargetStatusInProgress(target.status)
2378
- );
2402
+ let detailedProgress = completedTargetIds.size * 100;
2403
+ let installProgressMetadata:
2404
+ | {
2405
+ installTargetId: number;
2406
+ installPhase: 'prepare' | 'install' | 'verify';
2407
+ installPhaseProgress: number;
2408
+ }
2409
+ | undefined;
2410
+ let hasInProgressTarget = false;
2411
+ matchingTargets.forEach(target => {
2412
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2413
+ if (
2414
+ targetId === undefined ||
2415
+ completedTargetIds.has(targetId) ||
2416
+ !isProtocolV2TargetStatusInProgress(target.status) ||
2417
+ (liveTargetIds && !liveTargetIds.has(targetId))
2418
+ ) {
2419
+ return;
2420
+ }
2421
+ hasInProgressTarget = true;
2422
+ detailedProgress += normalizeProtocolV2ProgressPercent(target.progress_percent) ?? 0;
2423
+ const installPhase = target.phase_info
2424
+ ? PROTOCOL_V2_INSTALL_PHASE_BY_DECODED_VALUE.get(target.phase_info.phase)
2425
+ : undefined;
2426
+ const installPhaseProgress = normalizeProtocolV2ProgressPercent(
2427
+ target.phase_info?.progress_percent
2428
+ );
2429
+ if (installPhase && installPhaseProgress !== undefined && !installProgressMetadata) {
2430
+ installProgressMetadata = {
2431
+ installTargetId: targetId,
2432
+ installPhase,
2433
+ installPhaseProgress,
2434
+ };
2435
+ }
2436
+ });
2379
2437
  const completedProgress = Math.floor(
2380
2438
  (completedTargetIds.size / expectedTargetIds.size) * 100
2381
2439
  );
2382
- // The protocol exposes no per-target percentage, so report coarse progress by
2383
- // completed targets and use 1% once work starts to keep the UI responsive.
2384
- const progress = Math.min(99, Math.max(completedProgress, hasInProgressTarget ? 1 : 0));
2385
- this.postProgressMessage(progress, 'installingFirmware');
2440
+ const reportedProgress = Math.floor(detailedProgress / expectedTargetIds.size);
2441
+ const progress = Math.min(
2442
+ 99,
2443
+ Math.max(completedProgress, reportedProgress, hasInProgressTarget ? 1 : 0)
2444
+ );
2445
+ if (installProgressMetadata) {
2446
+ this.postProgressMessage(progress, 'installingFirmware', installProgressMetadata);
2447
+ } else {
2448
+ this.postProgressMessage(progress, 'installingFirmware');
2449
+ }
2386
2450
  }
2387
2451
 
2388
2452
  return false;
@@ -2453,6 +2517,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2453
2517
  ) {
2454
2518
  const expectedTargetIds = new Set(targets.map(target => target.target_id));
2455
2519
  const expectedPaths = new Map(targets.map(target => [target.target_id, target.path]));
2520
+ const isBleCoprocessorInstall =
2521
+ this.isBleReconnect() &&
2522
+ expectedTargetIds.has(ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR);
2456
2523
  const startTime = Date.now();
2457
2524
  let lastError: unknown;
2458
2525
  // The loader may release either USB or BLE as installation starts. Recovery reconnects
@@ -2461,6 +2528,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2461
2528
  this.protocolV2InstallNeedsReconnect = false;
2462
2529
  let deviceInfo: ProtocolV2DeviceInfo | undefined;
2463
2530
  let bleInstallLinkReady = false;
2531
+ let installStatusDisconnectObserved = false;
2532
+ let installReconnectObserved = false;
2533
+ let finishedStatusSnapshotKey: string | undefined;
2534
+ let finishedStatusSnapshotPolls = 0;
2535
+ let fastFinishedInstallProgressReported = false;
2464
2536
  let installEvidenceObserved = this.protocolV2InstallTerminalSuccessObserved;
2465
2537
  let currentInstallStatusObserved = false;
2466
2538
  const liveTargetIds = new Set<number>();
@@ -2477,6 +2549,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2477
2549
  deviceInfo = isBleInstallReconnect
2478
2550
  ? undefined
2479
2551
  : await this.verifyProtocolV2ReconnectIdentity();
2552
+ if (isBleInstallReconnect && installStatusDisconnectObserved) {
2553
+ installReconnectObserved = true;
2554
+ }
2480
2555
  shouldReconnect = false;
2481
2556
  }
2482
2557
  let currentDeviceInfo = deviceInfo;
@@ -2487,6 +2562,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2487
2562
  {
2488
2563
  fields: {
2489
2564
  status: true,
2565
+ progress_percent: true,
2566
+ phase_info: true,
2490
2567
  payload_version: true,
2491
2568
  path: true,
2492
2569
  },
@@ -2516,8 +2593,74 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2516
2593
  const targetId = normalizeProtocolV2TargetId(target.target_id);
2517
2594
  return targetId !== undefined && expectedTargetIds.has(targetId);
2518
2595
  });
2596
+ const matchingFinishedTargetIds = new Set<number>();
2597
+ const matchingFinishedStatus = matchingStatusTargets
2598
+ .map(target => {
2599
+ const targetId = normalizeProtocolV2TargetId(target.target_id);
2600
+ const expectedPath = targetId === undefined ? undefined : expectedPaths.get(targetId);
2601
+ const expectedVersion =
2602
+ targetId === undefined
2603
+ ? undefined
2604
+ : this.getExpectedProtocolV2TargetVersion(targetId);
2605
+ const payloadVersion = toProtocolV2FiniteNumber(target.payload_version);
2606
+ if (
2607
+ targetId === undefined ||
2608
+ matchingFinishedTargetIds.has(targetId) ||
2609
+ !isProtocolV2TargetStatusFinished(target.status) ||
2610
+ (Boolean(target.path) && Boolean(expectedPath) && target.path !== expectedPath) ||
2611
+ (expectedVersion !== undefined &&
2612
+ payloadVersion !== undefined &&
2613
+ payloadVersion !== expectedVersion)
2614
+ ) {
2615
+ return undefined;
2616
+ }
2617
+ matchingFinishedTargetIds.add(targetId);
2618
+ return [targetId, target.path ?? null, payloadVersion ?? null] as const;
2619
+ })
2620
+ .filter((target): target is readonly [number, string | null, number | null] => !!target)
2621
+ .sort(([leftTargetId], [rightTargetId]) => leftTargetId - rightTargetId);
2622
+ const hasCompleteMatchingFinishedStatus =
2623
+ matchingFinishedStatus.length === expectedTargetIds.size &&
2624
+ matchingFinishedTargetIds.size === expectedTargetIds.size &&
2625
+ expectedTargetIds.size > 0;
2626
+ if (
2627
+ requireCurrentInstallStatus &&
2628
+ installReconnectObserved &&
2629
+ hasCompleteMatchingFinishedStatus
2630
+ ) {
2631
+ const nextSnapshotKey = JSON.stringify(matchingFinishedStatus);
2632
+ if (nextSnapshotKey === finishedStatusSnapshotKey) {
2633
+ finishedStatusSnapshotPolls += 1;
2634
+ } else {
2635
+ finishedStatusSnapshotKey = nextSnapshotKey;
2636
+ finishedStatusSnapshotPolls = 1;
2637
+ }
2638
+ } else {
2639
+ finishedStatusSnapshotKey = undefined;
2640
+ finishedStatusSnapshotPolls = 0;
2641
+ }
2642
+ if (
2643
+ requireCurrentInstallStatus &&
2644
+ installReconnectObserved &&
2645
+ hasCompleteMatchingFinishedStatus &&
2646
+ !currentInstallStatusObserved &&
2647
+ !this.protocolV2InstallTerminalSuccessObserved &&
2648
+ !fastFinishedInstallProgressReported
2649
+ ) {
2650
+ // A fast BLE install may finish while disconnected, so IN_PROGRESS can be hidden.
2651
+ // A status response timeout alone is not evidence that the install was accepted.
2652
+ this.postProgressMessage(1, 'installingFirmware');
2653
+ fastFinishedInstallProgressReported = true;
2654
+ }
2655
+ const finishedAfterDisconnectObserved =
2656
+ finishedStatusSnapshotPolls >= PROTOCOL_V2_INSTALL_FINISHED_AFTER_DISCONNECT_POLLS;
2519
2657
  const hasCurrentInstallEvidence =
2520
- currentInstallStatusObserved || this.protocolV2InstallTerminalSuccessObserved;
2658
+ currentInstallStatusObserved ||
2659
+ this.protocolV2InstallTerminalSuccessObserved ||
2660
+ finishedAfterDisconnectObserved;
2661
+ if (hasCurrentInstallEvidence) {
2662
+ installEvidenceObserved = true;
2663
+ }
2521
2664
  const shouldVerifyTargetCompletion =
2522
2665
  !requireCurrentInstallStatus || hasCurrentInstallEvidence;
2523
2666
  if (
@@ -2526,7 +2669,9 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2526
2669
  statusTargets,
2527
2670
  expectedTargetIds,
2528
2671
  expectedPaths,
2529
- requireCurrentInstallStatus && !this.protocolV2InstallTerminalSuccessObserved
2672
+ requireCurrentInstallStatus &&
2673
+ !this.protocolV2InstallTerminalSuccessObserved &&
2674
+ !finishedAfterDisconnectObserved
2530
2675
  ? liveTargetIds
2531
2676
  : undefined
2532
2677
  )
@@ -2642,6 +2787,15 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
2642
2787
  );
2643
2788
  }
2644
2789
  } else {
2790
+ if (
2791
+ isBleCoprocessorInstall &&
2792
+ isProtocolV2DeviceDisconnectedError(error) &&
2793
+ !isProtocolV2ResponseTimeout(error)
2794
+ ) {
2795
+ installStatusDisconnectObserved = true;
2796
+ finishedStatusSnapshotKey = undefined;
2797
+ finishedStatusSnapshotPolls = 0;
2798
+ }
2645
2799
  shouldReconnect = true;
2646
2800
  deviceInfo = undefined;
2647
2801
  bleInstallLinkReady = false;
@@ -3048,13 +3202,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
3048
3202
  private async protocolV2Reboot(rebootType: DeviceRebootType) {
3049
3203
  const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
3050
3204
  try {
3051
- const res = await typedCall('DeviceReboot', 'Success', {
3052
- reboot_type: rebootType,
3053
- });
3205
+ const res = await typedCall(
3206
+ 'DeviceReboot',
3207
+ 'Success',
3208
+ {
3209
+ reboot_type: rebootType,
3210
+ },
3211
+ { timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT }
3212
+ );
3054
3213
  this.device.markProtocolV2Reboot(rebootType);
3055
3214
  return res.message;
3056
3215
  } catch (error) {
3057
- if (isProtocolV2DeviceDisconnectedError(error) || isProtocolV2ReconnectProbeError(error)) {
3216
+ if (
3217
+ isProtocolV2DeviceDisconnectedError(error) ||
3218
+ isProtocolV2ReconnectProbeError(error) ||
3219
+ isProtocolV2ResponseTimeout(error)
3220
+ ) {
3058
3221
  this.device.markProtocolV2Reboot(rebootType);
3059
3222
  return { message: 'Device rebooted successfully' };
3060
3223
  }
@@ -41,9 +41,15 @@ const FIRMWARE_UPDATE_CONFIRM = 'Firmware install confirmed';
41
41
  */
42
42
  export const BOOTLOADER_POLL_INITIALIZE_TIMEOUT_MS = 5000;
43
43
 
44
- type FirmwareTransferMetrics = Pick<
44
+ type FirmwareProgressMetadata = Pick<
45
45
  FirmwareProgress['payload'],
46
- 'transferredBytes' | 'totalBytes' | 'rateBytesPerSecond' | 'elapsedMs'
46
+ | 'transferredBytes'
47
+ | 'totalBytes'
48
+ | 'rateBytesPerSecond'
49
+ | 'elapsedMs'
50
+ | 'installTargetId'
51
+ | 'installPhase'
52
+ | 'installPhaseProgress'
47
53
  >;
48
54
 
49
55
  const isDeviceDisconnectedError = (error: unknown) => {
@@ -103,7 +109,7 @@ export class FirmwareUpdateBaseMethod<Params> extends BaseMethod<Params> {
103
109
  postProgressMessage = (
104
110
  progress: number,
105
111
  progressType: IFirmwareUpdateProgressType,
106
- metrics?: FirmwareTransferMetrics
112
+ metrics?: FirmwareProgressMetadata
107
113
  ) => {
108
114
  this.postMessage(
109
115
  createUiMessage(UI_REQUEST.FIRMWARE_PROGRESS, {
@@ -4,7 +4,13 @@ import { invalidParameter } from '../helpers/filesystemValidation';
4
4
  import type { DeviceFirmwareUpdateStatusGetParams } from './helpers';
5
5
  import type { DeviceFirmwareUpdateRecordFields } from '@onekeyfe/hd-transport';
6
6
 
7
- const DEVICE_FIRMWARE_UPDATE_STATUS_FIELDS = ['status', 'payload_version', 'path'] as const;
7
+ const DEVICE_FIRMWARE_UPDATE_STATUS_FIELDS = [
8
+ 'status',
9
+ 'progress_percent',
10
+ 'phase_info',
11
+ 'payload_version',
12
+ 'path',
13
+ ] as const;
8
14
 
9
15
  function normalizeStatusFields(
10
16
  fields: DeviceFirmwareUpdateStatusGetParams['fields']
@@ -1,4 +1,5 @@
1
1
  import { DeviceRebootType } from '@onekeyfe/hd-transport';
2
+ import { HardwareErrorCode } from '@onekeyfe/hd-shared';
2
3
 
3
4
  import { invalidParameter, validateNonEmptyString } from '../helpers/filesystemValidation';
4
5
  import { ProtocolV2FirmwareTargetType } from '../../protocols/protocol-v2/firmware';
@@ -103,6 +104,13 @@ export const getProtocolV2UnknownErrorText = (error: unknown) => {
103
104
  };
104
105
 
105
106
  export const isProtocolV2DeviceDisconnectedError = (error: unknown) => {
107
+ const errorCode =
108
+ error && typeof error === 'object'
109
+ ? (error as { errorCode?: number; code?: number }).errorCode ??
110
+ (error as { errorCode?: number; code?: number }).code
111
+ : undefined;
112
+ if (errorCode === HardwareErrorCode.BleDeviceDisconnected) return true;
113
+
106
114
  const message = getProtocolV2UnknownErrorText(error).toLowerCase();
107
115
  const compactMessage = message.replace(/\s+/g, '');
108
116
  return (
@@ -446,6 +446,9 @@
446
446
  "MessageType_DeviceCertificateSignature": 60423,
447
447
  "MessageType_DeviceCertificateSign": 60424,
448
448
  "MessageType_DeviceMiscUsbMscControl": 60440,
449
+ "MessageType_DeviceFindMyTokenState": 60450,
450
+ "MessageType_DeviceFindMyTokenUpdate": 60451,
451
+ "MessageType_DeviceFindMyTokenStateGet": 60452,
449
452
  "MessageType_DeviceInfoGet": 60600,
450
453
  "MessageType_DeviceInfo": 60601,
451
454
  "MessageType_DeviceStatusGet": 60602,
@@ -5404,6 +5407,10 @@
5404
5407
  "request_id": {
5405
5408
  "type": "bytes",
5406
5409
  "id": 5
5410
+ },
5411
+ "source_fingerprint": {
5412
+ "type": "uint32",
5413
+ "id": 6
5407
5414
  }
5408
5415
  }
5409
5416
  },
@@ -5539,6 +5546,14 @@
5539
5546
  "tx_type": {
5540
5547
  "type": "uint32",
5541
5548
  "id": 10
5549
+ },
5550
+ "expected_address": {
5551
+ "type": "bytes",
5552
+ "id": 12
5553
+ },
5554
+ "source_fingerprint": {
5555
+ "type": "uint32",
5556
+ "id": 13
5542
5557
  }
5543
5558
  }
5544
5559
  },
@@ -5619,6 +5634,14 @@
5619
5634
  "rule": "repeated",
5620
5635
  "type": "EthereumAccessListOneKey",
5621
5636
  "id": 11
5637
+ },
5638
+ "expected_address": {
5639
+ "type": "bytes",
5640
+ "id": 12
5641
+ },
5642
+ "source_fingerprint": {
5643
+ "type": "uint32",
5644
+ "id": 13
5622
5645
  }
5623
5646
  }
5624
5647
  },
@@ -5795,6 +5818,10 @@
5795
5818
  "chain_id": {
5796
5819
  "type": "uint64",
5797
5820
  "id": 3
5821
+ },
5822
+ "source_fingerprint": {
5823
+ "type": "uint32",
5824
+ "id": 4
5798
5825
  }
5799
5826
  }
5800
5827
  },
@@ -9474,6 +9501,10 @@
9474
9501
  "rule": "required",
9475
9502
  "type": "bytes",
9476
9503
  "id": 2
9504
+ },
9505
+ "source_fingerprint": {
9506
+ "type": "uint32",
9507
+ "id": 3
9477
9508
  }
9478
9509
  }
9479
9510
  },
@@ -9518,6 +9549,10 @@
9518
9549
  "application_domain": {
9519
9550
  "type": "bytes",
9520
9551
  "id": 5
9552
+ },
9553
+ "source_fingerprint": {
9554
+ "type": "uint32",
9555
+ "id": 6
9521
9556
  }
9522
9557
  }
9523
9558
  },
@@ -9535,6 +9570,10 @@
9535
9570
  "rule": "required",
9536
9571
  "type": "bytes",
9537
9572
  "id": 2
9573
+ },
9574
+ "source_fingerprint": {
9575
+ "type": "uint32",
9576
+ "id": 3
9538
9577
  }
9539
9578
  }
9540
9579
  },
@@ -11764,6 +11803,27 @@
11764
11803
  }
11765
11804
  }
11766
11805
  },
11806
+ "DeviceFindMyTokenUpdate": {
11807
+ "fields": {
11808
+ "token": {
11809
+ "rule": "required",
11810
+ "type": "bytes",
11811
+ "id": 1
11812
+ }
11813
+ }
11814
+ },
11815
+ "DeviceFindMyTokenStateGet": {
11816
+ "fields": {}
11817
+ },
11818
+ "DeviceFindMyTokenState": {
11819
+ "fields": {
11820
+ "burned": {
11821
+ "rule": "required",
11822
+ "type": "bool",
11823
+ "id": 1
11824
+ }
11825
+ }
11826
+ },
11767
11827
  "DeviceFactoryAck": {
11768
11828
  "values": {
11769
11829
  "FACTORY_ACK_SUCCESS": 0,
@@ -11893,6 +11953,13 @@
11893
11953
  "FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS": 10
11894
11954
  }
11895
11955
  },
11956
+ "DeviceFirmwareUpdatePhase": {
11957
+ "values": {
11958
+ "FW_MGMT_UPDATER_PHASE_PREPARE": 0,
11959
+ "FW_MGMT_UPDATER_PHASE_INSTALL": 1,
11960
+ "FW_MGMT_UPDATER_PHASE_VERIFY": 2
11961
+ }
11962
+ },
11896
11963
  "DeviceFirmwareTarget": {
11897
11964
  "fields": {
11898
11965
  "target_id": {
@@ -11917,7 +11984,26 @@
11917
11984
  }
11918
11985
  },
11919
11986
  "DeviceFirmwareUpdateRequest": {
11920
- "fields": {}
11987
+ "fields": {
11988
+ "reboot_after_update": {
11989
+ "type": "bool",
11990
+ "id": 1
11991
+ }
11992
+ }
11993
+ },
11994
+ "DeviceFirmwareUpdatePhaseInfo": {
11995
+ "fields": {
11996
+ "phase": {
11997
+ "rule": "required",
11998
+ "type": "DeviceFirmwareUpdatePhase",
11999
+ "id": 1
12000
+ },
12001
+ "progress_percent": {
12002
+ "rule": "required",
12003
+ "type": "uint32",
12004
+ "id": 2
12005
+ }
12006
+ }
11921
12007
  },
11922
12008
  "DeviceFirmwareUpdateRecord": {
11923
12009
  "fields": {
@@ -11930,6 +12016,14 @@
11930
12016
  "type": "DeviceFirmwareUpdateTaskStatus",
11931
12017
  "id": 10
11932
12018
  },
12019
+ "progress_percent": {
12020
+ "type": "uint32",
12021
+ "id": 11
12022
+ },
12023
+ "phase_info": {
12024
+ "type": "DeviceFirmwareUpdatePhaseInfo",
12025
+ "id": 12
12026
+ },
11933
12027
  "payload_version": {
11934
12028
  "type": "uint32",
11935
12029
  "id": 20
@@ -11946,6 +12040,14 @@
11946
12040
  "type": "bool",
11947
12041
  "id": 10
11948
12042
  },
12043
+ "progress_percent": {
12044
+ "type": "bool",
12045
+ "id": 11
12046
+ },
12047
+ "phase_info": {
12048
+ "type": "bool",
12049
+ "id": 12
12050
+ },
11949
12051
  "payload_version": {
11950
12052
  "type": "bool",
11951
12053
  "id": 20
@@ -12713,6 +12815,10 @@
12713
12815
  "text_id": {
12714
12816
  "type": "uint32",
12715
12817
  "id": 3
12818
+ },
12819
+ "text_arg": {
12820
+ "type": "string",
12821
+ "id": 4
12716
12822
  }
12717
12823
  }
12718
12824
  },
@@ -12780,6 +12886,10 @@
12780
12886
  "title_id": {
12781
12887
  "type": "uint32",
12782
12888
  "id": 8
12889
+ },
12890
+ "title_arg": {
12891
+ "type": "string",
12892
+ "id": 9
12783
12893
  }
12784
12894
  }
12785
12895
  },
@@ -189,6 +189,9 @@ export interface FirmwareProgress {
189
189
  totalBytes?: number;
190
190
  rateBytesPerSecond?: number;
191
191
  elapsedMs?: number;
192
+ installTargetId?: number;
193
+ installPhase?: 'prepare' | 'install' | 'verify';
194
+ installPhaseProgress?: number;
192
195
  };
193
196
  }
194
197
 
@@ -77,7 +77,8 @@ export type IProtocolV2Resources = {
77
77
  /** STM32 firmware config */
78
78
  export type IFirmwareReleaseInfo = {
79
79
  required: boolean;
80
- url: string;
80
+ /** Legacy release artifact URL. Protocol V2 releases use component URLs. */
81
+ url?: string;
81
82
  /**
82
83
  * Firmware type (bitcoinonly or universal)
83
84
  * This field is not present in the remote config, but will be inferred from the firmware field name
@@ -106,7 +107,8 @@ export type IFirmwareReleaseInfo = {
106
107
  bootloaderChangelog?: {
107
108
  [k in ILocale]: string;
108
109
  };
109
- fingerprint: string;
110
+ /** Legacy release artifact SHA-256. Protocol V2 releases use component fingerprints. */
111
+ fingerprint?: string;
110
112
  expectedSize?: number;
111
113
  version: IVersionArray;
112
114
  changelog: {