@onekeyfe/hd-core 1.2.0-alpha.107 → 1.2.0-alpha.109

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 (74) hide show
  1. package/__tests__/DeviceCommands.test.ts +140 -26
  2. package/__tests__/base64Data.test.ts +70 -0
  3. package/__tests__/device-lifecycle-events.test.ts +113 -2
  4. package/__tests__/device-pool-state.test.ts +25 -0
  5. package/__tests__/device-state-mapper.test.ts +4 -4
  6. package/__tests__/device-utils.test.ts +1 -1
  7. package/__tests__/deviceSettings.test.ts +0 -1
  8. package/__tests__/deviceUploadNft.test.ts +33 -4
  9. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +125 -0
  10. package/__tests__/get-device-state.test.ts +60 -21
  11. package/__tests__/logBlockEvent.test.ts +13 -1
  12. package/__tests__/protocol-v2-resources.test.ts +8 -0
  13. package/__tests__/protocol-v2.test.ts +821 -227
  14. package/__tests__/refresh-device-state.test.ts +3 -1
  15. package/__tests__/resourceBase64Boundary.test.ts +48 -0
  16. package/__tests__/ton-sign-message.test.ts +84 -0
  17. package/dist/api/FirmwareUpdateV4.d.ts +7 -3
  18. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  19. package/dist/api/UploadPortfolio.d.ts +1 -1
  20. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  21. package/dist/api/helpers/base64Data.d.ts +16 -0
  22. package/dist/api/helpers/base64Data.d.ts.map +1 -0
  23. package/dist/api/protocol-v2/DeviceInfoGet.d.ts +1 -1
  24. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -1
  25. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +2 -3
  26. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  27. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts +2 -3
  28. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  29. package/dist/api/ton/TonSignMessage.d.ts.map +1 -1
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/device/Device.d.ts +7 -2
  32. package/dist/device/Device.d.ts.map +1 -1
  33. package/dist/device/DeviceCommands.d.ts.map +1 -1
  34. package/dist/device/DevicePool.d.ts.map +1 -1
  35. package/dist/events/logBlockEvent.d.ts.map +1 -1
  36. package/dist/index.d.ts +16 -34
  37. package/dist/index.js +506 -329
  38. package/dist/protocols/protocol-v2/features.d.ts +13 -5
  39. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  40. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  41. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  42. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  43. package/dist/types/api/protocolV2.d.ts +1 -1
  44. package/dist/types/api/protocolV2.d.ts.map +1 -1
  45. package/dist/types/settings.d.ts +4 -19
  46. package/dist/types/settings.d.ts.map +1 -1
  47. package/dist/utils/patch.d.ts +1 -1
  48. package/dist/utils/patch.d.ts.map +1 -1
  49. package/dist/utils/pro2Nft.d.ts +7 -0
  50. package/dist/utils/pro2Nft.d.ts.map +1 -1
  51. package/package.json +6 -4
  52. package/src/api/FirmwareUpdateV4.ts +307 -245
  53. package/src/api/UploadPortfolio.ts +9 -2
  54. package/src/api/helpers/base64Data.ts +85 -0
  55. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +1 -1
  56. package/src/api/protocol-v2/DeviceInfoGet.ts +3 -3
  57. package/src/api/protocol-v2/DeviceUploadNft.ts +56 -8
  58. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +37 -18
  59. package/src/api/ton/TonSignMessage.ts +5 -3
  60. package/src/core/index.ts +6 -2
  61. package/src/data/messages/messages-protocol-v2.json +31 -14
  62. package/src/device/Device.ts +53 -31
  63. package/src/device/DeviceCommands.ts +4 -14
  64. package/src/device/DevicePool.ts +6 -2
  65. package/src/device/DeviceStateMapper.ts +15 -15
  66. package/src/deviceProfile/buildDeviceFeatures.ts +3 -3
  67. package/src/events/logBlockEvent.ts +14 -1
  68. package/src/protocols/protocol-v2/features.ts +22 -5
  69. package/src/protocols/protocol-v2/index.ts +2 -0
  70. package/src/protocols/protocol-v2/resources.ts +9 -46
  71. package/src/types/api/protocolV2.ts +1 -1
  72. package/src/types/settings.ts +4 -19
  73. package/src/utils/deviceSettings.ts +1 -1
  74. package/src/utils/pro2Nft.ts +31 -8
@@ -61,6 +61,7 @@ import {
61
61
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
62
62
  type ProtocolV2RuntimeMode,
63
63
  getProtocolV2RuntimeMode,
64
+ isLegacyProtocolV2ProtocolInfo,
64
65
  requestProtocolV2DeviceInfo,
65
66
  requestProtocolV2DeviceStatus,
66
67
  requestProtocolV2ProtocolInfo,
@@ -485,6 +486,10 @@ export class Device extends EventEmitter {
485
486
  }
486
487
 
487
488
  this.commands = new DeviceCommands(this, this.mainId ?? '');
489
+ // Protocol V2 runtime metadata belongs to one active transport link. A
490
+ // successful acquire creates a fresh link/session, so never carry cached
491
+ // ProtocolInfo or pre-initialize state across that boundary.
492
+ this.invalidateProtocolV2RuntimeState();
488
493
  } catch (error) {
489
494
  if (options?.forceProtocolDetection) {
490
495
  this.originalDescriptor.protocolType = previousProtocol;
@@ -977,11 +982,7 @@ export class Device extends EventEmitter {
977
982
  });
978
983
  // The default request excludes SE/hash data and therefore uses basic scope.
979
984
  // Full version and verification data require getDeviceState({ scope: 'firmware' }).
980
- const features = await this.probeProtocolV2RuntimeState(
981
- deviceInfo,
982
- options?.protocolV2DeviceInfoTimeoutMs
983
- );
984
- Log.debug('Protocol V2 features:', features);
985
+ await this.probeProtocolV2RuntimeState(deviceInfo, options?.protocolV2DeviceInfoTimeoutMs);
985
986
  } catch (error) {
986
987
  Log.error('Protocol V2 initialization failed:', error);
987
988
  throw error;
@@ -1053,7 +1054,13 @@ export class Device extends EventEmitter {
1053
1054
  }
1054
1055
 
1055
1056
  if (refresh.has('status') && !initializedWithDeviceInfo) {
1056
- await this.probeProtocolV2RuntimeState(refreshedDeviceInfo);
1057
+ const cachedMode = this.state?.status.mode;
1058
+ await this.probeProtocolV2RuntimeState(refreshedDeviceInfo, undefined, {
1059
+ // Loader firmware does not support DeviceStatusGet. Renegotiate ProtocolInfo
1060
+ // during an explicit refresh so a device rebooted into application firmware
1061
+ // can leave the cached loader state.
1062
+ forceRuntimeContextRefresh: cachedMode === 'bootloader' || cachedMode === 'romloader',
1063
+ });
1057
1064
  }
1058
1065
 
1059
1066
  if (refresh.has('settings') && this.state?.status.mode === 'normal') {
@@ -1101,9 +1108,10 @@ export class Device extends EventEmitter {
1101
1108
  source,
1102
1109
  changedKeys: result.changedKeys,
1103
1110
  };
1104
- Log.debug('Device state patch committed', {
1111
+ Log.debug('Device state updated', {
1105
1112
  source,
1106
- keys: result.changedKeys,
1113
+ revision: result.revision,
1114
+ changedKeyCount: result.changedKeys.length,
1107
1115
  });
1108
1116
  this.emit(DEVICE.STATE, this, event);
1109
1117
  if (result.state.protocol === 'V1') {
@@ -1124,12 +1132,17 @@ export class Device extends EventEmitter {
1124
1132
  return this.features;
1125
1133
  }
1126
1134
 
1127
- async ensureProtocolV2RuntimeContext(timeoutMs?: number): Promise<ProtocolInfo> {
1135
+ async ensureProtocolV2RuntimeContext(
1136
+ timeoutMs?: number,
1137
+ options?: { forceRefresh?: boolean }
1138
+ ): Promise<ProtocolInfo> {
1128
1139
  const cachedProtocolInfo =
1129
- this.protocolV2RuntimeContext ??
1130
- (!this.protocolV2StateNeedsReload
1131
- ? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
1132
- : undefined);
1140
+ options?.forceRefresh === true
1141
+ ? undefined
1142
+ : this.protocolV2RuntimeContext ??
1143
+ (!this.protocolV2StateNeedsReload
1144
+ ? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
1145
+ : undefined);
1133
1146
  if (cachedProtocolInfo) {
1134
1147
  this.protocolV2RuntimeContext = cachedProtocolInfo;
1135
1148
  return cachedProtocolInfo;
@@ -1169,10 +1182,19 @@ export class Device extends EventEmitter {
1169
1182
  }
1170
1183
  }
1171
1184
 
1172
- async probeProtocolV2RuntimeState(deviceInfo?: ProtocolV2DeviceInfo, timeoutMs?: number) {
1173
- const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs);
1174
- const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
1185
+ async probeProtocolV2RuntimeState(
1186
+ deviceInfo?: ProtocolV2DeviceInfo,
1187
+ timeoutMs?: number,
1188
+ options?: {
1189
+ forceRuntimeContextRefresh?: boolean;
1190
+ }
1191
+ ) {
1192
+ const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs, {
1193
+ forceRefresh: options?.forceRuntimeContextRefresh,
1194
+ });
1175
1195
  const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
1196
+ const runtimeMode = getProtocolV2RuntimeMode(protocolInfo, runtimeDeviceInfo);
1197
+ const legacyProtocolInfo = isLegacyProtocolV2ProtocolInfo(protocolInfo);
1176
1198
  const protocolV2DeviceType = runtimeDeviceInfo
1177
1199
  ? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
1178
1200
  : this.getCurrentDeviceType();
@@ -1186,10 +1208,9 @@ export class Device extends EventEmitter {
1186
1208
  'Protocol V2 romloader mode is only supported for Pro2 and Neo.'
1187
1209
  );
1188
1210
  }
1189
- const deviceStatusSupported = supportsProtocolV2Message(
1190
- protocolInfo,
1191
- PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE
1192
- );
1211
+ const deviceStatusSupported =
1212
+ legacyProtocolInfo ||
1213
+ supportsProtocolV2Message(protocolInfo, PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE);
1193
1214
 
1194
1215
  if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
1195
1216
  return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
@@ -1250,8 +1271,7 @@ export class Device extends EventEmitter {
1250
1271
  });
1251
1272
  }
1252
1273
 
1253
- markTransportDisconnected() {
1254
- this.deviceAcquired = false;
1274
+ private invalidateProtocolV2RuntimeState() {
1255
1275
  if (!this.isProtocolV2()) return;
1256
1276
  this.protocolV2StateNeedsReload = true;
1257
1277
  this.protocolV2RuntimeContext = undefined;
@@ -1260,6 +1280,11 @@ export class Device extends EventEmitter {
1260
1280
  this.clearPreInitialized();
1261
1281
  }
1262
1282
 
1283
+ markTransportDisconnected() {
1284
+ this.deviceAcquired = false;
1285
+ this.invalidateProtocolV2RuntimeState();
1286
+ }
1287
+
1263
1288
  invalidateAfterWipe() {
1264
1289
  const deviceId = this.getCurrentDeviceId();
1265
1290
  if (deviceId) {
@@ -1269,10 +1294,7 @@ export class Device extends EventEmitter {
1269
1294
  if (this.originalDescriptor.path !== deviceId) {
1270
1295
  deviceWalletSessionStore.deleteDevice(this.originalDescriptor.path);
1271
1296
  }
1272
- this.protocolV2StateNeedsReload = true;
1273
- this.protocolV2RuntimeContext = undefined;
1274
- this.protocolV2RuntimeContextPromise = undefined;
1275
- this.protocolV2RuntimeContextRequestToken = undefined;
1297
+ this.invalidateProtocolV2RuntimeState();
1276
1298
  }
1277
1299
 
1278
1300
  this.passphraseState = undefined;
@@ -1284,11 +1306,7 @@ export class Device extends EventEmitter {
1284
1306
  markProtocolV2Reboot(rebootType: DeviceRebootType) {
1285
1307
  if (!this.isProtocolV2()) return;
1286
1308
 
1287
- this.protocolV2StateNeedsReload = true;
1288
- this.protocolV2RuntimeContext = undefined;
1289
- this.protocolV2RuntimeContextPromise = undefined;
1290
- this.protocolV2RuntimeContextRequestToken = undefined;
1291
- this.clearPreInitialized();
1309
+ this.invalidateProtocolV2RuntimeState();
1292
1310
  let loaderMode: 'bootloader' | 'romloader' | undefined;
1293
1311
  if (rebootType === DeviceRebootType.Bootloader) {
1294
1312
  loaderMode = 'bootloader';
@@ -1362,6 +1380,10 @@ export class Device extends EventEmitter {
1362
1380
  if (device.features) {
1363
1381
  this._updateFeatures(device.features);
1364
1382
  }
1383
+ // Adopting another Device instance's command channel also crosses an active
1384
+ // link boundary. Renegotiate runtime metadata on that channel instead of
1385
+ // retaining ProtocolInfo from the previous instance/session.
1386
+ this.invalidateProtocolV2RuntimeState();
1365
1387
  }
1366
1388
 
1367
1389
  async run(fn?: () => Promise<void>, options?: RunOptions) {
@@ -304,13 +304,6 @@ export class DeviceCommands {
304
304
  const promise = this.transport.call(this.mainId, type, msg ?? {}, options) as any;
305
305
  this.callPromise = promise;
306
306
  const res = await promise;
307
- if (!shouldReduceDebug) {
308
- LogCore.debug(
309
- '[DeviceCommands] [call] Received',
310
- res.type,
311
- getSafeTransportLogPayload(res.message, res.type)
312
- );
313
- }
314
307
  return res;
315
308
  } catch (error) {
316
309
  LogCore.debug('[DeviceCommands] [call] Received error', {
@@ -445,13 +438,10 @@ export class DeviceCommands {
445
438
  if (!shouldReduceDebugForCall(callType)) {
446
439
  Log.debug('_filterCommonTypes: ', {
447
440
  request: callType,
448
- response:
449
- callType === 'DeviceFirmwareUpdateStatusGet'
450
- ? {
451
- type: res.type,
452
- message: getSafeTransportLogPayload(res.message, res.type),
453
- }
454
- : res.type,
441
+ response: {
442
+ type: res.type,
443
+ message: getSafeTransportLogPayload(res.message, res.type),
444
+ },
455
445
  });
456
446
  }
457
447
  } catch (error) {
@@ -218,9 +218,13 @@ export class DevicePool extends EventEmitter {
218
218
  * Device.getDeviceState skips unsupported status/settings calls in loader mode.
219
219
  */
220
220
  static async _refreshProtocolV2DiscoveryState(device: Device) {
221
- await device.getDeviceState({ refreshSections: ['status'] });
221
+ await device.getDeviceState({
222
+ refreshSections: ['status'],
223
+ });
222
224
  try {
223
- await device.getDeviceState({ refreshSections: ['settings'] });
225
+ await device.getDeviceState({
226
+ refreshSections: ['settings'],
227
+ });
224
228
  } catch (error) {
225
229
  Log.debug('Unable to refresh Protocol V2 device label during discovery', error);
226
230
  }
@@ -267,11 +267,11 @@ export const mapProtocolV2DeviceInfoToState = (
267
267
  }
268
268
  : { mode },
269
269
  versions: definedEntries({
270
- firmware: imageVersion(info.fw?.application),
271
- applicationP1: imageVersion(info.fw?.application),
272
- applicationP2: imageVersion(info.fw?.application_data),
273
- bootloader: imageVersion(info.fw?.bootloader),
274
- board: imageVersion(info.fw?.romloader),
270
+ firmware: imageVersion(info.main_mcu?.application),
271
+ applicationP1: imageVersion(info.main_mcu?.application),
272
+ applicationP2: imageVersion(info.main_mcu?.application_data),
273
+ bootloader: imageVersion(info.main_mcu?.bootloader),
274
+ board: imageVersion(info.main_mcu?.romloader),
275
275
  ble: imageVersion(info.coprocessor?.application),
276
276
  se01: imageVersion(info.se1?.application),
277
277
  se02: imageVersion(info.se2?.application),
@@ -283,16 +283,16 @@ export const mapProtocolV2DeviceInfoToState = (
283
283
  se04Boot: imageVersion(info.se4?.bootloader),
284
284
  }),
285
285
  verification: definedEntries({
286
- firmwareBuildId: imageBuildId(info.fw?.application),
287
- firmwareHash: imageHash(info.fw?.application),
288
- applicationP1BuildId: imageBuildId(info.fw?.application),
289
- applicationP1Hash: imageHash(info.fw?.application),
290
- applicationP2BuildId: imageBuildId(info.fw?.application_data),
291
- applicationP2Hash: imageHash(info.fw?.application_data),
292
- bootloaderBuildId: imageBuildId(info.fw?.bootloader),
293
- bootloaderHash: imageHash(info.fw?.bootloader),
294
- boardBuildId: imageBuildId(info.fw?.romloader),
295
- boardHash: imageHash(info.fw?.romloader),
286
+ firmwareBuildId: imageBuildId(info.main_mcu?.application),
287
+ firmwareHash: imageHash(info.main_mcu?.application),
288
+ applicationP1BuildId: imageBuildId(info.main_mcu?.application),
289
+ applicationP1Hash: imageHash(info.main_mcu?.application),
290
+ applicationP2BuildId: imageBuildId(info.main_mcu?.application_data),
291
+ applicationP2Hash: imageHash(info.main_mcu?.application_data),
292
+ bootloaderBuildId: imageBuildId(info.main_mcu?.bootloader),
293
+ bootloaderHash: imageHash(info.main_mcu?.bootloader),
294
+ boardBuildId: imageBuildId(info.main_mcu?.romloader),
295
+ boardHash: imageHash(info.main_mcu?.romloader),
296
296
  bleBuildId: imageBuildId(info.coprocessor?.application),
297
297
  bleHash: imageHash(info.coprocessor?.application),
298
298
  se01BuildId: imageBuildId(info.se1?.application),
@@ -216,9 +216,9 @@ export const buildProtocolV2FeaturesPayload = ({
216
216
  runtimeMode?: ProtocolV2RuntimeMode;
217
217
  }): Features => {
218
218
  const info = deviceInfo;
219
- const fwApplication = info?.fw?.application;
220
- const fwBootloader = info?.fw?.bootloader;
221
- const fwBoard = info?.fw?.romloader;
219
+ const fwApplication = info?.main_mcu?.application;
220
+ const fwBootloader = info?.main_mcu?.bootloader;
221
+ const fwBoard = info?.main_mcu?.romloader;
222
222
  const bleApplication = info?.coprocessor?.application;
223
223
  const status = deviceStatus;
224
224
  const incomingSerialNo = info?.hw?.serial_no;
@@ -17,6 +17,14 @@ const LogLabelMethod: Set<string> = new Set([
17
17
  'fileRead',
18
18
  ]);
19
19
 
20
+ // 资源上传参数可能包含很大的 Base64 字符串。这里按方法整段跳过,避免日志层
21
+ // 递归复制和序列化这些数据;资源 API 与传输内容本身保持不变。
22
+ const LogPayloadBlockMethod: Set<string> = new Set([
23
+ 'deviceUploadNft',
24
+ 'deviceUploadWallpaper',
25
+ 'uploadPortfolio',
26
+ ]);
27
+
20
28
  const SensitiveLogKeys: Set<string> = new Set([
21
29
  'devicestate',
22
30
  'entropy',
@@ -86,7 +94,12 @@ export function getLogBlockLabel(message: unknown): string | undefined {
86
94
  }
87
95
 
88
96
  export function getSafeLogPayload(value: unknown, blockLabel?: string): unknown {
89
- if (blockLabel && (LogBlockEvent.has(blockLabel) || isSigningMethod(blockLabel))) {
97
+ if (
98
+ blockLabel &&
99
+ (LogBlockEvent.has(blockLabel) ||
100
+ LogPayloadBlockMethod.has(blockLabel) ||
101
+ isSigningMethod(blockLabel))
102
+ ) {
90
103
  return { method: blockLabel, payload: '[REDACTED]' };
91
104
  }
92
105
 
@@ -56,6 +56,16 @@ export const getProtocolV2SeType = (se?: DeviceSEInfo): string | null =>
56
56
 
57
57
  export type ProtocolV2RuntimeMode = 'normal' | 'bootloader' | 'romloader';
58
58
 
59
+ export type ProtocolV2ProtocolInfo = ProtocolInfo & {
60
+ /** Present only when hd-transport decoded the pre-build-fingerprint wire layout. */
61
+ protobuf_definition?: string | null;
62
+ };
63
+
64
+ export const isLegacyProtocolV2ProtocolInfo = (
65
+ protocolInfo: ProtocolInfo
66
+ ): protocolInfo is ProtocolV2ProtocolInfo & { protobuf_definition: string | null } =>
67
+ Object.prototype.hasOwnProperty.call(protocolInfo, 'protobuf_definition');
68
+
59
69
  /**
60
70
  * Protocol V2 fingerprints use:
61
71
  * <binary>__<version>__<commit>__<PROD|DEV>__<DEBUG|RELEASE>
@@ -87,11 +97,18 @@ export const parseProtocolV2BuildFingerprint = (
87
97
  };
88
98
 
89
99
  export const getProtocolV2RuntimeMode = (
90
- protocolInfo: ProtocolInfo
100
+ protocolInfo: ProtocolInfo,
101
+ deviceInfo?: ProtocolV2DeviceInfo
91
102
  ): ProtocolV2RuntimeMode | undefined => {
92
103
  const binary = parseProtocolV2BuildFingerprint(protocolInfo.build_fingerprint)?.binary;
93
104
  if (binary === 'application') return 'normal';
94
- return binary;
105
+ if (binary) return binary;
106
+
107
+ if (isLegacyProtocolV2ProtocolInfo(protocolInfo) && !deviceInfo?.main_mcu?.application) {
108
+ if (deviceInfo?.main_mcu?.romloader) return 'romloader';
109
+ if (deviceInfo?.main_mcu?.bootloader) return 'bootloader';
110
+ }
111
+ return undefined;
95
112
  };
96
113
 
97
114
  // MessageType_DeviceStatusGet in the Protocol V2 protobuf registry.
@@ -105,7 +122,7 @@ export const supportsProtocolV2Message = (
105
122
  export const PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST = {
106
123
  targets: {
107
124
  hw: true,
108
- fw: true,
125
+ main_mcu: true,
109
126
  coprocessor: true,
110
127
  },
111
128
  types: {
@@ -121,7 +138,7 @@ export const PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST = {
121
138
  export const PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST = {
122
139
  targets: {
123
140
  hw: true,
124
- fw: true,
141
+ main_mcu: true,
125
142
  coprocessor: true,
126
143
  se1: true,
127
144
  se2: true,
@@ -137,7 +154,7 @@ export const PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST = {
137
154
  export const PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST = {
138
155
  targets: {
139
156
  hw: true,
140
- fw: true,
157
+ main_mcu: true,
141
158
  coprocessor: true,
142
159
  se1: true,
143
160
  se2: true,
@@ -7,6 +7,7 @@ export {
7
7
  getProtocolV2RuntimeMode,
8
8
  getProtocolV2SeState,
9
9
  getProtocolV2SeType,
10
+ isLegacyProtocolV2ProtocolInfo,
10
11
  parseProtocolV2BuildFingerprint,
11
12
  requestProtocolV2ProtocolInfo,
12
13
  supportsProtocolV2Message,
@@ -18,6 +19,7 @@ export type {
18
19
  ProtocolV2SEInfo,
19
20
  ProtocolV2SeStateLabel,
20
21
  ProtocolV2RuntimeMode,
22
+ ProtocolV2ProtocolInfo,
21
23
  } from './features';
22
24
  export * from './firmware';
23
25
  export * from './walletSession';
@@ -107,10 +107,10 @@ function parseProtocolV2ResourceManifestFile(
107
107
  }
108
108
  const file = value as Partial<IProtocolV2ResourceManifestFile>;
109
109
  const archivePath = assertManifestRelativePath(file.archive_path, `files[${index}].archive_path`);
110
- const originalName = assertManifestRelativePath(
111
- file.original_name,
112
- `files[${index}].original_name`
113
- );
110
+ const originalName =
111
+ file.original_name === undefined
112
+ ? archivePath.split('/').pop() ?? archivePath
113
+ : assertManifestRelativePath(file.original_name, `files[${index}].original_name`);
114
114
  if (originalName.includes('/')) {
115
115
  throw new Error(`Invalid Pro2 resource manifest files[${index}].original_name`);
116
116
  }
@@ -122,15 +122,6 @@ function parseProtocolV2ResourceManifestFile(
122
122
  throw new Error(`Invalid Pro2 resource manifest files[${index}].size`);
123
123
  }
124
124
  const digest = normalizeHex(file.sha256, SHA256_HEX_LENGTH, `files[${index}].sha256`);
125
- if (file.signed !== true) {
126
- throw new Error(`Invalid Pro2 resource manifest files[${index}].signed`);
127
- }
128
- if (file.sig_algo !== 'ed25519' && file.sig_algo !== 'mldsa65') {
129
- throw new Error(`Invalid Pro2 resource manifest files[${index}].sig_algo`);
130
- }
131
- if (file.payload_version !== null && typeof file.payload_version !== 'string') {
132
- throw new Error(`Invalid Pro2 resource manifest files[${index}].payload_version`);
133
- }
134
125
  if (!archivePath.endsWith('.okpkg') || !originalName.endsWith('.okpkg')) {
135
126
  throw new Error(`Invalid Pro2 resource manifest files[${index}] package extension`);
136
127
  }
@@ -140,9 +131,9 @@ function parseProtocolV2ResourceManifestFile(
140
131
  device_path: devicePath,
141
132
  size: Number(file.size),
142
133
  sha256: digest,
143
- signed: true,
144
- sig_algo: file.sig_algo,
145
- payload_version: file.payload_version ?? null,
134
+ ...(file.signed === undefined ? {} : { signed: file.signed }),
135
+ ...(file.sig_algo === undefined ? {} : { sig_algo: file.sig_algo }),
136
+ ...(file.payload_version === undefined ? {} : { payload_version: file.payload_version }),
146
137
  };
147
138
  }
148
139
 
@@ -151,15 +142,8 @@ export function parseProtocolV2ResourceManifest(value: unknown): IProtocolV2Reso
151
142
  throw new Error('Invalid Pro2 resource manifest');
152
143
  }
153
144
  const manifest = value as Partial<IProtocolV2ResourceManifest>;
154
- if (
155
- manifest.schema !== 1 ||
156
- manifest.variant !== 'resource' ||
157
- manifest.device_root !== 'vol0:' ||
158
- manifest.restore_mode !== 'bootloader_update' ||
159
- !Array.isArray(manifest.trees) ||
160
- !Array.isArray(manifest.files)
161
- ) {
162
- throw new Error('Invalid Pro2 resource manifest contract');
145
+ if (!Array.isArray(manifest.files)) {
146
+ throw new Error('Invalid Pro2 resource manifest files');
163
147
  }
164
148
  const files = manifest.files.map(parseProtocolV2ResourceManifestFile);
165
149
  const devicePaths = new Set(files.map(file => file.device_path));
@@ -174,27 +158,6 @@ export function parseProtocolV2ResourceManifest(value: unknown): IProtocolV2Reso
174
158
  throw new Error('Invalid Pro2 resource manifest file set');
175
159
  }
176
160
  return {
177
- schema: 1,
178
- artifact_name: assertManifestString(manifest.artifact_name, 'artifact_name'),
179
- release_name: assertManifestString(manifest.release_name, 'release_name'),
180
- variant: 'resource',
181
- commit: assertManifestString(manifest.commit, 'commit'),
182
- short_sha: assertManifestString(manifest.short_sha, 'short_sha'),
183
- timestamp_utc: assertManifestString(manifest.timestamp_utc, 'timestamp_utc'),
184
- core_version: assertManifestString(manifest.core_version, 'core_version'),
185
- key_set: assertManifestString(manifest.key_set, 'key_set'),
186
- device_root: 'vol0:',
187
- restore_mode: 'bootloader_update',
188
- trees: manifest.trees.map((tree, index) => {
189
- if (!tree || typeof tree !== 'object') {
190
- throw new Error(`Invalid Pro2 resource manifest trees[${index}]`);
191
- }
192
- const item = tree as { path?: unknown; device?: unknown };
193
- return {
194
- path: assertManifestRelativePath(item.path, `trees[${index}].path`),
195
- device: assertManifestString(item.device, `trees[${index}].device`),
196
- };
197
- }),
198
161
  files,
199
162
  };
200
163
  }
@@ -68,7 +68,7 @@ export declare function deviceUploadNft(
68
68
  export declare function uploadPortfolio(
69
69
  connectId: string,
70
70
  params: {
71
- packageBytes: ArrayBuffer | Uint8Array | Blob;
71
+ packageBase64: string;
72
72
  timeoutMs?: number | string;
73
73
  }
74
74
  ): Response<FileInfo & { portfolioUpdated: true }>;
@@ -76,31 +76,16 @@ export type IProtocolV2Resources = {
76
76
 
77
77
  export type IProtocolV2ResourceManifestFile = {
78
78
  archive_path: string;
79
- original_name: string;
79
+ original_name?: string;
80
80
  device_path: string;
81
81
  size: number;
82
82
  sha256: string;
83
- signed: true;
84
- sig_algo: 'ed25519' | 'mldsa65';
85
- payload_version: string | null;
83
+ signed?: boolean;
84
+ sig_algo?: string;
85
+ payload_version?: string | null;
86
86
  };
87
87
 
88
88
  export type IProtocolV2ResourceManifest = {
89
- schema: 1;
90
- artifact_name: string;
91
- release_name: string;
92
- variant: 'resource';
93
- commit: string;
94
- short_sha: string;
95
- timestamp_utc: string;
96
- core_version: string;
97
- key_set: string;
98
- device_root: 'vol0:';
99
- restore_mode: 'bootloader_update';
100
- trees: Array<{
101
- path: string;
102
- device: string;
103
- }>;
104
89
  files: IProtocolV2ResourceManifestFile[];
105
90
  };
106
91
 
@@ -200,7 +200,7 @@ export const getAutoShutDownOptions = (
200
200
  return withNever([60_000, 120_000, 300_000, 600_000], protocol);
201
201
  case EDeviceType.Pro2:
202
202
  case EDeviceType.Neo:
203
- return withNever([60_000, 120_000, 300_000, 600_000, 1_800_000], protocol);
203
+ return withNever([60_000, 120_000, 300_000, 600_000], protocol);
204
204
  default:
205
205
  return [];
206
206
  }
@@ -109,6 +109,34 @@ export function buildPro2NftBundle(options: {
109
109
  const { image, thumbnail, title, subtitle, timestampMs } = options;
110
110
  assertImage('image', image, PRO2_NFT_IMAGE_WIDTH, PRO2_NFT_IMAGE_HEIGHT);
111
111
  assertImage('thumbnail', thumbnail, PRO2_NFT_THUMBNAIL_WIDTH, PRO2_NFT_THUMBNAIL_HEIGHT);
112
+ const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
113
+ const encodedThumbnail = encodePro2Image({
114
+ ...thumbnail,
115
+ alphaMode: 'black-background',
116
+ }).data;
117
+ return buildPro2NftBundleFromEncodedImages({
118
+ image: encodedImage,
119
+ thumbnail: encodedThumbnail,
120
+ title,
121
+ subtitle,
122
+ timestampMs,
123
+ });
124
+ }
125
+
126
+ export function buildPro2NftBundleFromEncodedImages(options: {
127
+ image: Uint8Array;
128
+ thumbnail: Uint8Array;
129
+ title: string;
130
+ subtitle: string;
131
+ timestampMs: number;
132
+ }): Pro2NftBundle {
133
+ const { image, thumbnail, title, subtitle, timestampMs } = options;
134
+ if (!(image instanceof Uint8Array) || image.byteLength === 0) {
135
+ throw invalidParameter('Parameter [image] must contain encoded NFT image data.');
136
+ }
137
+ if (!(thumbnail instanceof Uint8Array) || thumbnail.byteLength === 0) {
138
+ throw invalidParameter('Parameter [thumbnail] must contain encoded NFT thumbnail data.');
139
+ }
112
140
  const titleLength = typeof title === 'string' ? utf8Length(title) : 0;
113
141
  const subtitleLength =
114
142
  typeof subtitle === 'string' ? utf8Length(subtitle) : Number.POSITIVE_INFINITY;
@@ -122,21 +150,16 @@ export function buildPro2NftBundle(options: {
122
150
  throw invalidParameter('Parameter [timestampMs] must be a positive safe integer.');
123
151
  }
124
152
 
125
- const encodedImage = encodePro2Image({ ...image, alphaMode: 'black-background' }).data;
126
- const encodedThumbnail = encodePro2Image({
127
- ...thumbnail,
128
- alphaMode: 'black-background',
129
- }).data;
130
153
  const metadata = new TextEncoder().encode(JSON.stringify({ title, subtitle }));
131
154
  if (metadata.byteLength === 0 || metadata.byteLength > 512) {
132
155
  throw invalidParameter('Pro2 NFT metadata must contain 1 to 512 UTF-8 bytes.');
133
156
  }
134
157
 
135
- const hash8 = bytesToHex(blake2s(encodedImage)).slice(0, 8);
158
+ const hash8 = bytesToHex(blake2s(image)).slice(0, 8);
136
159
  return {
137
160
  basename: `nft-${hash8}-${timestampMs}`,
138
- image: encodedImage,
139
- thumbnail: encodedThumbnail,
161
+ image,
162
+ thumbnail,
140
163
  metadata,
141
164
  };
142
165
  }