@onekeyfe/hd-core 1.2.0-alpha.34 → 1.2.0-alpha.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +74 -0
  2. package/__tests__/DeviceCommands.test.ts +84 -0
  3. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  4. package/__tests__/device-settings.test.ts +3 -0
  5. package/__tests__/device-wallet-session-store.test.ts +80 -7
  6. package/__tests__/evmSignTransaction.test.ts +69 -0
  7. package/__tests__/get-device-state.test.ts +180 -40
  8. package/__tests__/open-wallet-session.test.ts +314 -55
  9. package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
  10. package/__tests__/protocol-v2.test.ts +768 -139
  11. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  12. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  13. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  14. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  15. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  16. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  17. package/dist/api/evm/protocol.d.ts +3 -0
  18. package/dist/api/evm/protocol.d.ts.map +1 -0
  19. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +1 -1
  20. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  21. package/dist/core/deviceEventRegistration.d.ts +2 -0
  22. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/device/Device.d.ts +25 -6
  25. package/dist/device/Device.d.ts.map +1 -1
  26. package/dist/device/DeviceCommands.d.ts.map +1 -1
  27. package/dist/device/DeviceWalletSessionStore.d.ts +0 -1
  28. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -1
  29. package/dist/events/device.d.ts +4 -0
  30. package/dist/events/device.d.ts.map +1 -1
  31. package/dist/events/ui-request.d.ts +27 -2
  32. package/dist/events/ui-request.d.ts.map +1 -1
  33. package/dist/index.d.ts +63 -12
  34. package/dist/index.js +1164 -904
  35. package/dist/protocols/protocol-v2/features.d.ts +17 -6
  36. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  37. package/dist/protocols/protocol-v2/index.d.ts +2 -2
  38. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  39. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts +1 -0
  40. package/dist/protocols/protocol-v2/settingsUnlockPolicy.d.ts.map +1 -1
  41. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  42. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  43. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  44. package/dist/protocols/protocol-v2/walletSession.d.ts +5 -1
  45. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  46. package/dist/types/api/openWalletSession.d.ts +1 -1
  47. package/dist/types/api/openWalletSession.d.ts.map +1 -1
  48. package/dist/types/api/protocolV2.d.ts +2 -2
  49. package/dist/types/api/protocolV2.d.ts.map +1 -1
  50. package/dist/types/device.d.ts +2 -1
  51. package/dist/types/device.d.ts.map +1 -1
  52. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  53. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  54. package/dist/utils/patch.d.ts +1 -1
  55. package/dist/utils/patch.d.ts.map +1 -1
  56. package/package.json +4 -4
  57. package/src/api/BaseMethod.ts +1 -1
  58. package/src/api/FirmwareUpdateV4.ts +39 -14
  59. package/src/api/GetPassphraseState.ts +17 -0
  60. package/src/api/OpenWalletSession.ts +31 -8
  61. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +24 -2
  62. package/src/api/device/DeviceUnlock.ts +1 -1
  63. package/src/api/evm/EVMGetAddress.ts +2 -2
  64. package/src/api/evm/EVMGetPublicKey.ts +2 -2
  65. package/src/api/evm/EVMSignMessage.ts +2 -2
  66. package/src/api/evm/EVMSignTransaction.ts +2 -2
  67. package/src/api/evm/EVMSignTypedData.ts +35 -45
  68. package/src/api/evm/EVMVerifyMessage.ts +2 -2
  69. package/src/api/evm/protocol.ts +6 -0
  70. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +2 -2
  71. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +1 -1
  72. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  73. package/src/core/deviceEventRegistration.ts +4 -0
  74. package/src/core/index.ts +46 -4
  75. package/src/data/messages/messages-protocol-v2.json +408 -485
  76. package/src/device/Device.ts +266 -43
  77. package/src/device/DeviceCommands.ts +12 -2
  78. package/src/device/DeviceWalletSessionStore.ts +0 -14
  79. package/src/deviceProfile/buildDeviceFeatures.ts +1 -1
  80. package/src/events/device.ts +4 -0
  81. package/src/events/ui-request.ts +32 -2
  82. package/src/protocols/protocol-v2/features.ts +65 -16
  83. package/src/protocols/protocol-v2/index.ts +5 -0
  84. package/src/protocols/protocol-v2/settingsUnlockPolicy.ts +8 -3
  85. package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
  86. package/src/protocols/protocol-v2/unlockRetry.ts +51 -18
  87. package/src/protocols/protocol-v2/walletSession.ts +172 -64
  88. package/src/types/api/openWalletSession.ts +1 -1
  89. package/src/types/api/protocolV2.ts +2 -2
  90. package/src/types/device.ts +2 -0
  91. package/src/utils/deviceFeaturesUtils.ts +4 -0
@@ -55,6 +55,7 @@ import { DataManager } from '../data-manager';
55
55
  import TransportManager from '../data-manager/TransportManager';
56
56
  import { toHardened } from '../api/helpers/pathUtils';
57
57
  import {
58
+ PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE,
58
59
  PROTOCOL_V2_FEATURES_DEVICE_INFO_REQUEST,
59
60
  PROTOCOL_V2_FULL_DEVICE_INFO_REQUEST,
60
61
  PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
@@ -62,21 +63,27 @@ import {
62
63
  getProtocolV2RuntimeMode,
63
64
  requestProtocolV2DeviceInfo,
64
65
  requestProtocolV2DeviceStatus,
66
+ requestProtocolV2ProtocolInfo,
67
+ supportsProtocolV2Message,
65
68
  } from '../protocols/protocol-v2/features';
66
69
  import { buildProtocolV1FeaturesPayload } from '../deviceProfile';
70
+ import { resolveProtocolV2DeviceIdentity } from '../deviceProfile/protocolV2DeviceIdentity';
67
71
 
68
72
  import type { PROTO } from '../constants';
69
73
  import type { DeviceStateReadOptions } from '../types/api/getDeviceState';
70
74
  import type {
71
75
  DeviceButtonRequestPayload,
72
76
  DeviceFeaturesPayload,
77
+ HardwareUiInteractionMeta,
73
78
  PassphraseRequestPayload,
79
+ ProtocolV2UiEventMetadata,
74
80
  } from '../events';
75
81
  import type { PassphrasePromptResponse } from './DeviceCommands';
76
82
  import type { Deferred, HardwareConnectProtocol } from '@onekeyfe/hd-shared';
77
83
  import type {
78
84
  OneKeyDeviceInfo as DeviceDescriptor,
79
85
  DeviceStatus,
86
+ ProtocolInfo,
80
87
  ProtocolV2DeviceInfo,
81
88
  Success,
82
89
  } from '@onekeyfe/hd-transport';
@@ -105,8 +112,30 @@ const parseRunOptions = (options?: RunOptions): RunOptions => {
105
112
 
106
113
  const Log = getLogger(LoggerNames.Device);
107
114
 
115
+ const isProtocolV2DeviceStatusUnsupportedError = (error: unknown) => {
116
+ if (error instanceof HardwareError) {
117
+ if (error.errorCode === HardwareErrorCode.DeviceNotSupportMethod) {
118
+ return true;
119
+ }
120
+ if (error.params?.failureCode === 'Failure_UnexpectedMessage') {
121
+ return true;
122
+ }
123
+ }
124
+
125
+ const message =
126
+ error instanceof Error
127
+ ? error.message
128
+ : String((error as { message?: unknown } | null)?.message ?? error ?? '');
129
+ return (
130
+ /^Failure_UnexpectedMessage(?:,|\b)/i.test(message) ||
131
+ /\b(?:unsupported message|handler not registered|message handler not found)\b/i.test(message)
132
+ );
133
+ };
134
+
108
135
  export interface DeviceEvents {
109
136
  [DEVICE.PIN]: [Device, PROTO.PinMatrixRequestType | undefined, (err: any, pin: string) => void];
137
+ [DEVICE.PIN_ON_DEVICE]: [Device, DeviceSessionPinType, ProtocolV2UiEventMetadata?];
138
+ [DEVICE.PIN_ON_DEVICE_COMPLETE]: [Device, HardwareUiInteractionMeta];
110
139
  [DEVICE.PASSPHRASE_ON_DEVICE]: [Device, PassphraseRequestPayload?];
111
140
  [DEVICE.ATTACH_PIN_ON_DEVICE]: [Device, PassphraseRequestPayload?];
112
141
  [DEVICE.BUTTON]: [Device, DeviceButtonRequestPayload];
@@ -205,6 +234,24 @@ export class Device extends EventEmitter {
205
234
  /** Force the next initialization to reload DeviceInfo after reconnect or reboot. */
206
235
  private protocolV2StateNeedsReload = false;
207
236
 
237
+ /** Runtime context negotiated once per active Protocol V2 link. */
238
+ private protocolV2RuntimeContext?: ProtocolInfo;
239
+
240
+ /** Coalesces concurrent first-use runtime negotiation on the same active link. */
241
+ private protocolV2RuntimeContextPromise?: Promise<ProtocolInfo>;
242
+
243
+ /** Invalidates an in-flight runtime-context response without adding a transport epoch. */
244
+ private protocolV2RuntimeContextRequestToken?: object;
245
+
246
+ private protocolV2UiInteraction?: {
247
+ interactionId: string;
248
+ phaseCounter: number;
249
+ sequence: number;
250
+ opened: boolean;
251
+ };
252
+
253
+ private protocolV2UiInteractionCounter = 0;
254
+
208
255
  get state() {
209
256
  return this.stateStore.getState();
210
257
  }
@@ -835,8 +882,8 @@ export class Device extends EventEmitter {
835
882
 
836
883
  const expectedDeviceId = options?.deviceId;
837
884
  if (expectedDeviceId) {
838
- // Verify the physical device with a read-only Features request. An
839
- // empty-context Initialize would invalidate a cached V1 hidden-wallet session.
885
+ // 先只读校验物理设备身份;钱包上下文仍由下方携带完整参数的
886
+ // Initialize 选择,避免标准钱包请求复用此前的隐藏钱包上下文。
840
887
  this.passphraseState = undefined;
841
888
  const { message } = await this.commands.typedCall('GetFeatures', 'Features', {});
842
889
  this._updateFeatures(message);
@@ -864,15 +911,7 @@ export class Device extends EventEmitter {
864
911
  payload.derive_cardano = true;
865
912
  }
866
913
 
867
- const requiresWalletInitialize =
868
- !expectedDeviceId ||
869
- Boolean(internalState) ||
870
- Boolean(options?.passphraseState) ||
871
- options?.deriveCardano === true ||
872
- options?.initSession === true;
873
- if (requiresWalletInitialize) {
874
- await callInitialize(payload, options?.initSession);
875
- }
914
+ await callInitialize(payload, options?.initSession);
876
915
  } catch (error) {
877
916
  Log.error('Initialization failed:', error);
878
917
  throw error;
@@ -970,13 +1009,7 @@ export class Device extends EventEmitter {
970
1009
  }
971
1010
 
972
1011
  if (refresh.has('status') && !initializedWithDeviceInfo) {
973
- const deviceInfo =
974
- refreshedDeviceInfo ??
975
- (await requestProtocolV2DeviceInfo({
976
- commands: this.commands,
977
- request: getProtocolV2DeviceInfoRequest(),
978
- }));
979
- await this.probeProtocolV2RuntimeState(deviceInfo);
1012
+ await this.probeProtocolV2RuntimeState(refreshedDeviceInfo);
980
1013
  }
981
1014
 
982
1015
  if (refresh.has('settings') && this.state?.status.mode === 'normal') {
@@ -1047,7 +1080,83 @@ export class Device extends EventEmitter {
1047
1080
  return this.features;
1048
1081
  }
1049
1082
 
1050
- async probeProtocolV2RuntimeState(deviceInfo: ProtocolV2DeviceInfo, timeoutMs?: number) {
1083
+ async ensureProtocolV2RuntimeContext(timeoutMs?: number): Promise<ProtocolInfo> {
1084
+ const cachedProtocolInfo =
1085
+ this.protocolV2RuntimeContext ??
1086
+ (!this.protocolV2StateNeedsReload
1087
+ ? this.state?.raw?.protocolV2ProtocolInfo ?? undefined
1088
+ : undefined);
1089
+ if (cachedProtocolInfo) {
1090
+ this.protocolV2RuntimeContext = cachedProtocolInfo;
1091
+ return cachedProtocolInfo;
1092
+ }
1093
+
1094
+ if (this.protocolV2RuntimeContextPromise) {
1095
+ return this.protocolV2RuntimeContextPromise;
1096
+ }
1097
+
1098
+ const requestToken = {};
1099
+ const pendingRequest = (async () => {
1100
+ const protocolInfo = await requestProtocolV2ProtocolInfo({
1101
+ commands: this.commands,
1102
+ timeoutMs,
1103
+ });
1104
+ if (this.protocolV2RuntimeContextRequestToken !== requestToken) {
1105
+ throw ERRORS.TypedError(
1106
+ HardwareErrorCode.DeviceInitializeFailed,
1107
+ 'Protocol V2 runtime context was invalidated while loading.'
1108
+ );
1109
+ }
1110
+ this.protocolV2RuntimeContext = protocolInfo;
1111
+ return protocolInfo;
1112
+ })();
1113
+ this.protocolV2RuntimeContextRequestToken = requestToken;
1114
+ this.protocolV2RuntimeContextPromise = pendingRequest;
1115
+
1116
+ try {
1117
+ return await pendingRequest;
1118
+ } finally {
1119
+ if (this.protocolV2RuntimeContextPromise === pendingRequest) {
1120
+ this.protocolV2RuntimeContextPromise = undefined;
1121
+ }
1122
+ if (this.protocolV2RuntimeContextRequestToken === requestToken) {
1123
+ this.protocolV2RuntimeContextRequestToken = undefined;
1124
+ }
1125
+ }
1126
+ }
1127
+
1128
+ async probeProtocolV2RuntimeState(deviceInfo?: ProtocolV2DeviceInfo, timeoutMs?: number) {
1129
+ const protocolInfo = await this.ensureProtocolV2RuntimeContext(timeoutMs);
1130
+ const runtimeMode = getProtocolV2RuntimeMode(protocolInfo);
1131
+ const runtimeDeviceInfo = deviceInfo ?? this.state?.raw?.protocolV2DeviceInfo;
1132
+ const protocolV2DeviceType = runtimeDeviceInfo
1133
+ ? resolveProtocolV2DeviceIdentity(runtimeDeviceInfo.hw?.Device_type).deviceType
1134
+ : this.getCurrentDeviceType();
1135
+ if (runtimeMode === 'romloader' && protocolV2DeviceType !== EDeviceType.Pro2) {
1136
+ throw ERRORS.TypedError(
1137
+ HardwareErrorCode.DeviceInitializeFailed,
1138
+ 'Protocol V2 romloader mode is only supported for Pro2.'
1139
+ );
1140
+ }
1141
+ const deviceStatusSupported = supportsProtocolV2Message(
1142
+ protocolInfo,
1143
+ PROTOCOL_V2_DEVICE_STATUS_GET_MESSAGE_TYPE
1144
+ );
1145
+
1146
+ if (runtimeMode === 'bootloader' || runtimeMode === 'romloader') {
1147
+ return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
1148
+ }
1149
+
1150
+ if (!deviceStatusSupported) {
1151
+ if (runtimeMode === 'normal') {
1152
+ return this.updateProtocolV2Features(deviceInfo, null, runtimeMode, protocolInfo);
1153
+ }
1154
+ throw ERRORS.TypedError(
1155
+ HardwareErrorCode.DeviceInitializeFailed,
1156
+ `Unknown Protocol V2 build fingerprint without DeviceStatusGet capability: ${protocolInfo.build_fingerprint}`
1157
+ );
1158
+ }
1159
+
1051
1160
  let deviceStatus: DeviceStatus;
1052
1161
  try {
1053
1162
  deviceStatus = await requestProtocolV2DeviceStatus({
@@ -1055,33 +1164,28 @@ export class Device extends EventEmitter {
1055
1164
  timeoutMs,
1056
1165
  });
1057
1166
  } catch (error) {
1058
- const runtimeMode = getProtocolV2RuntimeMode({
1059
- deviceInfo,
1060
- deviceStatusAvailable: false,
1061
- });
1062
- Log.debug('Protocol V2 DeviceStatusGet unavailable; use temporary loader fallback', {
1063
- runtimeMode,
1064
- error: error instanceof Error ? error.message : String(error),
1065
- });
1066
- return this.updateProtocolV2Features(deviceInfo, null, runtimeMode);
1167
+ if (runtimeMode === undefined && isProtocolV2DeviceStatusUnsupportedError(error)) {
1168
+ return this.updateProtocolV2Features(deviceInfo, null, 'bootloader', protocolInfo);
1169
+ }
1170
+ throw error;
1067
1171
  }
1068
- return this.updateProtocolV2Features(deviceInfo, deviceStatus, 'normal');
1172
+ return this.updateProtocolV2Features(deviceInfo, deviceStatus, 'normal', protocolInfo);
1069
1173
  }
1070
1174
 
1071
1175
  updateProtocolV2Features(
1072
1176
  deviceInfo?: ProtocolV2DeviceInfo,
1073
1177
  deviceStatus?: DeviceStatus | null,
1074
- runtimeMode?: ProtocolV2RuntimeMode
1178
+ runtimeMode?: ProtocolV2RuntimeMode,
1179
+ protocolInfo?: ProtocolInfo
1075
1180
  ) {
1076
1181
  const previousDeviceId = this.getCurrentDeviceId();
1077
- const resolvedMode =
1078
- runtimeMode ??
1079
- (deviceStatus
1080
- ? getProtocolV2RuntimeMode({ deviceInfo, deviceStatusAvailable: true })
1081
- : this.state?.status.mode);
1182
+ const resolvedMode = runtimeMode ?? (deviceStatus ? 'normal' : this.state?.status.mode);
1082
1183
  if (deviceInfo) {
1083
1184
  this.updateState(mapProtocolV2DeviceInfoToState(deviceInfo, resolvedMode), 'device-info');
1084
1185
  }
1186
+ if (protocolInfo) {
1187
+ this.updateState({ raw: { protocolV2ProtocolInfo: protocolInfo } }, 'device-info');
1188
+ }
1085
1189
  if (deviceStatus) {
1086
1190
  this.updateState(mapProtocolV2DeviceStatusToState(deviceStatus), 'device-status');
1087
1191
  }
@@ -1102,6 +1206,9 @@ export class Device extends EventEmitter {
1102
1206
  this.deviceAcquired = false;
1103
1207
  if (!this.isProtocolV2()) return;
1104
1208
  this.protocolV2StateNeedsReload = true;
1209
+ this.protocolV2RuntimeContext = undefined;
1210
+ this.protocolV2RuntimeContextPromise = undefined;
1211
+ this.protocolV2RuntimeContextRequestToken = undefined;
1105
1212
  this.clearPreInitialized();
1106
1213
  }
1107
1214
 
@@ -1110,9 +1217,14 @@ export class Device extends EventEmitter {
1110
1217
  if (deviceId) {
1111
1218
  deviceWalletSessionStore.deleteDevice(deviceId);
1112
1219
  }
1113
- if (this.isProtocolV2() && this.originalDescriptor.path !== deviceId) {
1114
- deviceWalletSessionStore.deleteDevice(this.originalDescriptor.path);
1220
+ if (this.isProtocolV2()) {
1221
+ if (this.originalDescriptor.path !== deviceId) {
1222
+ deviceWalletSessionStore.deleteDevice(this.originalDescriptor.path);
1223
+ }
1115
1224
  this.protocolV2StateNeedsReload = true;
1225
+ this.protocolV2RuntimeContext = undefined;
1226
+ this.protocolV2RuntimeContextPromise = undefined;
1227
+ this.protocolV2RuntimeContextRequestToken = undefined;
1116
1228
  }
1117
1229
 
1118
1230
  this.passphraseState = undefined;
@@ -1125,6 +1237,9 @@ export class Device extends EventEmitter {
1125
1237
  if (!this.isProtocolV2()) return;
1126
1238
 
1127
1239
  this.protocolV2StateNeedsReload = true;
1240
+ this.protocolV2RuntimeContext = undefined;
1241
+ this.protocolV2RuntimeContextPromise = undefined;
1242
+ this.protocolV2RuntimeContextRequestToken = undefined;
1128
1243
  this.clearPreInitialized();
1129
1244
  let loaderMode: 'bootloader' | 'romloader' | undefined;
1130
1245
  if (rebootType === DeviceRebootType.Bootloader) {
@@ -1150,7 +1265,7 @@ export class Device extends EventEmitter {
1150
1265
  attachToPinEnabled: null,
1151
1266
  unlockedAttachPin: null,
1152
1267
  },
1153
- raw: { protocolV2DeviceStatus: null },
1268
+ raw: { protocolV2ProtocolInfo: null, protocolV2DeviceStatus: null },
1154
1269
  },
1155
1270
  'transport-reconnect'
1156
1271
  );
@@ -1160,7 +1275,7 @@ export class Device extends EventEmitter {
1160
1275
  this.updateState(
1161
1276
  {
1162
1277
  status: { mode: 'normal', unlocked: null },
1163
- raw: { protocolV2DeviceStatus: null },
1278
+ raw: { protocolV2ProtocolInfo: null, protocolV2DeviceStatus: null },
1164
1279
  },
1165
1280
  'transport-reconnect'
1166
1281
  );
@@ -1406,7 +1521,16 @@ export class Device extends EventEmitter {
1406
1521
 
1407
1522
  isBootloader() {
1408
1523
  if (!this.state) return undefined;
1409
- return this.state.status.mode === 'bootloader' || this.state.status.mode === 'romloader';
1524
+ return this.state.status.mode === 'bootloader';
1525
+ }
1526
+
1527
+ isRomloader() {
1528
+ if (!this.state) return undefined;
1529
+ return (
1530
+ this.isProtocolV2() &&
1531
+ this.getCurrentDeviceType() === EDeviceType.Pro2 &&
1532
+ this.state.status.mode === 'romloader'
1533
+ );
1410
1534
  }
1411
1535
 
1412
1536
  isInitialized() {
@@ -1468,6 +1592,75 @@ export class Device extends EventEmitter {
1468
1592
  return res.message;
1469
1593
  }
1470
1594
 
1595
+ beginProtocolV2UiInteraction() {
1596
+ if (!this.isProtocolV2()) return;
1597
+ this.protocolV2UiInteraction = {
1598
+ interactionId: `${this.instanceId}:${Date.now()}:${++this.protocolV2UiInteractionCounter}`,
1599
+ phaseCounter: 0,
1600
+ sequence: 0,
1601
+ opened: false,
1602
+ };
1603
+ }
1604
+
1605
+ createProtocolV2UiPhaseMetadata(
1606
+ phase: HardwareUiInteractionMeta['phase'],
1607
+ transition: HardwareUiInteractionMeta['transition'],
1608
+ options?: {
1609
+ phaseId?: string;
1610
+ outcome?: HardwareUiInteractionMeta['outcome'];
1611
+ }
1612
+ ): HardwareUiInteractionMeta | undefined {
1613
+ if (!this.isProtocolV2()) return undefined;
1614
+ if (!this.protocolV2UiInteraction) this.beginProtocolV2UiInteraction();
1615
+
1616
+ const interaction = this.protocolV2UiInteraction;
1617
+ if (!interaction) return undefined;
1618
+ const phaseId =
1619
+ options?.phaseId ?? `${interaction.interactionId}:phase-${++interaction.phaseCounter}`;
1620
+ interaction.opened = true;
1621
+ interaction.sequence += 1;
1622
+
1623
+ return {
1624
+ interactionId: interaction.interactionId,
1625
+ phaseId,
1626
+ sequence: interaction.sequence,
1627
+ phase,
1628
+ transition,
1629
+ ...(options?.outcome ? { outcome: options.outcome } : {}),
1630
+ protocol: 'V2',
1631
+ };
1632
+ }
1633
+
1634
+ completeProtocolV2UiPhase(
1635
+ phase: HardwareUiInteractionMeta,
1636
+ outcome: HardwareUiInteractionMeta['outcome'] = 'succeeded'
1637
+ ) {
1638
+ return this.createProtocolV2UiPhaseMetadata(phase.phase, 'complete', {
1639
+ phaseId: phase.phaseId,
1640
+ outcome,
1641
+ });
1642
+ }
1643
+
1644
+ finishProtocolV2UiInteraction(outcome: HardwareUiInteractionMeta['outcome'] = 'succeeded') {
1645
+ const interaction = this.protocolV2UiInteraction;
1646
+ if (!interaction?.opened) {
1647
+ this.protocolV2UiInteraction = undefined;
1648
+ return undefined;
1649
+ }
1650
+
1651
+ const phaseId = `${interaction.interactionId}:phase-${Math.max(interaction.phaseCounter, 1)}`;
1652
+ const metadata = this.createProtocolV2UiPhaseMetadata('processing', 'finish', {
1653
+ phaseId,
1654
+ outcome,
1655
+ });
1656
+ this.protocolV2UiInteraction = undefined;
1657
+ return metadata;
1658
+ }
1659
+
1660
+ hasOpenProtocolV2UiInteraction() {
1661
+ return this.protocolV2UiInteraction?.opened === true;
1662
+ }
1663
+
1471
1664
  supportUnlockVersionRange(): DeviceFirmwareRange {
1472
1665
  // This range applies to Protocol V1 Pro devices; Pro2 has a dedicated unlock flow.
1473
1666
  return {
@@ -1477,10 +1670,33 @@ export class Device extends EventEmitter {
1477
1670
  };
1478
1671
  }
1479
1672
 
1480
- async unlockDevice(pinType: DeviceSessionPinType = DeviceSessionPinType.Main) {
1673
+ async unlockDevice(
1674
+ pinType?: DeviceSessionPinType,
1675
+ options?: ProtocolV2UiEventMetadata & { emitUiEvent?: boolean }
1676
+ ) {
1481
1677
  if (this.isProtocolV2()) {
1678
+ const requestedPinType = pinType ?? DeviceSessionPinType.Main;
1679
+ const interaction =
1680
+ options?.interaction ??
1681
+ (options?.emitUiEvent === false
1682
+ ? undefined
1683
+ : this.createProtocolV2UiPhaseMetadata('pin', 'start'));
1684
+ if (options?.emitUiEvent !== false) {
1685
+ this.emit(DEVICE.PIN_ON_DEVICE, this, requestedPinType, {
1686
+ source: options?.source ?? 'unlock-coordinator',
1687
+ reason: options?.reason ?? 'device-unlock',
1688
+ deviceOnly: options?.deviceOnly ?? true,
1689
+ completion: options?.completion,
1690
+ method: options?.method,
1691
+ page: options?.page,
1692
+ operation: options?.operation,
1693
+ interaction: options?.interaction ?? interaction,
1694
+ });
1695
+ }
1482
1696
  try {
1483
- await this.commands.typedCall('DeviceSessionAskPin', 'Success', { type: pinType });
1697
+ await this.commands.typedCall('DeviceSessionAskPin', 'Success', {
1698
+ type: requestedPinType,
1699
+ });
1484
1700
  } catch (error) {
1485
1701
  const errorText =
1486
1702
  error instanceof Error
@@ -1492,6 +1708,11 @@ export class Device extends EventEmitter {
1492
1708
  throw error;
1493
1709
  }
1494
1710
 
1711
+ const completion = interaction ? this.completeProtocolV2UiPhase(interaction) : undefined;
1712
+ if (completion) {
1713
+ this.emit(DEVICE.PIN_ON_DEVICE_COMPLETE, this, completion);
1714
+ }
1715
+
1495
1716
  const status = await requestProtocolV2DeviceStatus({ commands: this.commands });
1496
1717
  return this.updateProtocolV2Status(status);
1497
1718
  }
@@ -1553,7 +1774,8 @@ export class Device extends EventEmitter {
1553
1774
  async checkPassphraseStateSafety(
1554
1775
  passphraseState?: string,
1555
1776
  useEmptyPassphrase?: boolean,
1556
- skipPassphraseCheck?: boolean
1777
+ skipPassphraseCheck?: boolean,
1778
+ deriveCardano?: boolean
1557
1779
  ) {
1558
1780
  if (this.isUnacquired()) return false;
1559
1781
 
@@ -1562,6 +1784,7 @@ export class Device extends EventEmitter {
1562
1784
  await getPassphraseStateWithRefreshDeviceInfo(this, {
1563
1785
  expectPassphraseState: expectedPassphraseState,
1564
1786
  onlyMainPin: useEmptyPassphrase,
1787
+ deriveCardano,
1565
1788
  });
1566
1789
 
1567
1790
  // Main wallet and unlock Attach Pin, throw safe error
@@ -6,6 +6,7 @@ import {
6
6
  type Messages,
7
7
  type Transport,
8
8
  type TransportCallOptions,
9
+ getSafeTransportLogPayload,
9
10
  } from '@onekeyfe/hd-transport';
10
11
 
11
12
  import TransportManager from '../data-manager/TransportManager';
@@ -282,7 +283,11 @@ export class DeviceCommands {
282
283
  ): Promise<DefaultMessageResponse> {
283
284
  const shouldReduceDebug = shouldReduceDebugForCall(type);
284
285
  if (!shouldReduceDebug) {
285
- Log.debug('[DeviceCommands] [call] Sending', type);
286
+ Log.debug(
287
+ '[DeviceCommands] [call] Sending',
288
+ type,
289
+ getSafeTransportLogPayload(msg ?? {}, type)
290
+ );
286
291
  }
287
292
 
288
293
  try {
@@ -290,13 +295,18 @@ export class DeviceCommands {
290
295
  this.callPromise = promise;
291
296
  const res = await promise;
292
297
  if (!shouldReduceDebug) {
293
- LogCore.debug('[DeviceCommands] [call] Received', res.type);
298
+ LogCore.debug(
299
+ '[DeviceCommands] [call] Received',
300
+ res.type,
301
+ getSafeTransportLogPayload(res.message, res.type)
302
+ );
294
303
  }
295
304
  return res;
296
305
  } catch (error) {
297
306
  LogCore.debug('[DeviceCommands] [call] Received error', {
298
307
  request: type,
299
308
  errorCode: error?.errorCode,
309
+ response: getSafeTransportLogPayload(error?.response?.data, type),
300
310
  });
301
311
  if (error.errorCode === HardwareErrorCode.BleDeviceBondError) {
302
312
  return {
@@ -1,6 +1,4 @@
1
1
  export class DeviceWalletSessionStore {
2
- private static readonly MAX_SESSIONS_PER_DEVICE = 3;
3
-
4
2
  private readonly walletSessions = new Map<string, Map<string, string>>();
5
3
 
6
4
  private readonly standardWalletSessions = new Map<
@@ -22,18 +20,6 @@ export class DeviceWalletSessionStore {
22
20
  deviceSessions = new Map<string, string>();
23
21
  this.walletSessions.set(deviceKey, deviceSessions);
24
22
  }
25
- if (
26
- !deviceSessions.has(passphraseState) &&
27
- deviceSessions.size >= DeviceWalletSessionStore.MAX_SESSIONS_PER_DEVICE
28
- ) {
29
- const oldestPassphraseState = deviceSessions.keys().next().value as string | undefined;
30
- if (oldestPassphraseState) {
31
- deviceSessions.delete(oldestPassphraseState);
32
- if (this.standardWalletSessions.get(deviceKey)?.passphraseState === oldestPassphraseState) {
33
- this.standardWalletSessions.delete(deviceKey);
34
- }
35
- }
36
- }
37
23
  deviceSessions.set(passphraseState, sessionId);
38
24
  }
39
25
 
@@ -260,7 +260,7 @@ export const buildProtocolV2FeaturesPayload = ({
260
260
  const attachToPinEnabled = status?.attach_to_pin_enabled ?? cached?.attachToPinEnabled ?? null;
261
261
  const unlockedAttachPin =
262
262
  status?.unlocked_by_attach_to_pin ?? cached?.unlockedAttachPin ?? undefined;
263
- const bootloaderMode = runtimeMode === 'bootloader';
263
+ const bootloaderMode = runtimeMode === 'bootloader' || runtimeMode === 'romloader';
264
264
  let mode: DeviceFeaturesMode = 'unknown';
265
265
  if (runtimeMode) {
266
266
  mode = runtimeMode;
@@ -1,4 +1,5 @@
1
1
  import type { MessageFactoryFn } from './utils';
2
+ import type { HardwareUiInteractionMeta } from './ui-request';
2
3
  import type { PROTO } from '../constants';
3
4
  import type {
4
5
  KnownDevice as Device,
@@ -26,6 +27,8 @@ export const DEVICE = {
26
27
  // onekey-transport events in protobuf format
27
28
  BUTTON: 'button',
28
29
  PIN: 'pin',
30
+ PIN_ON_DEVICE: 'pin_on_device',
31
+ PIN_ON_DEVICE_COMPLETE: 'pin_on_device_complete',
29
32
  PASSPHRASE: 'passphrase',
30
33
  PASSPHRASE_ON_DEVICE: 'passphrase_on_device',
31
34
  ATTACH_PIN_ON_DEVICE: 'attach_pin_on_device',
@@ -58,6 +61,7 @@ export type PassphraseRequestPayload = {
58
61
  source?: 'wallet-session-coordinator';
59
62
  reason?: 'open-wallet' | 'session-recovery';
60
63
  expectedPassphraseState?: string;
64
+ interaction?: HardwareUiInteractionMeta;
61
65
  };
62
66
 
63
67
  export interface DeviceButtonRequest {
@@ -56,6 +56,16 @@ export type ProtocolV2UiEventSource =
56
56
 
57
57
  export type ProtocolV2UiCompletion = 'page-accepted' | 'operation-completed';
58
58
 
59
+ export type HardwareUiInteractionMeta = {
60
+ interactionId: string;
61
+ phaseId: string;
62
+ sequence: number;
63
+ phase: 'pin' | 'passphrase' | 'passphrase-on-device' | 'button' | 'processing';
64
+ transition: 'start' | 'complete' | 'finish';
65
+ outcome?: 'submitted' | 'succeeded' | 'failed' | 'cancelled' | 'disconnected';
66
+ protocol: 'V2';
67
+ };
68
+
59
69
  export type ProtocolV2UiEventMetadata = {
60
70
  source?: ProtocolV2UiEventSource;
61
71
  reason?: string;
@@ -64,12 +74,29 @@ export type ProtocolV2UiEventMetadata = {
64
74
  method?: string;
65
75
  page?: string | number;
66
76
  operation?: string;
77
+ interaction?: HardwareUiInteractionMeta;
67
78
  };
68
79
 
80
+ export type UiRequestWindowClose =
81
+ | {
82
+ type: typeof UI_REQUEST.CLOSE_UI_WINDOW;
83
+ payload: HardwareUiInteractionMeta;
84
+ }
85
+ | {
86
+ type: typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW;
87
+ payload: HardwareUiInteractionMeta;
88
+ }
89
+ | {
90
+ type: typeof UI_REQUEST.CLOSE_UI_WINDOW;
91
+ payload?: undefined;
92
+ }
93
+ | {
94
+ type: typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW;
95
+ payload?: undefined;
96
+ };
97
+
69
98
  export interface UiRequestWithoutPayload {
70
99
  type:
71
- | typeof UI_REQUEST.CLOSE_UI_WINDOW
72
- | typeof UI_REQUEST.CLOSE_UI_PIN_WINDOW
73
100
  | typeof UI_REQUEST.BLUETOOTH_PERMISSION
74
101
  | typeof UI_REQUEST.BLUETOOTH_UNSUPPORTED
75
102
  | typeof UI_REQUEST.BLUETOOTH_POWERED_OFF
@@ -111,6 +138,7 @@ export interface UiRequestPassphrase {
111
138
  source?: 'wallet-session-coordinator';
112
139
  reason?: 'open-wallet' | 'session-recovery';
113
140
  expectedPassphraseState?: string;
141
+ interaction?: HardwareUiInteractionMeta;
114
142
  };
115
143
  }
116
144
 
@@ -121,6 +149,7 @@ export interface UiRequestPassphraseOnDevice {
121
149
  passphraseState?: string;
122
150
  source?: 'wallet-session-coordinator';
123
151
  reason?: 'open-wallet' | 'session-recovery';
152
+ interaction?: HardwareUiInteractionMeta;
124
153
  };
125
154
  }
126
155
 
@@ -191,6 +220,7 @@ export interface PreviousAddressResult {
191
220
 
192
221
  export type UiEvent =
193
222
  | UiRequestWithoutPayload
223
+ | UiRequestWindowClose
194
224
  | UiRequestDeviceAction
195
225
  | UiRequestButton
196
226
  | UiRequestPassphraseOnDevice