@onekeyfe/hd-core 1.2.2-alpha.8 → 1.2.3-alpha.1

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 (40) hide show
  1. package/__tests__/DeviceCommands.test.ts +254 -1
  2. package/__tests__/device-lifecycle-events.test.ts +83 -14
  3. package/__tests__/logBlockEvent.test.ts +45 -122
  4. package/__tests__/open-wallet-session.test.ts +139 -5
  5. package/__tests__/protocol-v2.test.ts +81 -2
  6. package/__tests__/sol-sign-offchain-message.test.ts +72 -0
  7. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  8. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  9. package/dist/api/UploadPortfolio.d.ts +1 -0
  10. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  11. package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
  12. package/dist/core/RequestQueue.d.ts +2 -0
  13. package/dist/core/RequestQueue.d.ts.map +1 -1
  14. package/dist/core/index.d.ts +2 -1
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/uiPromiseRegistry.d.ts +1 -1
  17. package/dist/device/DeviceCommands.d.ts +5 -4
  18. package/dist/device/DeviceCommands.d.ts.map +1 -1
  19. package/dist/events/logBlockEvent.d.ts.map +1 -1
  20. package/dist/index.d.ts +19 -12
  21. package/dist/index.js +171 -141
  22. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  23. package/dist/types/api/protocolV2.d.ts +3 -4
  24. package/dist/types/api/protocolV2.d.ts.map +1 -1
  25. package/dist/types/api/solSignOffchainMessage.d.ts +1 -0
  26. package/dist/types/api/solSignOffchainMessage.d.ts.map +1 -1
  27. package/dist/utils/patch.d.ts +1 -1
  28. package/package.json +4 -4
  29. package/src/api/FirmwareUpdateV4.ts +9 -4
  30. package/src/api/UploadPortfolio.ts +5 -4
  31. package/src/api/solana/SolSignOffchainMessage.ts +44 -4
  32. package/src/core/RequestQueue.ts +16 -1
  33. package/src/core/index.ts +43 -20
  34. package/src/data/messages/messages-protocol-v2.json +28 -33
  35. package/src/data/messages/messages.json +8 -5
  36. package/src/device/DeviceCommands.ts +29 -2
  37. package/src/events/logBlockEvent.ts +6 -75
  38. package/src/protocols/protocol-v2/walletSession.ts +11 -2
  39. package/src/types/api/protocolV2.ts +3 -4
  40. package/src/types/api/solSignOffchainMessage.ts +2 -0
@@ -56,11 +56,20 @@ export default class RequestQueue {
56
56
  return false;
57
57
  }
58
58
 
59
+ private isRequestForConnectId(request: RequestTask, connectId: string) {
60
+ const { method } = request;
61
+ return (
62
+ method.connectId === connectId ||
63
+ method.device?.mainId === connectId ||
64
+ method.device?.getConnectId() === connectId
65
+ );
66
+ }
67
+
59
68
  // 取消与指定connectId相关的所有请求
60
69
  public abortRequestsByConnectId(connectId: string) {
61
70
  let count = 0;
62
71
  this.requestQueue.forEach((request, _) => {
63
- if (request.abortController && request.method.connectId === connectId) {
72
+ if (request.abortController && this.isRequestForConnectId(request, connectId)) {
64
73
  request.abortController.abort();
65
74
  request.abortController = undefined;
66
75
  count++;
@@ -69,6 +78,12 @@ export default class RequestQueue {
69
78
  return count;
70
79
  }
71
80
 
81
+ public getRequestTasksIdByConnectId(connectId: string) {
82
+ return Array.from(this.requestQueue.values())
83
+ .filter(request => this.isRequestForConnectId(request, connectId))
84
+ .map(request => request.id);
85
+ }
86
+
72
87
  // 取消所有请求
73
88
  public abortAllRequests() {
74
89
  let count = 0;
package/src/core/index.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  createNeedUpgradeFirmwareHardwareError,
23
23
  createNewFirmwareForceUpdateHardwareError,
24
24
  createNewFirmwareUnReleaseHardwareError,
25
+ isBleStaleBondHardwareError,
25
26
  } from '@onekeyfe/hd-shared';
26
27
 
27
28
  import { LoggerNames, enableLog, getLogger, setLoggerPostMessage, wait } from '../utils';
@@ -945,7 +946,16 @@ export function isRetryableBleConnectionError(method: BaseMethod, error: unknown
945
946
  if (method.device?.wasInterruptedByUser()) {
946
947
  return false;
947
948
  }
948
- const typedError = error as { errorCode?: unknown };
949
+ const typedError = error as {
950
+ errorCode?: unknown;
951
+ params?: { acquireDeadlineExceeded?: unknown };
952
+ };
953
+ if (
954
+ typedError?.errorCode === HardwareErrorCode.BleTimeoutError &&
955
+ typedError.params?.acquireDeadlineExceeded === true
956
+ ) {
957
+ return false;
958
+ }
949
959
  return (
950
960
  typedError?.errorCode === HardwareErrorCode.BleTimeoutError ||
951
961
  typedError?.errorCode === HardwareErrorCode.BleConnectedError ||
@@ -964,11 +974,13 @@ export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unkn
964
974
  );
965
975
  }
966
976
 
967
- export function isProtocolV2PeerRemovedPairingError(method: BaseMethod, error: unknown) {
977
+ export function isTerminalBleStaleBondError(error: unknown) {
978
+ return isBleStaleBondHardwareError(error);
979
+ }
980
+
981
+ export function isDeviceIdentityMismatchError(error: unknown) {
968
982
  return (
969
- method.payload.connectProtocol === 'V2' &&
970
- (error as { errorCode?: unknown })?.errorCode ===
971
- HardwareErrorCode.BlePeerRemovedPairingInformation
983
+ (error as { errorCode?: unknown })?.errorCode === HardwareErrorCode.DeviceCheckDeviceIdError
972
984
  );
973
985
  }
974
986
 
@@ -1001,7 +1013,8 @@ function raceBleAcquire<T>(acquirePromise: Promise<T>, abortSignal?: AbortSignal
1001
1013
  reject(
1002
1014
  ERRORS.TypedError(
1003
1015
  HardwareErrorCode.BleTimeoutError,
1004
- `BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`
1016
+ `BLE acquire exceeded ${BLE_ACQUIRE_DEADLINE_MS}ms deadline`,
1017
+ { acquireDeadlineExceeded: true }
1005
1018
  )
1006
1019
  )
1007
1020
  ),
@@ -1283,7 +1296,8 @@ const ensureConnected = async (
1283
1296
  HardwareErrorCode.DeviceInterruptedFromUser,
1284
1297
  HardwareErrorCode.CallQueueActionCancelled,
1285
1298
  ].includes(error.errorCode) ||
1286
- isProtocolV2PeerRemovedPairingError(method, error)
1299
+ isTerminalBleStaleBondError(error) ||
1300
+ isDeviceIdentityMismatchError(error)
1287
1301
  ) {
1288
1302
  reject(error);
1289
1303
  return;
@@ -1333,7 +1347,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1333
1347
  // cancel callback tasks
1334
1348
  requestQueue.cancelCallbackTasks(connectId);
1335
1349
 
1336
- const requestIds = requestQueue.getRequestTasksId();
1350
+ const requestIds = requestQueue.getRequestTasksIdByConnectId(connectId);
1337
1351
  Log.debug(
1338
1352
  `Cancel Api connect requestQueues: length:${requestIds.length} requestIds:${requestIds.join(
1339
1353
  ','
@@ -1341,10 +1355,9 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1341
1355
  );
1342
1356
  // Abort before rejecting: rejectRequest releases the task and would make
1343
1357
  // its AbortController unreachable to an in-flight method loop.
1344
- // This branch rejects every queued request below. Abort the same set first so
1345
- // methods whose physical connectId is selected internally (for example
1346
- // Desktop WebUSB firmwareUpdateV4) cannot keep retrying after rejection.
1347
- requestQueue.abortAllRequests();
1358
+ // Match both the requested connectId and a device selected internally by the
1359
+ // method, such as Desktop WebUSB firmwareUpdateV4.
1360
+ requestQueue.abortRequestsByConnectId(connectId);
1348
1361
  const canceledDevices: Device[] = [];
1349
1362
  const interruptDevice = (device: Device | undefined, deviceConnectId: string) => {
1350
1363
  if (!device || canceledDevices.includes(device)) {
@@ -1360,7 +1373,7 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1360
1373
  // During ensureConnected the method has a connectId but device is
1361
1374
  // assigned only after the poll succeeds. Interrupt the cached BLE
1362
1375
  // Device so an in-flight acquire/initialize cannot finish.
1363
- interruptDevice(task.method?.device, task.method.connectId ?? connectId);
1376
+ interruptDevice(task.method?.device, connectId);
1364
1377
  interruptDevice(deviceCacheMap.get(connectId), connectId);
1365
1378
  requestQueue.rejectRequest(
1366
1379
  requestId,
@@ -1375,10 +1388,11 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1375
1388
  }
1376
1389
  } else {
1377
1390
  const env = DataManager.getSettings('env');
1391
+ // Abort every method before rejecting its queue task. Non-BLE methods also
1392
+ // use the signal to stop recovery loops after the public promise is rejected.
1393
+ requestQueue.abortAllRequests();
1378
1394
  if (DataManager.isBleConnect(env)) {
1379
1395
  Log.debug('Cancel Api all _deviceList: ');
1380
- // Keep method abort signals observable until every active task is rejected.
1381
- requestQueue.abortAllRequests();
1382
1396
  const canceledDevices: Device[] = [];
1383
1397
  const interruptDevice = (device?: Device) => {
1384
1398
  if (!device || canceledDevices.includes(device)) {
@@ -1421,8 +1435,10 @@ export const cancel = (context: CoreContext, connectId?: string) => {
1421
1435
  }
1422
1436
  }
1423
1437
 
1424
- cleanup();
1425
- closePopup();
1438
+ cleanup(connectId);
1439
+ if (!connectId || _uiPromises.length === 0) {
1440
+ closePopup();
1441
+ }
1426
1442
  };
1427
1443
 
1428
1444
  const checkPassphraseEnableState = (method: BaseMethod, features?: Features) => {
@@ -1460,9 +1476,16 @@ const shouldCheckPassphraseState = (method: BaseMethod, device: Device) => {
1460
1476
  return device.hasUsePassphrase();
1461
1477
  };
1462
1478
 
1463
- const cleanup = () => {
1464
- const pendingUiPromises = _uiPromises;
1465
- _uiPromises = [];
1479
+ const cleanup = (connectId?: string) => {
1480
+ const pendingUiPromises = connectId
1481
+ ? _uiPromises.filter(
1482
+ uiPromise =>
1483
+ uiPromise.data?.mainId === connectId || uiPromise.data?.getConnectId() === connectId
1484
+ )
1485
+ : _uiPromises;
1486
+ _uiPromises = connectId
1487
+ ? _uiPromises.filter(uiPromise => !pendingUiPromises.includes(uiPromise))
1488
+ : [];
1466
1489
  rejectUiPromises(
1467
1490
  pendingUiPromises,
1468
1491
  ERRORS.TypedError(HardwareErrorCode.ActionCancelled, 'UI request was cancelled')
@@ -4463,10 +4463,6 @@
4463
4463
  "request_id": {
4464
4464
  "type": "bytes",
4465
4465
  "id": 5
4466
- },
4467
- "source_fingerprint": {
4468
- "type": "uint32",
4469
- "id": 6
4470
4466
  }
4471
4467
  }
4472
4468
  },
@@ -4722,10 +4718,6 @@
4722
4718
  "expected_address": {
4723
4719
  "type": "bytes",
4724
4720
  "id": 12
4725
- },
4726
- "source_fingerprint": {
4727
- "type": "uint32",
4728
- "id": 13
4729
4721
  }
4730
4722
  }
4731
4723
  },
@@ -4810,10 +4802,6 @@
4810
4802
  "expected_address": {
4811
4803
  "type": "bytes",
4812
4804
  "id": 12
4813
- },
4814
- "source_fingerprint": {
4815
- "type": "uint32",
4816
- "id": 13
4817
4805
  }
4818
4806
  }
4819
4807
  },
@@ -4961,7 +4949,14 @@
4961
4949
  "type": "EthereumAuthorizationSignature",
4962
4950
  "id": 10
4963
4951
  }
4964
- }
4952
+ },
4953
+ "reserved": [
4954
+ [5, 5],
4955
+ [6, 6],
4956
+ [7, 7],
4957
+ [8, 8],
4958
+ [9, 9]
4959
+ ]
4965
4960
  },
4966
4961
  "EthereumTxAckOneKey": {
4967
4962
  "fields": {
@@ -4990,10 +4985,6 @@
4990
4985
  "chain_id": {
4991
4986
  "type": "uint64",
4992
4987
  "id": 3
4993
- },
4994
- "source_fingerprint": {
4995
- "type": "uint32",
4996
- "id": 4
4997
4988
  }
4998
4989
  }
4999
4990
  },
@@ -8968,7 +8959,8 @@
8968
8959
  },
8969
8960
  "SolanaOffChainMessageVersion": {
8970
8961
  "values": {
8971
- "MESSAGE_VERSION_0": 0
8962
+ "MESSAGE_VERSION_0": 0,
8963
+ "MESSAGE_VERSION_1": 1
8972
8964
  }
8973
8965
  },
8974
8966
  "SolanaOffChainMessageFormat": {
@@ -9016,10 +9008,6 @@
9016
9008
  "rule": "required",
9017
9009
  "type": "bytes",
9018
9010
  "id": 2
9019
- },
9020
- "source_fingerprint": {
9021
- "type": "uint32",
9022
- "id": 3
9023
9011
  }
9024
9012
  }
9025
9013
  },
@@ -9056,17 +9044,15 @@
9056
9044
  },
9057
9045
  "message_format": {
9058
9046
  "type": "SolanaOffChainMessageFormat",
9059
- "id": 4,
9060
- "options": {
9061
- "default": "V0_RESTRICTED_ASCII"
9062
- }
9047
+ "id": 4
9063
9048
  },
9064
9049
  "application_domain": {
9065
9050
  "type": "bytes",
9066
9051
  "id": 5
9067
9052
  },
9068
- "source_fingerprint": {
9069
- "type": "uint32",
9053
+ "required_signers": {
9054
+ "rule": "repeated",
9055
+ "type": "bytes",
9070
9056
  "id": 6
9071
9057
  }
9072
9058
  }
@@ -9085,10 +9071,6 @@
9085
9071
  "rule": "required",
9086
9072
  "type": "bytes",
9087
9073
  "id": 2
9088
- },
9089
- "source_fingerprint": {
9090
- "type": "uint32",
9091
- "id": 3
9092
9074
  }
9093
9075
  }
9094
9076
  },
@@ -11885,6 +11867,10 @@
11885
11867
  "manufacture_time": {
11886
11868
  "type": "DeviceFactoryInfoManufactureTime",
11887
11869
  "id": 5
11870
+ },
11871
+ "data": {
11872
+ "type": "bytes",
11873
+ "id": 100
11888
11874
  }
11889
11875
  }
11890
11876
  },
@@ -11894,11 +11880,20 @@
11894
11880
  "rule": "required",
11895
11881
  "type": "DeviceFactoryInfo",
11896
11882
  "id": 1
11883
+ },
11884
+ "full_data": {
11885
+ "type": "bool",
11886
+ "id": 2
11897
11887
  }
11898
11888
  }
11899
11889
  },
11900
11890
  "DeviceFactoryInfoGet": {
11901
- "fields": {}
11891
+ "fields": {
11892
+ "full_data": {
11893
+ "type": "bool",
11894
+ "id": 1
11895
+ }
11896
+ }
11902
11897
  },
11903
11898
  "DeviceFactoryPermanentLock": {
11904
11899
  "fields": {
@@ -10243,7 +10243,8 @@
10243
10243
  },
10244
10244
  "SolanaOffChainMessageVersion": {
10245
10245
  "values": {
10246
- "MESSAGE_VERSION_0": 0
10246
+ "MESSAGE_VERSION_0": 0,
10247
+ "MESSAGE_VERSION_1": 1
10247
10248
  }
10248
10249
  },
10249
10250
  "SolanaOffChainMessageFormat": {
@@ -10276,14 +10277,16 @@
10276
10277
  },
10277
10278
  "message_format": {
10278
10279
  "type": "SolanaOffChainMessageFormat",
10279
- "id": 4,
10280
- "options": {
10281
- "default": "V0_RESTRICTED_ASCII"
10282
- }
10280
+ "id": 4
10283
10281
  },
10284
10282
  "application_domain": {
10285
10283
  "type": "bytes",
10286
10284
  "id": 5
10285
+ },
10286
+ "required_signers": {
10287
+ "rule": "repeated",
10288
+ "type": "bytes",
10289
+ "id": 7
10287
10290
  }
10288
10291
  }
10289
10292
  },
@@ -11,7 +11,7 @@ import {
11
11
  } from '@onekeyfe/hd-transport';
12
12
 
13
13
  import TransportManager from '../data-manager/TransportManager';
14
- import { LoggerNames, getLogger, patchFeatures } from '../utils';
14
+ import { LoggerNames, getLogger, patchFeatures, wait } from '../utils';
15
15
  import { DEVICE, type PassphraseRequestPayload } from '../events';
16
16
  import { DeviceModelToTypes } from '../types';
17
17
  import {
@@ -198,6 +198,8 @@ export class DeviceCommands {
198
198
 
199
199
  disposed: boolean;
200
200
 
201
+ private disposalToken?: object;
202
+
201
203
  callPromise?: Promise<DefaultMessageResponse>;
202
204
 
203
205
  constructor(device: Device, mainId: string) {
@@ -212,6 +214,7 @@ export class DeviceCommands {
212
214
 
213
215
  async dispose(_cancelRequest: boolean) {
214
216
  this.disposed = true;
217
+ this.disposalToken = {};
215
218
  await this.transport.cancel?.();
216
219
  }
217
220
 
@@ -439,7 +442,31 @@ export class DeviceCommands {
439
442
  msg?: DefaultMessageResponse['message'],
440
443
  options?: TransportCallOptions
441
444
  ) {
442
- const resp = await this.call(type, msg, options);
445
+ const { disposalToken } = this;
446
+ let resp = await this.call(type, msg, options);
447
+ // Session busy failures precede wallet derivation or reject an occupied flow.
448
+ // Three retries cover the 240 ms PIN dismissal animation on older V2 firmware.
449
+ for (
450
+ let retry = 0;
451
+ retry < 3 &&
452
+ this.device.isProtocolV2() &&
453
+ DEVICE_SESSION_CALLS.has(type) &&
454
+ resp.type === 'Failure' &&
455
+ (resp.message.code as string | FailureType) === 'Failure_ProcessError' &&
456
+ resp.message.subcode === DeviceSessionErrorCode.DeviceSessionError_Busy;
457
+ retry += 1
458
+ ) {
459
+ await wait(100);
460
+ if (this.device.wasInterruptedByUser()) {
461
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
462
+ }
463
+ this.checkDisposed();
464
+ if (this.disposalToken !== disposalToken) {
465
+ // React Native can revive this instance. A stale retry must not release the new run.
466
+ throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'DeviceCommands lifecycle changed');
467
+ }
468
+ resp = await this.call(type, msg, options);
469
+ }
443
470
  return this._filterCommonTypes(resp, type, options);
444
471
  }
445
472
 
@@ -8,8 +8,9 @@ export const LogBlockEvent: Set<string> = new Set([
8
8
  UI_RESPONSE.RECEIVE_PASSPHRASE,
9
9
  ]);
10
10
 
11
- const LogLabelMethod: Set<string> = new Set([
12
- 'openWalletSession',
11
+ // These resource/file APIs did not exist in 1.1.32. Skip the payload so
12
+ // the log layer does not copy huge Base64 or binary data.
13
+ const LogPayloadBlockMethod: Set<string> = new Set([
13
14
  'deviceUploadNft',
14
15
  'deviceUploadWallpaper',
15
16
  'uploadPortfolio',
@@ -17,61 +18,6 @@ const LogLabelMethod: Set<string> = new Set([
17
18
  'fileRead',
18
19
  ]);
19
20
 
20
- // 资源上传参数可能包含很大的 Base64 字符串。这里按方法整段跳过,避免日志层
21
- // 递归复制和序列化这些数据;资源 API 与传输内容本身保持不变。
22
- const LogPayloadBlockMethod: Set<string> = new Set([
23
- 'deviceUploadNft',
24
- 'deviceUploadWallpaper',
25
- 'uploadPortfolio',
26
- ]);
27
-
28
- const SensitiveLogKeys: Set<string> = new Set([
29
- 'devicestate',
30
- 'entropy',
31
- 'expectedpassphrasestate',
32
- 'mnemonic',
33
- 'passphrase',
34
- 'passphrasestate',
35
- 'password',
36
- 'pin',
37
- 'privatekey',
38
- 'seed',
39
- 'session',
40
- 'sessionid',
41
- 'walletsessionid',
42
- 'xprv',
43
- ]);
44
-
45
- const normalizeLogKey = (key: string) => key.replace(/[_-]/g, '').toLowerCase();
46
-
47
- const isSigningMethod = (methodName: string) => /sign/i.test(methodName);
48
-
49
- const redactLogValue = (value: unknown, seen: WeakSet<object>): unknown => {
50
- if (ArrayBuffer.isView(value)) {
51
- return `[BINARY:${value.byteLength}]`;
52
- }
53
- if (value instanceof ArrayBuffer) {
54
- return `[BINARY:${value.byteLength}]`;
55
- }
56
- if (Array.isArray(value)) {
57
- return value.map(item => redactLogValue(item, seen));
58
- }
59
- if (!value || typeof value !== 'object') return value;
60
- if (seen.has(value)) return '[CIRCULAR]';
61
-
62
- seen.add(value);
63
- const redacted = Object.fromEntries(
64
- Object.entries(value as Record<string, unknown>).map(([key, item]) => [
65
- key,
66
- SensitiveLogKeys.has(normalizeLogKey(key)) && item !== null && item !== undefined
67
- ? '[REDACTED]'
68
- : redactLogValue(item, seen),
69
- ])
70
- );
71
- seen.delete(value);
72
- return redacted;
73
- };
74
-
75
21
  export function getLogBlockLabel(message: unknown): string | undefined {
76
22
  if (!message || typeof message !== 'object') return undefined;
77
23
 
@@ -86,7 +32,7 @@ export function getLogBlockLabel(message: unknown): string | undefined {
86
32
  }
87
33
 
88
34
  const methodName = method ?? payload?.method;
89
- if (methodName && (LogLabelMethod.has(methodName) || isSigningMethod(methodName))) {
35
+ if (methodName && LogPayloadBlockMethod.has(methodName)) {
90
36
  return methodName;
91
37
  }
92
38
 
@@ -94,25 +40,10 @@ export function getLogBlockLabel(message: unknown): string | undefined {
94
40
  }
95
41
 
96
42
  export function getSafeLogPayload(value: unknown, blockLabel?: string): unknown {
97
- if (
98
- blockLabel &&
99
- (LogBlockEvent.has(blockLabel) ||
100
- LogPayloadBlockMethod.has(blockLabel) ||
101
- isSigningMethod(blockLabel))
102
- ) {
43
+ if (blockLabel) {
103
44
  return { method: blockLabel, payload: '[REDACTED]' };
104
45
  }
105
-
106
- const redactedValue = redactLogValue(value, new WeakSet());
107
- if (
108
- blockLabel &&
109
- redactedValue &&
110
- typeof redactedValue === 'object' &&
111
- !Array.isArray(redactedValue)
112
- ) {
113
- return { ...redactedValue, method: blockLabel };
114
- }
115
- return redactedValue;
46
+ return value;
116
47
  }
117
48
 
118
49
  export function formatLogMethodLabel(label: string, methodName?: string): string {
@@ -234,9 +234,9 @@ export async function getProtocolV2WalletSession(
234
234
  const forceWalletSelection =
235
235
  options?.forceWalletSelection === true || options?.initSession === true;
236
236
  const readCurrentAttachPinSession = options?.readCurrentAttachPinSession === true;
237
- const sessionIsAttachPinWallet = (session?: { viaAttachPin?: boolean }) =>
237
+ const sessionIsAttachPinWallet = (session?: unknown) =>
238
238
  readCurrentAttachPinSession ||
239
- session?.viaAttachPin === true ||
239
+ (session as { viaAttachPin?: boolean } | undefined)?.viaAttachPin === true ||
240
240
  device.features?.unlockedAttachPin === true;
241
241
 
242
242
  if (forceWalletSelection) {
@@ -273,6 +273,10 @@ export async function getProtocolV2WalletSession(
273
273
  const markWalletStatusRefreshed = () => {
274
274
  walletStatusRefreshed = true;
275
275
  };
276
+ if (options?.onlyMainPin && options.mainPinSelected !== true) {
277
+ await refreshProtocolV2DeviceStatus(device);
278
+ markWalletStatusRefreshed();
279
+ }
276
280
  let mainPinAuthenticated =
277
281
  options?.mainPinSelected === true ||
278
282
  (options?.onlyMainPin === true &&
@@ -451,6 +455,11 @@ export async function getProtocolV2WalletSession(
451
455
  throw error;
452
456
  }
453
457
  resumed = false;
458
+ if (device.features?.unlockedAttachPin === true) {
459
+ // Locking invalidates the old handle, but Attach PIN already selected the wallet.
460
+ // Read the current session and validate its passphrase state below.
461
+ response = await getDeviceSession(device, sessionGetRequest());
462
+ }
454
463
  }
455
464
  } else if (expectedPassphraseState) {
456
465
  try {
@@ -1,6 +1,7 @@
1
1
  import type { CommonParams, Response } from '../params';
2
2
  import type { OnboardingStatus, Success } from '@onekeyfe/hd-transport';
3
3
  import type { DeviceRebootParams } from '../../api/protocol-v2/helpers';
4
+ import type { UploadPortfolioParams } from '../../api/UploadPortfolio';
4
5
  import type {
5
6
  DeviceUploadWallpaperParams,
6
7
  DeviceUploadWallpaperResponse,
@@ -12,6 +13,7 @@ import type {
12
13
 
13
14
  // Re-export implementation parameter types as the single source of truth.
14
15
  export type { DeviceRebootParams, RebootTypeInput } from '../../api/protocol-v2/helpers';
16
+ export type { UploadPortfolioParams } from '../../api/UploadPortfolio';
15
17
  export type {
16
18
  DeviceUploadWallpaperParams,
17
19
  DeviceUploadWallpaperResponse,
@@ -67,8 +69,5 @@ export declare function deviceUploadNft(
67
69
 
68
70
  export declare function uploadPortfolio(
69
71
  connectId: string,
70
- params: {
71
- packageBase64: string;
72
- timeoutMs?: number | string;
73
- }
72
+ params: UploadPortfolioParams
74
73
  ): Response<FileInfo & { portfolioUpdated: true }>;
@@ -15,6 +15,8 @@ export type SolSignOffchainMessageParams = {
15
15
  messageVersion?: SolanaOffChainMessageVersion;
16
16
  messageFormat?: SolanaOffChainMessageFormat;
17
17
  applicationDomainHex?: string;
18
+ /** 32-byte public keys encoded as hex, strictly sorted and unique. */
19
+ requiredSigners?: string[];
18
20
  };
19
21
 
20
22
  export declare function solSignOffchainMessage(