@onekeyfe/hd-core 1.2.0-alpha.67 → 1.2.0-alpha.68

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 (254) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +313 -0
  2. package/__tests__/DeviceCommands.test.ts +59 -13
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +198 -3
  5. package/__tests__/core-initialization.test.ts +23 -0
  6. package/__tests__/device-connector-protocol.test.ts +81 -0
  7. package/__tests__/device-lifecycle-events.test.ts +105 -3
  8. package/__tests__/device-pool-state.test.ts +22 -0
  9. package/__tests__/device-settings.test.ts +39 -18
  10. package/__tests__/device-state-contract.test.ts +1 -0
  11. package/__tests__/device-state-mapper.test.ts +13 -1
  12. package/__tests__/device-utils.test.ts +48 -1
  13. package/__tests__/deviceSettings.test.ts +11 -1
  14. package/__tests__/deviceUploadNft.test.ts +316 -0
  15. package/__tests__/firmware-update/firmware-update-v3-reconnect.test.ts +5 -0
  16. package/__tests__/get-device-state.test.ts +132 -12
  17. package/__tests__/homescreen.test.ts +41 -1
  18. package/__tests__/kaspa-use-tweak-pro2.test.ts +2 -2
  19. package/__tests__/kaspaSignTransaction.test.ts +3 -26
  20. package/__tests__/logBlockEvent.test.ts +1 -1
  21. package/__tests__/method-protocol-support.test.ts +19 -0
  22. package/__tests__/open-wallet-session.test.ts +568 -37
  23. package/__tests__/pro2Nft.test.ts +108 -0
  24. package/__tests__/protocol-binding.test.ts +89 -0
  25. package/__tests__/protocol-v2-resources.test.ts +340 -0
  26. package/__tests__/protocol-v2-ui-lifecycle.test.ts +91 -0
  27. package/__tests__/protocol-v2-unlock-policy.test.ts +300 -37
  28. package/__tests__/protocol-v2.test.ts +1449 -867
  29. package/__tests__/protocolV2FileWrite.test.ts +152 -0
  30. package/__tests__/protocolV2UiInteraction.test.ts +63 -0
  31. package/__tests__/public-pro2-api-boundary.test.ts +1 -0
  32. package/__tests__/search-devices.test.ts +12 -3
  33. package/__tests__/tron-sign-message-init.test.ts +2 -2
  34. package/__tests__/uiProgressThrottle.test.ts +74 -0
  35. package/__tests__/uiPromiseRegistry.test.ts +115 -0
  36. package/dist/api/BaseMethod.d.ts +3 -1
  37. package/dist/api/BaseMethod.d.ts.map +1 -1
  38. package/dist/api/CheckAllFirmwareRelease.d.ts +2 -1
  39. package/dist/api/CheckAllFirmwareRelease.d.ts.map +1 -1
  40. package/dist/api/DetectDeviceConnectProtocol.d.ts +7 -0
  41. package/dist/api/DetectDeviceConnectProtocol.d.ts.map +1 -0
  42. package/dist/api/FirmwareUpdate.d.ts.map +1 -1
  43. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  44. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  45. package/dist/api/FirmwareUpdateV4.d.ts +11 -4
  46. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  47. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  48. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  49. package/dist/api/SearchDevices.d.ts.map +1 -1
  50. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  51. package/dist/api/device/DeviceSettings.d.ts.map +1 -1
  52. package/dist/api/device/DeviceUnlock.d.ts +2 -2
  53. package/dist/api/device/DeviceUnlock.d.ts.map +1 -1
  54. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +4 -3
  55. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  56. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  57. package/dist/api/helpers/protocolV2FileWrite.d.ts +9 -0
  58. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  59. package/dist/api/index.d.ts +2 -0
  60. package/dist/api/index.d.ts.map +1 -1
  61. package/dist/api/kaspa/KaspaGetAddress.d.ts +1 -1
  62. package/dist/api/kaspa/KaspaSignTransaction.d.ts +1 -1
  63. package/dist/api/kaspa/KaspaSignTransaction.d.ts.map +1 -1
  64. package/dist/api/polkadot/PolkadotGetAddress.d.ts +1 -1
  65. package/dist/api/polkadot/networks.d.ts +1 -1
  66. package/dist/api/protocol-v2/DeviceUploadNft.d.ts +31 -0
  67. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -0
  68. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  69. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  70. package/dist/api/solana/SolSignMessage.d.ts +1 -1
  71. package/dist/api/solana/SolSignOffchainMessage.d.ts +1 -1
  72. package/dist/api/solana/SolSignTransaction.d.ts +2 -2
  73. package/dist/api/sui/SuiGetAddress.d.ts +1 -1
  74. package/dist/api/sui/SuiGetPublicKey.d.ts +1 -1
  75. package/dist/api/sui/SuiSignMessage.d.ts +1 -1
  76. package/dist/api/sui/SuiSignTransaction.d.ts +1 -1
  77. package/dist/api/ton/TonGetAddress.d.ts +1 -1
  78. package/dist/api/ton/TonSignMessage.d.ts +1 -1
  79. package/dist/api/ton/TonSignProof.d.ts +1 -1
  80. package/dist/api/tron/TronSignMessage.d.ts +2 -2
  81. package/dist/api/tron/TronSignTransaction.d.ts +1 -1
  82. package/dist/core/deviceEventRegistration.d.ts +2 -0
  83. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  84. package/dist/core/index.d.ts.map +1 -1
  85. package/dist/core/uiPromiseRegistry.d.ts +6 -0
  86. package/dist/core/uiPromiseRegistry.d.ts.map +1 -0
  87. package/dist/data-manager/DataManager.d.ts +9 -3
  88. package/dist/data-manager/DataManager.d.ts.map +1 -1
  89. package/dist/data-manager/TransportManager.d.ts.map +1 -1
  90. package/dist/device/Device.d.ts +29 -5
  91. package/dist/device/Device.d.ts.map +1 -1
  92. package/dist/device/DeviceCommands.d.ts +4 -4
  93. package/dist/device/DeviceCommands.d.ts.map +1 -1
  94. package/dist/device/DeviceConnector.d.ts +2 -1
  95. package/dist/device/DeviceConnector.d.ts.map +1 -1
  96. package/dist/device/DevicePool.d.ts.map +1 -1
  97. package/dist/deviceProfile/protocolV2DeviceIdentity.d.ts.map +1 -1
  98. package/dist/events/device.d.ts +4 -0
  99. package/dist/events/device.d.ts.map +1 -1
  100. package/dist/events/logBlockEvent.d.ts.map +1 -1
  101. package/dist/events/ui-promise.d.ts +4 -2
  102. package/dist/events/ui-promise.d.ts.map +1 -1
  103. package/dist/events/ui-request.d.ts +31 -2
  104. package/dist/events/ui-request.d.ts.map +1 -1
  105. package/dist/events/ui-response.d.ts +10 -2
  106. package/dist/events/ui-response.d.ts.map +1 -1
  107. package/dist/index.d.ts +243 -69
  108. package/dist/index.js +2563 -1217
  109. package/dist/inject.d.ts +6 -1
  110. package/dist/inject.d.ts.map +1 -1
  111. package/dist/lowLevelInject.d.ts.map +1 -1
  112. package/dist/protocols/protocol-v2/features.d.ts +0 -4
  113. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  114. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  115. package/dist/protocols/protocol-v2/resources.d.ts +30 -0
  116. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -0
  117. package/dist/protocols/protocol-v2/uiInteraction.d.ts +7 -4
  118. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  119. package/dist/protocols/protocol-v2/unlockPolicy.d.ts +1 -3
  120. package/dist/protocols/protocol-v2/unlockPolicy.d.ts.map +1 -1
  121. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +22 -0
  122. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -0
  123. package/dist/protocols/protocol-v2/walletSession.d.ts +6 -1
  124. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  125. package/dist/topLevelInject.d.ts.map +1 -1
  126. package/dist/types/api/checkAllFirmwareRelease.d.ts +3 -1
  127. package/dist/types/api/checkAllFirmwareRelease.d.ts.map +1 -1
  128. package/dist/types/api/detectDeviceConnectProtocol.d.ts +4 -0
  129. package/dist/types/api/detectDeviceConnectProtocol.d.ts.map +1 -0
  130. package/dist/types/api/deviceUnlock.d.ts +5 -1
  131. package/dist/types/api/deviceUnlock.d.ts.map +1 -1
  132. package/dist/types/api/export.d.ts +1 -0
  133. package/dist/types/api/export.d.ts.map +1 -1
  134. package/dist/types/api/firmwareUpdate.d.ts +4 -2
  135. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  136. package/dist/types/api/index.d.ts +6 -1
  137. package/dist/types/api/index.d.ts.map +1 -1
  138. package/dist/types/api/protocolV2.d.ts +3 -0
  139. package/dist/types/api/protocolV2.d.ts.map +1 -1
  140. package/dist/types/device.d.ts +2 -2
  141. package/dist/types/device.d.ts.map +1 -1
  142. package/dist/types/params.d.ts +1 -0
  143. package/dist/types/params.d.ts.map +1 -1
  144. package/dist/types/settings.d.ts +38 -8
  145. package/dist/types/settings.d.ts.map +1 -1
  146. package/dist/utils/deviceFeaturesCompat.d.ts.map +1 -1
  147. package/dist/utils/deviceFeaturesUtils.d.ts +4 -0
  148. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  149. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  150. package/dist/utils/deviceSettings.d.ts.map +1 -1
  151. package/dist/utils/homescreen.d.ts +4 -0
  152. package/dist/utils/homescreen.d.ts.map +1 -1
  153. package/dist/utils/index.d.ts +1 -1
  154. package/dist/utils/index.d.ts.map +1 -1
  155. package/dist/utils/patch.d.ts +1 -1
  156. package/dist/utils/patch.d.ts.map +1 -1
  157. package/dist/utils/pro2Nft.d.ts +31 -0
  158. package/dist/utils/pro2Nft.d.ts.map +1 -0
  159. package/dist/utils/pro2Wallpaper.d.ts +10 -0
  160. package/dist/utils/pro2Wallpaper.d.ts.map +1 -1
  161. package/dist/utils/uiProgressThrottle.d.ts +3 -0
  162. package/dist/utils/uiProgressThrottle.d.ts.map +1 -0
  163. package/package.json +4 -4
  164. package/src/api/BaseMethod.ts +8 -10
  165. package/src/api/CheckAllFirmwareRelease.ts +61 -11
  166. package/src/api/DetectDeviceConnectProtocol.ts +18 -0
  167. package/src/api/FirmwareUpdate.ts +6 -2
  168. package/src/api/FirmwareUpdateV2.ts +5 -2
  169. package/src/api/FirmwareUpdateV3.ts +6 -2
  170. package/src/api/FirmwareUpdateV4.ts +463 -258
  171. package/src/api/GetPassphraseState.ts +7 -1
  172. package/src/api/OpenWalletSession.ts +71 -17
  173. package/src/api/SearchDevices.ts +3 -1
  174. package/src/api/allnetwork/AllNetworkGetAddress.ts +21 -21
  175. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +36 -11
  176. package/src/api/device/DeviceSettings.ts +5 -4
  177. package/src/api/device/DeviceUnlock.ts +8 -3
  178. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +27 -12
  179. package/src/api/firmware/uploadFirmware.ts +17 -4
  180. package/src/api/helpers/protocolV2FileWrite.ts +202 -63
  181. package/src/api/index.ts +2 -0
  182. package/src/api/kaspa/KaspaGetAddress.ts +1 -1
  183. package/src/api/kaspa/KaspaSignTransaction.ts +2 -13
  184. package/src/api/polkadot/networks.ts +3 -3
  185. package/src/api/protocol-v2/DeviceUploadNft.ts +197 -0
  186. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  187. package/src/api/protocol-v2/helpers.ts +1 -1
  188. package/src/api/solana/SolSignMessage.ts +1 -1
  189. package/src/api/solana/SolSignOffchainMessage.ts +1 -1
  190. package/src/api/solana/SolSignTransaction.ts +2 -2
  191. package/src/api/sui/SuiGetAddress.ts +1 -1
  192. package/src/api/sui/SuiGetPublicKey.ts +1 -1
  193. package/src/api/sui/SuiSignMessage.ts +1 -1
  194. package/src/api/sui/SuiSignTransaction.ts +1 -1
  195. package/src/api/ton/TonGetAddress.ts +1 -1
  196. package/src/api/ton/TonSignMessage.ts +1 -1
  197. package/src/api/ton/TonSignProof.ts +1 -1
  198. package/src/api/tron/TronSignMessage.ts +2 -2
  199. package/src/api/tron/TronSignTransaction.ts +1 -1
  200. package/src/core/deviceEventRegistration.ts +4 -0
  201. package/src/core/index.ts +213 -111
  202. package/src/core/uiPromiseRegistry.ts +63 -0
  203. package/src/data/messages/messages-protocol-v2.json +25 -0
  204. package/src/data-manager/DataManager.ts +88 -11
  205. package/src/data-manager/TransportManager.ts +6 -0
  206. package/src/device/Device.ts +273 -43
  207. package/src/device/DeviceCommands.ts +31 -4
  208. package/src/device/DeviceConnector.ts +33 -13
  209. package/src/device/DevicePool.ts +21 -7
  210. package/src/deviceProfile/buildDeviceFeatures.ts +2 -2
  211. package/src/deviceProfile/protocolV2DeviceIdentity.ts +3 -0
  212. package/src/events/device.ts +4 -0
  213. package/src/events/logBlockEvent.ts +1 -0
  214. package/src/events/ui-promise.ts +4 -2
  215. package/src/events/ui-request.ts +36 -2
  216. package/src/events/ui-response.ts +12 -2
  217. package/src/inject.ts +58 -2
  218. package/src/lowLevelInject.ts +6 -3
  219. package/src/protocols/protocol-v2/features.ts +10 -7
  220. package/src/protocols/protocol-v2/firmware.ts +2 -0
  221. package/src/protocols/protocol-v2/resources.ts +390 -0
  222. package/src/protocols/protocol-v2/uiInteraction.ts +46 -6
  223. package/src/protocols/protocol-v2/unlockPolicy.ts +1 -105
  224. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +131 -0
  225. package/src/protocols/protocol-v2/walletSession.ts +214 -36
  226. package/src/topLevelInject.ts +6 -3
  227. package/src/types/api/checkAllFirmwareRelease.ts +3 -1
  228. package/src/types/api/detectDeviceConnectProtocol.ts +6 -0
  229. package/src/types/api/deviceUnlock.ts +12 -1
  230. package/src/types/api/export.ts +1 -0
  231. package/src/types/api/firmwareUpdate.ts +6 -3
  232. package/src/types/api/index.ts +13 -0
  233. package/src/types/api/protocolV2.ts +13 -0
  234. package/src/types/device.ts +15 -3
  235. package/src/types/params.ts +7 -1
  236. package/src/types/settings.ts +60 -9
  237. package/src/utils/deviceFeaturesCompat.ts +5 -0
  238. package/src/utils/deviceFeaturesUtils.ts +14 -3
  239. package/src/utils/deviceInfoUtils.ts +2 -0
  240. package/src/utils/deviceSettings.ts +3 -0
  241. package/src/utils/homescreen.ts +32 -6
  242. package/src/utils/index.ts +6 -1
  243. package/src/utils/pro2Nft.ts +142 -0
  244. package/src/utils/pro2Wallpaper.ts +35 -8
  245. package/src/utils/uiProgressThrottle.ts +63 -0
  246. package/dist/api/firmware/progressThrottle.d.ts +0 -3
  247. package/dist/api/firmware/progressThrottle.d.ts.map +0 -1
  248. package/dist/protocols/protocol-v2/lockedError.d.ts +0 -2
  249. package/dist/protocols/protocol-v2/lockedError.d.ts.map +0 -1
  250. package/dist/protocols/protocol-v2/unlockRetry.d.ts +0 -10
  251. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +0 -1
  252. package/src/api/firmware/progressThrottle.ts +0 -44
  253. package/src/protocols/protocol-v2/lockedError.ts +0 -10
  254. package/src/protocols/protocol-v2/unlockRetry.ts +0 -109
@@ -74,7 +74,9 @@ import type { DeviceStateReadOptions } from '../types/api/getDeviceState';
74
74
  import type {
75
75
  DeviceButtonRequestPayload,
76
76
  DeviceFeaturesPayload,
77
+ HardwareUiInteractionMeta,
77
78
  PassphraseRequestPayload,
79
+ ProtocolV2UiEventMetadata,
78
80
  } from '../events';
79
81
  import type { PassphrasePromptResponse } from './DeviceCommands';
80
82
  import type { Deferred, HardwareConnectProtocol } from '@onekeyfe/hd-shared';
@@ -93,6 +95,7 @@ export type InitOptions = {
93
95
  passphraseState?: string;
94
96
  deriveCardano?: boolean;
95
97
  connectProtocol?: HardwareConnectProtocol;
98
+ forceProtocolDetection?: boolean;
96
99
  protocolV2DeviceInfoTimeoutMs?: number;
97
100
  /** Refresh Protocol V2 runtime state before returning discovery results. */
98
101
  refreshRuntimeState?: boolean;
@@ -137,6 +140,8 @@ const isProtocolV2DeviceStatusUnsupportedError = (error: unknown) => {
137
140
 
138
141
  export interface DeviceEvents {
139
142
  [DEVICE.PIN]: [Device, PROTO.PinMatrixRequestType | undefined, (err: any, pin: string) => void];
143
+ [DEVICE.PIN_ON_DEVICE]: [Device, DeviceSessionPinType, ProtocolV2UiEventMetadata?];
144
+ [DEVICE.PIN_ON_DEVICE_COMPLETE]: [Device, HardwareUiInteractionMeta];
140
145
  [DEVICE.PASSPHRASE_ON_DEVICE]: [Device, PassphraseRequestPayload?];
141
146
  [DEVICE.ATTACH_PIN_ON_DEVICE]: [Device, PassphraseRequestPayload?];
142
147
  [DEVICE.BUTTON]: [Device, DeviceButtonRequestPayload];
@@ -235,6 +240,24 @@ export class Device extends EventEmitter {
235
240
  /** Force the next initialization to reload DeviceInfo after reconnect or reboot. */
236
241
  private protocolV2StateNeedsReload = false;
237
242
 
243
+ /** Runtime context negotiated once per active Protocol V2 link. */
244
+ private protocolV2RuntimeContext?: ProtocolInfo;
245
+
246
+ /** Coalesces concurrent first-use runtime negotiation on the same active link. */
247
+ private protocolV2RuntimeContextPromise?: Promise<ProtocolInfo>;
248
+
249
+ /** Invalidates an in-flight runtime-context response without adding a transport epoch. */
250
+ private protocolV2RuntimeContextRequestToken?: object;
251
+
252
+ private protocolV2UiInteraction?: {
253
+ interactionId: string;
254
+ phaseCounter: number;
255
+ sequence: number;
256
+ opened: boolean;
257
+ };
258
+
259
+ private protocolV2UiInteractionCounter = 0;
260
+
238
261
  get state() {
239
262
  return this.stateStore.getState();
240
263
  }
@@ -342,7 +365,7 @@ export class Device extends EventEmitter {
342
365
  deviceId,
343
366
  path: this.originalDescriptor?.path,
344
367
  bleName,
345
- name: displayName || bleName || `OneKey ${deviceType?.toUpperCase()}`,
368
+ name: bleName || displayName || `OneKey ${deviceType?.toUpperCase()}`,
346
369
  // Keep the legacy top-level field string-compatible while preserving
347
370
  // the canonical nullable value at state.identity.label.
348
371
  label: displayName ?? '',
@@ -360,7 +383,10 @@ export class Device extends EventEmitter {
360
383
  * Device connect
361
384
  * @returns {Promise<boolean>}
362
385
  */
363
- connect(connectProtocol?: HardwareConnectProtocol) {
386
+ connect(
387
+ connectProtocol?: HardwareConnectProtocol,
388
+ options?: { forceProtocolDetection?: boolean }
389
+ ) {
364
390
  const env = DataManager.getSettings('env');
365
391
  // eslint-disable-next-line no-async-promise-executor
366
392
  return new Promise<boolean>(async (resolve, reject) => {
@@ -370,7 +396,7 @@ export class Device extends EventEmitter {
370
396
  return;
371
397
  }
372
398
  try {
373
- await this.acquire(connectProtocol);
399
+ await this.acquire(connectProtocol, options);
374
400
  resolve(true);
375
401
  } catch (error) {
376
402
  reject(error);
@@ -380,7 +406,7 @@ export class Device extends EventEmitter {
380
406
  // 不存在 Session ID 或存在 Session ID 但设备在别处使用,都需要 acquire 获取最新 sessionID
381
407
  if (!this.mainId || (!this.isUsedHere() && this.originalDescriptor)) {
382
408
  try {
383
- await this.acquire(connectProtocol);
409
+ await this.acquire(connectProtocol, options);
384
410
  resolve(true);
385
411
  } catch (error) {
386
412
  reject(error);
@@ -397,11 +423,16 @@ export class Device extends EventEmitter {
397
423
 
398
424
  async acquire(
399
425
  expectedProtocol?: HardwareConnectProtocol,
400
- options?: { throwOnRunPromiseError?: boolean }
426
+ options?: { throwOnRunPromiseError?: boolean; forceProtocolDetection?: boolean }
401
427
  ) {
402
428
  const env = DataManager.getSettings('env');
403
429
  const mainIdKey = DataManager.isBleConnect(env) ? 'id' : 'session';
404
- const protocolHint = expectedProtocol ? undefined : this.originalDescriptor.protocolType;
430
+ const previousProtocol = this.originalDescriptor.protocolType;
431
+ // A protocol stored after a successful probe is authoritative. Only the explicit
432
+ // first-connection/recovery path may bypass it and probe both protocols again.
433
+ const strictProtocol = options?.forceProtocolDetection
434
+ ? undefined
435
+ : expectedProtocol ?? this.originalDescriptor.protocolType;
405
436
  try {
406
437
  let acquireResult: unknown;
407
438
  if (DataManager.isBleConnect(env)) {
@@ -413,8 +444,8 @@ export class Device extends EventEmitter {
413
444
  this.originalDescriptor.id,
414
445
  undefined,
415
446
  true,
416
- expectedProtocol,
417
- protocolHint
447
+ strictProtocol,
448
+ undefined
418
449
  );
419
450
  this.mainId = (acquireResult as any)?.uuid ?? '';
420
451
  Log.debug('Expected uuid:', this.mainId);
@@ -423,24 +454,31 @@ export class Device extends EventEmitter {
423
454
  this.originalDescriptor.path,
424
455
  this.originalDescriptor.session,
425
456
  undefined,
426
- expectedProtocol,
427
- protocolHint
457
+ strictProtocol,
458
+ undefined
428
459
  );
429
460
  this.mainId = acquireResult as string | undefined;
430
461
  Log.debug('Expected session id:', this.mainId);
431
462
  }
432
- this.deviceAcquired = true;
433
- this.updateDescriptor({ [mainIdKey]: this.mainId } as unknown as DeviceDescriptor);
434
-
435
463
  // Propagate protocol version detected during acquire.
436
464
  const detectedProtocol =
437
465
  (acquireResult as { protocolType?: HardwareConnectProtocol } | undefined)?.protocolType ??
438
466
  TransportManager.transport?.getProtocolType?.(
439
467
  DataManager.isBleConnect(env) ? this.originalDescriptor.id : this.originalDescriptor.path
440
468
  );
469
+ if (options?.forceProtocolDetection && !detectedProtocol) {
470
+ throw ERRORS.TypedError(
471
+ HardwareErrorCode.RuntimeError,
472
+ `Active protocol detection returned no protocol for ${
473
+ this.originalDescriptor.path || this.originalDescriptor.id
474
+ }`
475
+ );
476
+ }
441
477
  if (detectedProtocol) {
442
478
  this.originalDescriptor.protocolType = detectedProtocol;
443
479
  }
480
+ this.deviceAcquired = true;
481
+ this.updateDescriptor({ [mainIdKey]: this.mainId } as unknown as DeviceDescriptor);
444
482
 
445
483
  if (this.commands) {
446
484
  await this.commands.dispose(false);
@@ -448,6 +486,22 @@ export class Device extends EventEmitter {
448
486
 
449
487
  this.commands = new DeviceCommands(this, this.mainId ?? '');
450
488
  } catch (error) {
489
+ if (options?.forceProtocolDetection) {
490
+ this.originalDescriptor.protocolType = previousProtocol;
491
+ const failedSession = this.mainId;
492
+ this.deviceAcquired = false;
493
+ if (failedSession) {
494
+ try {
495
+ await this.deviceConnector?.release?.(failedSession, false);
496
+ } catch (releaseError) {
497
+ Log.debug('Failed to release an unsuccessful protocol probe', releaseError);
498
+ }
499
+ }
500
+ if (!DataManager.isBleConnect(env)) {
501
+ this.mainId = null;
502
+ this.updateDescriptor({ session: null } as DeviceDescriptor);
503
+ }
504
+ }
451
505
  if (options?.throwOnRunPromiseError) {
452
506
  throw error;
453
507
  }
@@ -647,6 +701,7 @@ export class Device extends EventEmitter {
647
701
  // Most-specific model first; must match getMethodVersionRange in deviceInfoUtils,
648
702
  // otherwise e.g. Classic1s resolves the looser model_mini range before model_classic1s.
649
703
  const modelFallbacks: IDeviceModel[] = [
704
+ 'model_pro2',
650
705
  'model_classic1s',
651
706
  'model_classic',
652
707
  'model_mini',
@@ -666,7 +721,8 @@ export class Device extends EventEmitter {
666
721
  if (
667
722
  deviceType === EDeviceType.Touch ||
668
723
  deviceType === EDeviceType.Pro ||
669
- deviceType === EDeviceType.Pro2
724
+ deviceType === EDeviceType.Pro2 ||
725
+ deviceType === EDeviceType.Neo
670
726
  ) {
671
727
  return { support: true };
672
728
  }
@@ -683,7 +739,8 @@ export class Device extends EventEmitter {
683
739
  if (
684
740
  deviceType === EDeviceType.Touch ||
685
741
  deviceType === EDeviceType.Pro ||
686
- deviceType === EDeviceType.Pro2
742
+ deviceType === EDeviceType.Pro2 ||
743
+ deviceType === EDeviceType.Neo
687
744
  ) {
688
745
  return { support: false };
689
746
  }
@@ -969,7 +1026,10 @@ export class Device extends EventEmitter {
969
1026
  await this.getFeatures();
970
1027
  }
971
1028
 
972
- if (!this.isProtocolV2() && refresh.has('verification')) {
1029
+ const supportsProtocolV1OnekeyFeatures =
1030
+ this.getCurrentDeviceType() === EDeviceType.Touch ||
1031
+ this.getCurrentDeviceType() === EDeviceType.Pro;
1032
+ if (!this.isProtocolV2() && refresh.has('verification') && supportsProtocolV1OnekeyFeatures) {
973
1033
  const { message } = await this.commands.typedCall('OnekeyGetFeatures', 'OnekeyFeatures');
974
1034
  this.updateState(mapProtocolV1OnekeyFeaturesToState(message), 'device-info');
975
1035
  }
@@ -992,13 +1052,7 @@ export class Device extends EventEmitter {
992
1052
  }
993
1053
 
994
1054
  if (refresh.has('status') && !initializedWithDeviceInfo) {
995
- const deviceInfo =
996
- refreshedDeviceInfo ??
997
- (await requestProtocolV2DeviceInfo({
998
- commands: this.commands,
999
- request: getProtocolV2DeviceInfoRequest(),
1000
- }));
1001
- await this.probeProtocolV2RuntimeState(deviceInfo);
1055
+ await this.probeProtocolV2RuntimeState(refreshedDeviceInfo);
1002
1056
  }
1003
1057
 
1004
1058
  if (refresh.has('settings') && this.state?.status.mode === 'normal') {
@@ -1069,19 +1123,66 @@ export class Device extends EventEmitter {
1069
1123
  return this.features;
1070
1124
  }
1071
1125
 
1072
- async probeProtocolV2RuntimeState(deviceInfo: ProtocolV2DeviceInfo, timeoutMs?: number) {
1073
- const protocolInfo = await requestProtocolV2ProtocolInfo({
1074
- commands: this.commands,
1075
- timeoutMs,
1076
- });
1126
+ async ensureProtocolV2RuntimeContext(timeoutMs?: number): Promise<ProtocolInfo> {
1127
+ const cachedProtocolInfo =
1128
+ this.protocolV2RuntimeContext ??
1129
+ (!this.protocolV2StateNeedsReload
1130
+ ? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
1131
+ : undefined);
1132
+ if (cachedProtocolInfo) {
1133
+ this.protocolV2RuntimeContext = cachedProtocolInfo;
1134
+ return cachedProtocolInfo;
1135
+ }
1136
+
1137
+ if (this.protocolV2RuntimeContextPromise) {
1138
+ return this.protocolV2RuntimeContextPromise;
1139
+ }
1140
+
1141
+ const requestToken = {};
1142
+ const pendingRequest = (async () => {
1143
+ const protocolInfo = await requestProtocolV2ProtocolInfo({
1144
+ commands: this.commands,
1145
+ timeoutMs,
1146
+ });
1147
+ if (this.protocolV2RuntimeContextRequestToken !== requestToken) {
1148
+ throw ERRORS.TypedError(
1149
+ HardwareErrorCode.DeviceInitializeFailed,
1150
+ 'Protocol V2 runtime context was invalidated while loading.'
1151
+ );
1152
+ }
1153
+ this.protocolV2RuntimeContext = protocolInfo;
1154
+ return protocolInfo;
1155
+ })();
1156
+ this.protocolV2RuntimeContextRequestToken = requestToken;
1157
+ this.protocolV2RuntimeContextPromise = pendingRequest;
1158
+
1159
+ try {
1160
+ return await pendingRequest;
1161
+ } finally {
1162
+ if (this.protocolV2RuntimeContextPromise === pendingRequest) {
1163
+ this.protocolV2RuntimeContextPromise = undefined;
1164
+ }
1165
+ if (this.protocolV2RuntimeContextRequestToken === requestToken) {
1166
+ this.protocolV2RuntimeContextRequestToken = undefined;
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ async probeProtocolV2RuntimeState(deviceInfo?: ProtocolV2DeviceInfo, timeoutMs?: number) {
1172
+ const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs);
1077
1173
  const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
1078
- const protocolV2DeviceType = resolveProtocolV2DeviceIdentity(
1079
- deviceInfo.hw?.Device_type
1080
- ).deviceType;
1081
- if (runtimeMode === 'romloader' && protocolV2DeviceType !== EDeviceType.Pro2) {
1174
+ const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
1175
+ const protocolV2DeviceType = runtimeDeviceInfo
1176
+ ? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
1177
+ : this.getCurrentDeviceType();
1178
+ if (
1179
+ runtimeMode === 'romloader' &&
1180
+ protocolV2DeviceType !== EDeviceType.Pro2 &&
1181
+ protocolV2DeviceType !== EDeviceType.Neo
1182
+ ) {
1082
1183
  throw ERRORS.TypedError(
1083
1184
  HardwareErrorCode.DeviceInitializeFailed,
1084
- 'Protocol V2 romloader mode is only supported for Pro2.'
1185
+ 'Protocol V2 romloader mode is only supported for Pro2 and Neo.'
1085
1186
  );
1086
1187
  }
1087
1188
  const deviceStatusSupported = supportsProtocolV2Message(
@@ -1152,6 +1253,9 @@ export class Device extends EventEmitter {
1152
1253
  this.deviceAcquired = false;
1153
1254
  if (!this.isProtocolV2()) return;
1154
1255
  this.protocolV2StateNeedsReload = true;
1256
+ this.protocolV2RuntimeContext = undefined;
1257
+ this.protocolV2RuntimeContextPromise = undefined;
1258
+ this.protocolV2RuntimeContextRequestToken = undefined;
1155
1259
  this.clearPreInitialized();
1156
1260
  }
1157
1261
 
@@ -1160,9 +1264,14 @@ export class Device extends EventEmitter {
1160
1264
  if (deviceId) {
1161
1265
  deviceWalletSessionStore.deleteDevice(deviceId);
1162
1266
  }
1163
- if (this.isProtocolV2() && this.originalDescriptor.path !== deviceId) {
1164
- deviceWalletSessionStore.deleteDevice(this.originalDescriptor.path);
1267
+ if (this.isProtocolV2()) {
1268
+ if (this.originalDescriptor.path !== deviceId) {
1269
+ deviceWalletSessionStore.deleteDevice(this.originalDescriptor.path);
1270
+ }
1165
1271
  this.protocolV2StateNeedsReload = true;
1272
+ this.protocolV2RuntimeContext = undefined;
1273
+ this.protocolV2RuntimeContextPromise = undefined;
1274
+ this.protocolV2RuntimeContextRequestToken = undefined;
1166
1275
  }
1167
1276
 
1168
1277
  this.passphraseState = undefined;
@@ -1175,6 +1284,9 @@ export class Device extends EventEmitter {
1175
1284
  if (!this.isProtocolV2()) return;
1176
1285
 
1177
1286
  this.protocolV2StateNeedsReload = true;
1287
+ this.protocolV2RuntimeContext = undefined;
1288
+ this.protocolV2RuntimeContextPromise = undefined;
1289
+ this.protocolV2RuntimeContextRequestToken = undefined;
1178
1290
  this.clearPreInitialized();
1179
1291
  let loaderMode: 'bootloader' | 'romloader' | undefined;
1180
1292
  if (rebootType === DeviceRebootType.Bootloader) {
@@ -1281,11 +1393,17 @@ export class Device extends EventEmitter {
1281
1393
  }
1282
1394
  };
1283
1395
 
1284
- if (!this.isUsedHere() || this.commands.disposed) {
1285
- const env = DataManager.getSettings('env');
1396
+ const env = DataManager.getSettings('env');
1397
+ if (options.forceProtocolDetection && env !== 'react-native' && this.isUsedHere()) {
1398
+ await this.release();
1399
+ }
1400
+
1401
+ if (options.forceProtocolDetection || !this.isUsedHere() || this.commands.disposed) {
1286
1402
  if (env !== 'react-native') {
1287
1403
  try {
1288
- await this.acquire(options.connectProtocol);
1404
+ await this.acquire(options.connectProtocol, {
1405
+ forceProtocolDetection: options.forceProtocolDetection,
1406
+ });
1289
1407
  } catch (error) {
1290
1408
  clearRunPromise();
1291
1409
  runPromise.reject(error);
@@ -1463,7 +1581,8 @@ export class Device extends EventEmitter {
1463
1581
  if (!this.state) return undefined;
1464
1582
  return (
1465
1583
  this.isProtocolV2() &&
1466
- this.getCurrentDeviceType() === EDeviceType.Pro2 &&
1584
+ (this.getCurrentDeviceType() === EDeviceType.Pro2 ||
1585
+ this.getCurrentDeviceType() === EDeviceType.Neo) &&
1467
1586
  this.state.status.mode === 'romloader'
1468
1587
  );
1469
1588
  }
@@ -1509,7 +1628,8 @@ export class Device extends EventEmitter {
1509
1628
  const isModeT =
1510
1629
  deviceType === EDeviceType.Touch ||
1511
1630
  deviceType === EDeviceType.Pro ||
1512
- deviceType === EDeviceType.Pro2;
1631
+ deviceType === EDeviceType.Pro2 ||
1632
+ deviceType === EDeviceType.Neo;
1513
1633
  const unlocked = this.state?.status.unlocked;
1514
1634
  const preCheckTouch = isModeT && unlocked === false;
1515
1635
  const passphraseProtection = this.getCurrentPassphraseProtection();
@@ -1527,6 +1647,85 @@ export class Device extends EventEmitter {
1527
1647
  return res.message;
1528
1648
  }
1529
1649
 
1650
+ beginProtocolV2UiInteraction() {
1651
+ if (!this.isProtocolV2()) return;
1652
+ this.protocolV2UiInteraction = {
1653
+ interactionId: `${this.instanceId}:${Date.now()}:${++this.protocolV2UiInteractionCounter}`,
1654
+ phaseCounter: 0,
1655
+ sequence: 0,
1656
+ opened: false,
1657
+ };
1658
+ }
1659
+
1660
+ createProtocolV2UiPhaseMetadata(
1661
+ phase: HardwareUiInteractionMeta['phase'],
1662
+ transition: HardwareUiInteractionMeta['transition'],
1663
+ options?: {
1664
+ phaseId?: string;
1665
+ outcome?: HardwareUiInteractionMeta['outcome'];
1666
+ }
1667
+ ): HardwareUiInteractionMeta | undefined {
1668
+ if (!this.isProtocolV2()) return undefined;
1669
+ if (!this.protocolV2UiInteraction) this.beginProtocolV2UiInteraction();
1670
+
1671
+ const interaction = this.protocolV2UiInteraction;
1672
+ if (!interaction) return undefined;
1673
+ const phaseId =
1674
+ options?.phaseId ?? `${interaction.interactionId}:phase-${++interaction.phaseCounter}`;
1675
+ interaction.opened = true;
1676
+ interaction.sequence += 1;
1677
+
1678
+ return {
1679
+ interactionId: interaction.interactionId,
1680
+ phaseId,
1681
+ sequence: interaction.sequence,
1682
+ phase,
1683
+ transition,
1684
+ ...(options?.outcome ? { outcome: options.outcome } : {}),
1685
+ protocol: 'V2',
1686
+ };
1687
+ }
1688
+
1689
+ completeProtocolV2UiPhase(
1690
+ phase: HardwareUiInteractionMeta,
1691
+ outcome: HardwareUiInteractionMeta['outcome'] = 'succeeded'
1692
+ ) {
1693
+ return this.createProtocolV2UiPhaseMetadata(phase.phase, 'complete', {
1694
+ phaseId: phase.phaseId,
1695
+ outcome,
1696
+ });
1697
+ }
1698
+
1699
+ finishProtocolV2UiInteraction(
1700
+ outcome?: HardwareUiInteractionMeta['outcome'],
1701
+ options?: { ensureMetadata?: boolean }
1702
+ ) {
1703
+ const interaction = this.protocolV2UiInteraction;
1704
+ if (!interaction || (!interaction.opened && !options?.ensureMetadata)) {
1705
+ this.protocolV2UiInteraction = undefined;
1706
+ return undefined;
1707
+ }
1708
+
1709
+ const phaseId = `${interaction.interactionId}:phase-${Math.max(interaction.phaseCounter, 1)}`;
1710
+ interaction.opened = true;
1711
+ interaction.sequence += 1;
1712
+ const metadata: HardwareUiInteractionMeta = {
1713
+ interactionId: interaction.interactionId,
1714
+ phaseId,
1715
+ sequence: interaction.sequence,
1716
+ phase: 'processing',
1717
+ transition: 'finish',
1718
+ outcome: outcome ?? 'succeeded',
1719
+ protocol: 'V2',
1720
+ };
1721
+ this.protocolV2UiInteraction = undefined;
1722
+ return metadata;
1723
+ }
1724
+
1725
+ hasOpenProtocolV2UiInteraction() {
1726
+ return this.protocolV2UiInteraction?.opened === true;
1727
+ }
1728
+
1530
1729
  supportUnlockVersionRange(): DeviceFirmwareRange {
1531
1730
  // This range applies to Protocol V1 Pro devices; Pro2 has a dedicated unlock flow.
1532
1731
  return {
@@ -1536,10 +1735,33 @@ export class Device extends EventEmitter {
1536
1735
  };
1537
1736
  }
1538
1737
 
1539
- async unlockDevice(pinType: DeviceSessionPinType = DeviceSessionPinType.Main) {
1738
+ async unlockDevice(
1739
+ pinType?: DeviceSessionPinType,
1740
+ options?: ProtocolV2UiEventMetadata & { emitUiEvent?: boolean }
1741
+ ) {
1540
1742
  if (this.isProtocolV2()) {
1743
+ const requestedPinType = pinType ?? DeviceSessionPinType.Main;
1744
+ const interaction =
1745
+ options?.interaction ??
1746
+ (options?.emitUiEvent === false
1747
+ ? undefined
1748
+ : this.createProtocolV2UiPhaseMetadata('pin', 'start'));
1749
+ if (options?.emitUiEvent !== false) {
1750
+ this.emit(DEVICE.PIN_ON_DEVICE, this, requestedPinType, {
1751
+ source: options?.source ?? 'unlock-coordinator',
1752
+ reason: options?.reason ?? 'device-unlock',
1753
+ deviceOnly: options?.deviceOnly ?? true,
1754
+ completion: options?.completion,
1755
+ method: options?.method,
1756
+ page: options?.page,
1757
+ operation: options?.operation,
1758
+ interaction: options?.interaction ?? interaction,
1759
+ });
1760
+ }
1541
1761
  try {
1542
- await this.commands.typedCall('DeviceSessionAskPin', 'Success', { type: pinType });
1762
+ await this.commands.typedCall('DeviceSessionAskPin', 'Success', {
1763
+ type: requestedPinType,
1764
+ });
1543
1765
  } catch (error) {
1544
1766
  const errorText =
1545
1767
  error instanceof Error
@@ -1551,6 +1773,11 @@ export class Device extends EventEmitter {
1551
1773
  throw error;
1552
1774
  }
1553
1775
 
1776
+ const completion = interaction ? this.completeProtocolV2UiPhase(interaction) : undefined;
1777
+ if (completion) {
1778
+ this.emit(DEVICE.PIN_ON_DEVICE_COMPLETE, this, completion);
1779
+ }
1780
+
1554
1781
  const status = await requestProtocolV2DeviceStatus({ commands: this.commands });
1555
1782
  return this.updateProtocolV2Status(status);
1556
1783
  }
@@ -1612,7 +1839,8 @@ export class Device extends EventEmitter {
1612
1839
  async checkPassphraseStateSafety(
1613
1840
  passphraseState?: string,
1614
1841
  useEmptyPassphrase?: boolean,
1615
- skipPassphraseCheck?: boolean
1842
+ skipPassphraseCheck?: boolean,
1843
+ deriveCardano?: boolean
1616
1844
  ) {
1617
1845
  if (this.isUnacquired()) return false;
1618
1846
 
@@ -1621,6 +1849,8 @@ export class Device extends EventEmitter {
1621
1849
  await getPassphraseStateWithRefreshDeviceInfo(this, {
1622
1850
  expectPassphraseState: expectedPassphraseState,
1623
1851
  onlyMainPin: useEmptyPassphrase,
1852
+ deriveCardano,
1853
+ rejectAttachPinForMainWallet: useEmptyPassphrase === true,
1624
1854
  });
1625
1855
 
1626
1856
  // Main wallet and unlock Attach Pin, throw safe error
@@ -54,9 +54,19 @@ const DEVICE_SESSION_CALLS = new Set([
54
54
  'DeviceSessionAskPin',
55
55
  'DeviceSessionAskPassphrase',
56
56
  ]);
57
+ const DEVICE_CONTROL_CALLS = new Set([
58
+ 'DeviceReboot',
59
+ 'DeviceSettingsGet',
60
+ 'DeviceSettingsSet',
61
+ 'DeviceSettingsPageShow',
62
+ 'DeviceCertificateWrite',
63
+ 'DeviceCertificateRead',
64
+ 'DeviceCertificateSign',
65
+ 'DeviceMiscUsbMscControl',
66
+ ]);
57
67
 
58
- // Protocol V2 subcodes are domain-scoped, so cross-domain cancellation fallback
59
- // must use explicit firmware cancellation messages instead of a global number map.
68
+ // Older Protocol V2 firmware may omit the cancellation subcode, so retain a
69
+ // narrow message fallback for compatibility.
60
70
  const isProtocolV2ActionCancelledMessage = (message: string) =>
61
71
  /^(?:cancel(?:led|ed)(?: on device)?|confirm dismissed|user cancel(?:led|ed)(?:\s+.*)?)$/i.test(
62
72
  message
@@ -435,7 +445,13 @@ export class DeviceCommands {
435
445
  if (!shouldReduceDebugForCall(callType)) {
436
446
  Log.debug('_filterCommonTypes: ', {
437
447
  request: callType,
438
- response: res.type,
448
+ response:
449
+ callType === 'DeviceFirmwareUpdateStatusGet'
450
+ ? {
451
+ type: res.type,
452
+ message: getSafeTransportLogPayload(res.message, res.type),
453
+ }
454
+ : res.type,
439
455
  });
440
456
  }
441
457
  } catch (error) {
@@ -493,6 +509,10 @@ export class DeviceCommands {
493
509
  const normalizedMessage = message?.trim() ?? '';
494
510
  const isProtocolV2ActionCancelledFailure =
495
511
  this.device.isProtocolV2() && isProtocolV2ActionCancelledMessage(normalizedMessage);
512
+ const isProtocolV2DeviceBusyFailure =
513
+ this.device.isProtocolV2() &&
514
+ DEVICE_CONTROL_CALLS.has(callType) &&
515
+ subcode === DeviceErrorCode.DeviceError_Busy;
496
516
  const isLegacyProtocolV2LockedFailure =
497
517
  this.device.isProtocolV2() && /^device (?:is )?locked$/i.test(normalizedMessage);
498
518
  if (
@@ -550,8 +570,15 @@ export class DeviceCommands {
550
570
  subcode,
551
571
  firmwareMessage: message,
552
572
  });
573
+ } else if (isProtocolV2DeviceBusyFailure) {
574
+ error = ERRORS.TypedError(HardwareErrorCode.DeviceBusy, message, {
575
+ failureCode: code,
576
+ subcode,
577
+ firmwareMessage: message,
578
+ });
553
579
  } else if (
554
- subcode === DeviceErrorCode.DeviceError_ActionCancelled ||
580
+ (DEVICE_CONTROL_CALLS.has(callType) &&
581
+ subcode === DeviceErrorCode.DeviceError_ActionCancelled) ||
555
582
  isProtocolV2ActionCancelledFailure
556
583
  ) {
557
584
  error = ERRORS.TypedError(HardwareErrorCode.ActionCancelled, message, {