@onekeyfe/hd-core 1.2.2-alpha.100 → 1.2.2-alpha.101

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 (94) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +20 -4
  2. package/__tests__/DeviceCommands.test.ts +13 -0
  3. package/__tests__/check-all-firmware-release-protocol-v2.test.ts +0 -2
  4. package/__tests__/check-firmware-release-protocol-v2.test.ts +0 -2
  5. package/__tests__/device-lifecycle-events.test.ts +102 -0
  6. package/__tests__/deviceUploadNft.test.ts +68 -0
  7. package/__tests__/evmSignTypedData.test.ts +42 -0
  8. package/__tests__/firmware-update/firmware-update-v4-install-poll.test.ts +825 -12
  9. package/__tests__/logBlockEvent.test.ts +45 -122
  10. package/__tests__/open-wallet-session.test.ts +781 -69
  11. package/__tests__/pro2HostAssetPackage.test.ts +165 -0
  12. package/__tests__/protocol-v2-resources.test.ts +7 -4
  13. package/__tests__/protocol-v2-unlock-policy.test.ts +35 -1
  14. package/__tests__/protocol-v2.test.ts +937 -74
  15. package/__tests__/protocolV2FileWrite.test.ts +40 -0
  16. package/__tests__/sol-sign-offchain-message.test.ts +70 -0
  17. package/dist/api/FirmwareUpdateV4.d.ts +5 -0
  18. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  19. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  20. package/dist/api/UploadPortfolio.d.ts +1 -0
  21. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  22. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  23. package/dist/api/evm/EVMSignTypedData.d.ts.map +1 -1
  24. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +2 -2
  25. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  26. package/dist/api/firmware/getBinary.d.ts +2 -2
  27. package/dist/api/helpers/protocolV2FileWrite.d.ts +1 -0
  28. package/dist/api/helpers/protocolV2FileWrite.d.ts.map +1 -1
  29. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  30. package/dist/api/protocol-v2/DeviceUploadNft.d.ts.map +1 -1
  31. package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
  32. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  33. package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
  34. package/dist/core/RequestQueue.d.ts +2 -0
  35. package/dist/core/RequestQueue.d.ts.map +1 -1
  36. package/dist/core/index.d.ts +4 -1
  37. package/dist/core/index.d.ts.map +1 -1
  38. package/dist/core/uiPromiseRegistry.d.ts +1 -1
  39. package/dist/device/Device.d.ts +1 -1
  40. package/dist/device/Device.d.ts.map +1 -1
  41. package/dist/device/DeviceCommands.d.ts +4 -4
  42. package/dist/device/DeviceConnector.d.ts +1 -1
  43. package/dist/device/DeviceConnector.d.ts.map +1 -1
  44. package/dist/events/logBlockEvent.d.ts.map +1 -1
  45. package/dist/events/ui-request.d.ts +3 -0
  46. package/dist/events/ui-request.d.ts.map +1 -1
  47. package/dist/index.d.ts +39 -16
  48. package/dist/index.js +2572 -1554
  49. package/dist/protocols/protocol-v2/resources.d.ts.map +1 -1
  50. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts +2 -0
  51. package/dist/protocols/protocol-v2/unlockPolicyRunner.d.ts.map +1 -1
  52. package/dist/protocols/protocol-v2/walletSession.d.ts +3 -0
  53. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  54. package/dist/types/api/protocolV2.d.ts +3 -4
  55. package/dist/types/api/protocolV2.d.ts.map +1 -1
  56. package/dist/types/api/solSignOffchainMessage.d.ts +2 -0
  57. package/dist/types/api/solSignOffchainMessage.d.ts.map +1 -1
  58. package/dist/types/settings.d.ts +2 -2
  59. package/dist/types/settings.d.ts.map +1 -1
  60. package/dist/utils/deviceFeaturesUtils.d.ts +3 -0
  61. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  62. package/dist/utils/patch.d.ts +1 -1
  63. package/dist/utils/patch.d.ts.map +1 -1
  64. package/dist/utils/pro2HostAssetPackage.d.ts +8 -0
  65. package/dist/utils/pro2HostAssetPackage.d.ts.map +1 -0
  66. package/package.json +4 -4
  67. package/src/api/FirmwareUpdateV4.ts +474 -83
  68. package/src/api/OpenWalletSession.ts +39 -12
  69. package/src/api/UploadPortfolio.ts +5 -4
  70. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +5 -2
  71. package/src/api/evm/EVMSignTypedData.ts +1 -0
  72. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +9 -3
  73. package/src/api/helpers/protocolV2FileWrite.ts +11 -5
  74. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +7 -1
  75. package/src/api/protocol-v2/DeviceUploadNft.ts +31 -8
  76. package/src/api/protocol-v2/DeviceUploadWallpaper.ts +33 -8
  77. package/src/api/protocol-v2/helpers.ts +8 -0
  78. package/src/api/solana/SolSignOffchainMessage.ts +57 -4
  79. package/src/core/RequestQueue.ts +16 -1
  80. package/src/core/index.ts +179 -27
  81. package/src/data/messages/messages-protocol-v2.json +1562 -1345
  82. package/src/data/messages/messages.json +8 -5
  83. package/src/device/Device.ts +3 -1
  84. package/src/device/DeviceConnector.ts +7 -3
  85. package/src/events/logBlockEvent.ts +6 -75
  86. package/src/events/ui-request.ts +3 -0
  87. package/src/protocols/protocol-v2/resources.ts +9 -1
  88. package/src/protocols/protocol-v2/unlockPolicyRunner.ts +8 -1
  89. package/src/protocols/protocol-v2/walletSession.ts +223 -56
  90. package/src/types/api/protocolV2.ts +3 -4
  91. package/src/types/api/solSignOffchainMessage.ts +3 -0
  92. package/src/types/settings.ts +4 -2
  93. package/src/utils/deviceFeaturesUtils.ts +4 -0
  94. package/src/utils/pro2HostAssetPackage.ts +400 -0
@@ -63,35 +63,50 @@ const negotiateEventlessWalletSession = async (device: Device) => {
63
63
  const getDeviceSession = async (device: Device, request: DeviceSessionGet) =>
64
64
  device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
65
65
 
66
+ const STANDARD_SEED_DOMAINS = [DeviceSessionSeedDomain.SeedDomain_Standard];
67
+ const CARDANO_SEED_DOMAINS = [
68
+ DeviceSessionSeedDomain.SeedDomain_Standard,
69
+ DeviceSessionSeedDomain.SeedDomain_Cardano,
70
+ ];
71
+
72
+ const buildDeviceSessionSeedDomains = (deriveCardano?: boolean): DeviceSessionSeedDomain[] =>
73
+ deriveCardano === true ? CARDANO_SEED_DOMAINS : STANDARD_SEED_DOMAINS;
74
+
75
+ const deviceSessionHasCardano = (message: { seed_domains?: DeviceSessionSeedDomain[] }) =>
76
+ Array.isArray(message.seed_domains) &&
77
+ message.seed_domains.includes(DeviceSessionSeedDomain.SeedDomain_Cardano);
78
+
79
+ // origin/dev Get is read/resume only. Seed generation lives on AskPassphrase.
66
80
  const buildDeviceSessionGetRequest = ({
67
81
  sessionId,
68
82
  expectedPassphraseState,
69
- deriveCardano,
70
83
  }: {
71
84
  sessionId?: string;
72
85
  expectedPassphraseState?: string;
73
- deriveCardano?: boolean;
74
86
  } = {}): DeviceSessionGet => ({
75
87
  ...(sessionId ? { session_id: sessionId } : {}),
76
88
  ...(expectedPassphraseState ? { btc_test_address: expectedPassphraseState } : {}),
77
- seed_domains:
78
- deriveCardano === undefined
79
- ? []
80
- : [
81
- DeviceSessionSeedDomain.SeedDomain_Standard,
82
- ...(deriveCardano ? [DeviceSessionSeedDomain.SeedDomain_Cardano] : []),
83
- ],
84
89
  });
85
90
 
86
- const askDevicePassphrase = async (device: Device, requestPayload: DeviceSessionAskPassphrase) => {
87
- await device.commands.typedCall('DeviceSessionAskPassphrase', 'Success', requestPayload);
91
+ const askDevicePassphrase = async (
92
+ device: Device,
93
+ requestPayload: Omit<DeviceSessionAskPassphrase, 'seed_domains'>,
94
+ deriveCardano?: boolean,
95
+ onStatusRefreshed?: () => void
96
+ ) => {
97
+ await device.commands.typedCall('DeviceSessionAskPassphrase', 'Success', {
98
+ ...requestPayload,
99
+ seed_domains: buildDeviceSessionSeedDomains(deriveCardano),
100
+ });
88
101
  await refreshProtocolV2DeviceStatus(device);
102
+ onStatusRefreshed?.();
89
103
  };
90
104
 
91
105
  const selectDeviceSession = async (
92
106
  device: Device,
93
107
  expectedPassphraseState?: string,
94
- deriveCardano?: boolean
108
+ deriveCardano?: boolean,
109
+ onStatusRefreshed?: () => void
95
110
  ) => {
96
111
  const existsAttachPinUser = device.features?.attachToPinEnabled === true;
97
112
  const metadata = {
@@ -153,15 +168,36 @@ const selectDeviceSession = async (
153
168
  emitUiEvent: false,
154
169
  interaction: attachPinInteraction,
155
170
  });
156
- return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
171
+ onStatusRefreshed?.();
172
+ if (deriveCardano === true) {
173
+ await askDevicePassphrase(
174
+ device,
175
+ { passphrase: '', on_device: false },
176
+ true,
177
+ onStatusRefreshed
178
+ );
179
+ // Firmware AskPassphrase Success clears unlocked_by_attach_to_pin.
180
+ // Identity is still the Attach PIN wallet; keep the SDK flag so a later
181
+ // passphrase picker still locks instead of prompting.
182
+ if (device.features) {
183
+ device.features.unlockedAttachPin = true;
184
+ }
185
+ }
186
+ const attachPinSession = await getDeviceSession(device, buildDeviceSessionGetRequest());
187
+ return Object.assign(attachPinSession, { viaAttachPin: true as const });
157
188
  }
158
189
 
159
190
  if (hasHostPassphrase) {
160
- await askDevicePassphrase(device, {
161
- passphrase: hostPassphrase,
162
- on_device: false,
163
- });
164
- return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
191
+ await askDevicePassphrase(
192
+ device,
193
+ {
194
+ passphrase: hostPassphrase,
195
+ on_device: false,
196
+ },
197
+ deriveCardano,
198
+ onStatusRefreshed
199
+ );
200
+ return getDeviceSession(device, buildDeviceSessionGetRequest());
165
201
  }
166
202
 
167
203
  const passphraseOnDeviceInteraction = device.createProtocolV2UiPhaseMetadata?.(
@@ -172,8 +208,8 @@ const selectDeviceSession = async (
172
208
  ...metadata,
173
209
  ...(passphraseOnDeviceInteraction ? { interaction: passphraseOnDeviceInteraction } : {}),
174
210
  });
175
- await askDevicePassphrase(device, { on_device: true });
176
- return getDeviceSession(device, buildDeviceSessionGetRequest({ deriveCardano }));
211
+ await askDevicePassphrase(device, { on_device: true }, deriveCardano, onStatusRefreshed);
212
+ return getDeviceSession(device, buildDeviceSessionGetRequest());
177
213
  };
178
214
 
179
215
  export async function getProtocolV2WalletSession(
@@ -191,11 +227,17 @@ export async function getProtocolV2WalletSession(
191
227
  deriveCardano?: boolean;
192
228
  /** Read the wallet already selected by Attach PIN without reopening wallet selection. */
193
229
  readCurrentAttachPinSession?: boolean;
230
+ /** The current call already selected the Main PIN during its unlock preflight. */
231
+ mainPinSelected?: boolean;
194
232
  }
195
233
  ) {
196
234
  const forceWalletSelection =
197
235
  options?.forceWalletSelection === true || options?.initSession === true;
198
236
  const readCurrentAttachPinSession = options?.readCurrentAttachPinSession === true;
237
+ const sessionIsAttachPinWallet = (session?: unknown) =>
238
+ readCurrentAttachPinSession ||
239
+ (session as { viaAttachPin?: boolean } | undefined)?.viaAttachPin === true ||
240
+ device.features?.unlockedAttachPin === true;
199
241
 
200
242
  if (forceWalletSelection) {
201
243
  if (options.onlyMainPin) {
@@ -227,10 +269,20 @@ export async function getProtocolV2WalletSession(
227
269
  : undefined;
228
270
  let response;
229
271
  let resumed = false;
230
- let mainPinSelected =
231
- options?.onlyMainPin === true &&
232
- device.features?.unlocked === true &&
233
- device.features?.unlockedAttachPin === false;
272
+ let walletStatusRefreshed = false;
273
+ const markWalletStatusRefreshed = () => {
274
+ walletStatusRefreshed = true;
275
+ };
276
+ if (options?.onlyMainPin && options.mainPinSelected !== true) {
277
+ await refreshProtocolV2DeviceStatus(device);
278
+ markWalletStatusRefreshed();
279
+ }
280
+ let mainPinAuthenticated =
281
+ options?.mainPinSelected === true ||
282
+ (options?.onlyMainPin === true &&
283
+ device.features?.unlocked === true &&
284
+ device.features?.unlockedAttachPin === false);
285
+ let standardWalletSelected = options?.mainPinSelected === true;
234
286
 
235
287
  const clearCurrentWalletSession = () => {
236
288
  if (options?.onlyMainPin) {
@@ -240,8 +292,41 @@ export async function getProtocolV2WalletSession(
240
292
  }
241
293
  };
242
294
 
295
+ const sessionGetRequest = ({
296
+ sessionId,
297
+ expectedPassphraseState: passphraseState,
298
+ }: {
299
+ sessionId?: string;
300
+ expectedPassphraseState?: string;
301
+ } = {}) =>
302
+ buildDeviceSessionGetRequest({
303
+ sessionId,
304
+ expectedPassphraseState: passphraseState,
305
+ });
306
+
307
+ const askEmptyPassphraseAndGet = async ({
308
+ deriveCardano,
309
+ keepAttachPin,
310
+ }: {
311
+ deriveCardano?: boolean;
312
+ keepAttachPin?: boolean;
313
+ } = {}) => {
314
+ await askDevicePassphrase(
315
+ device,
316
+ { passphrase: '', on_device: false },
317
+ deriveCardano,
318
+ markWalletStatusRefreshed
319
+ );
320
+ if (keepAttachPin && device.features) {
321
+ device.features.unlockedAttachPin = true;
322
+ }
323
+ const session = await getDeviceSession(device, buildDeviceSessionGetRequest());
324
+ return keepAttachPin ? Object.assign(session, { viaAttachPin: true as const }) : session;
325
+ };
326
+
243
327
  const rejectMismatchedAttachPinWallet = async () => {
244
328
  const features = await refreshProtocolV2DeviceStatus(device);
329
+ markWalletStatusRefreshed();
245
330
  if (features.unlockedAttachPin !== true) {
246
331
  return;
247
332
  }
@@ -260,35 +345,63 @@ export async function getProtocolV2WalletSession(
260
345
  throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckUnlockTypeError);
261
346
  };
262
347
 
348
+ // The passphrase picker would prompt. Empty host AskPassphrase does not;
349
+ // that path is Attach PIN / standard Cardano. Switching to a different
350
+ // passphrase wallet still locks first.
351
+ const lockAttachPinBeforePassphraseSelection = async () => {
352
+ if (readCurrentAttachPinSession || options?.onlyMainPin) {
353
+ return;
354
+ }
355
+ if (device.features?.unlockedAttachPin !== true) {
356
+ return;
357
+ }
358
+ await rejectMismatchedAttachPinWallet();
359
+ };
360
+
263
361
  if (options?.onlyMainPin && options.rejectAttachPinForMainWallet) {
264
362
  await rejectMismatchedAttachPinWallet();
265
363
  }
266
364
 
267
365
  const selectMainPin = async (force = false) => {
268
- if (force || !mainPinSelected) {
366
+ if (force || !mainPinAuthenticated) {
269
367
  await device.unlockDevice(DeviceSessionPinType.Main, {
270
368
  source: 'wallet-session-coordinator',
271
369
  reason: expectedPassphraseState ? 'session-recovery' : 'open-wallet',
272
370
  deviceOnly: true,
273
371
  });
274
- mainPinSelected = true;
372
+ markWalletStatusRefreshed();
373
+ mainPinAuthenticated = true;
374
+ standardWalletSelected = true;
275
375
  }
276
376
  };
277
377
 
278
- const selectStandardWallet = async () => {
279
- // Main PIN authenticates the device; an empty host passphrase selects the standard derivation.
280
- await selectMainPin();
378
+ const selectStandardWallet = async (forceMainPin = false) => {
281
379
  if (device.features?.passphraseProtection === true) {
282
- await askDevicePassphrase(device, {
283
- passphrase: '',
284
- on_device: false,
285
- });
380
+ // Main PIN authenticates the device; an empty host passphrase selects the standard derivation.
381
+ await selectMainPin();
382
+ await askDevicePassphrase(
383
+ device,
384
+ {
385
+ passphrase: '',
386
+ on_device: false,
387
+ },
388
+ options?.deriveCardano,
389
+ markWalletStatusRefreshed
390
+ );
391
+ standardWalletSelected = true;
392
+ } else if (!standardWalletSelected) {
393
+ // Without passphrase protection there is no empty-passphrase selector.
394
+ // An unlocked non-Attach-PIN device is already in the only available wallet context.
395
+ // Force Main PIN only when recovering from a mismatched cached standard session.
396
+ await selectMainPin(forceMainPin);
397
+ standardWalletSelected = true;
286
398
  }
287
399
  };
288
400
 
289
401
  if (readCurrentAttachPinSession) {
290
402
  const status = await requestProtocolV2DeviceStatus(device);
291
403
  device.updateProtocolV2Status(status);
404
+ markWalletStatusRefreshed();
292
405
  if (
293
406
  status.unlocked !== true ||
294
407
  status.passphrase_enabled !== true ||
@@ -297,10 +410,7 @@ export async function getProtocolV2WalletSession(
297
410
  device.clearInternalState();
298
411
  throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckUnlockTypeError);
299
412
  }
300
- response = await getDeviceSession(
301
- device,
302
- buildDeviceSessionGetRequest({ deriveCardano: options?.deriveCardano })
303
- );
413
+ response = await getDeviceSession(device, sessionGetRequest());
304
414
  } else if (options?.onlyMainPin) {
305
415
  expectedPassphraseState = cachedStandardSession?.passphraseState;
306
416
  if (cachedStandardSession) {
@@ -310,10 +420,9 @@ export async function getProtocolV2WalletSession(
310
420
  }
311
421
  response = await getDeviceSession(
312
422
  device,
313
- buildDeviceSessionGetRequest({
423
+ sessionGetRequest({
314
424
  sessionId: cachedStandardSession.sessionId,
315
425
  expectedPassphraseState,
316
- deriveCardano: options?.deriveCardano,
317
426
  })
318
427
  );
319
428
  resumed = true;
@@ -328,19 +437,15 @@ export async function getProtocolV2WalletSession(
328
437
 
329
438
  if (!response) {
330
439
  await selectStandardWallet();
331
- response = await getDeviceSession(
332
- device,
333
- buildDeviceSessionGetRequest({ deriveCardano: options?.deriveCardano })
334
- );
440
+ response = await getDeviceSession(device, sessionGetRequest());
335
441
  }
336
442
  } else if (cachedSessionId && expectedPassphraseState) {
337
443
  try {
338
444
  response = await getDeviceSession(
339
445
  device,
340
- buildDeviceSessionGetRequest({
446
+ sessionGetRequest({
341
447
  sessionId: cachedSessionId,
342
448
  expectedPassphraseState,
343
- deriveCardano: options?.deriveCardano,
344
449
  })
345
450
  );
346
451
  resumed = true;
@@ -350,14 +455,18 @@ export async function getProtocolV2WalletSession(
350
455
  throw error;
351
456
  }
352
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
+ }
353
463
  }
354
464
  } else if (expectedPassphraseState) {
355
465
  try {
356
466
  response = await getDeviceSession(
357
467
  device,
358
- buildDeviceSessionGetRequest({
468
+ sessionGetRequest({
359
469
  expectedPassphraseState,
360
- deriveCardano: options?.deriveCardano,
361
470
  })
362
471
  );
363
472
  } catch (error) {
@@ -372,7 +481,13 @@ export async function getProtocolV2WalletSession(
372
481
  device.clearInternalState();
373
482
  throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
374
483
  }
375
- response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
484
+ await lockAttachPinBeforePassphraseSelection();
485
+ response = await selectDeviceSession(
486
+ device,
487
+ expectedPassphraseState,
488
+ options?.deriveCardano,
489
+ markWalletStatusRefreshed
490
+ );
376
491
  }
377
492
 
378
493
  let { message } = response;
@@ -395,14 +510,17 @@ export async function getProtocolV2WalletSession(
395
510
  }
396
511
  if (options?.onlyMainPin) {
397
512
  device.clearStandardInternalState?.();
398
- await selectStandardWallet();
399
- response = await getDeviceSession(
400
- device,
401
- buildDeviceSessionGetRequest({ deriveCardano: options?.deriveCardano })
402
- );
513
+ await selectStandardWallet(true);
514
+ response = await getDeviceSession(device, sessionGetRequest());
403
515
  } else {
404
516
  device.clearInternalState();
405
- response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
517
+ await lockAttachPinBeforePassphraseSelection();
518
+ response = await selectDeviceSession(
519
+ device,
520
+ expectedPassphraseState,
521
+ options?.deriveCardano,
522
+ markWalletStatusRefreshed
523
+ );
406
524
  }
407
525
  message = response.message;
408
526
  try {
@@ -422,6 +540,54 @@ export async function getProtocolV2WalletSession(
422
540
  }
423
541
  }
424
542
 
543
+ // origin/dev generates Cardano on AskPassphrase, not Get. Empty host
544
+ // passphrase is the Attach PIN / standard-wallet secret. Hidden wallets
545
+ // still need a real passphrase Ask. Passphrase-off Get auto-requests Cardano.
546
+ if (options?.deriveCardano === true && !deviceSessionHasCardano(message)) {
547
+ if (options?.resumeOnly) {
548
+ device.clearInternalState();
549
+ throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
550
+ }
551
+ resumed = false;
552
+ const previousAddress = message.btc_test_address;
553
+ if (sessionIsAttachPinWallet(response)) {
554
+ response = await askEmptyPassphraseAndGet({ deriveCardano: true, keepAttachPin: true });
555
+ } else if (device.features?.passphraseProtection === false) {
556
+ response = await getDeviceSession(device, buildDeviceSessionGetRequest());
557
+ } else if (options?.onlyMainPin) {
558
+ await selectMainPin();
559
+ response = await askEmptyPassphraseAndGet({ deriveCardano: true });
560
+ } else {
561
+ await lockAttachPinBeforePassphraseSelection();
562
+ response = await selectDeviceSession(
563
+ device,
564
+ expectedPassphraseState,
565
+ true,
566
+ markWalletStatusRefreshed
567
+ );
568
+ }
569
+ message = response.message;
570
+ try {
571
+ assertCompleteDeviceSession(message);
572
+ } catch (error) {
573
+ if (options?.onlyMainPin) {
574
+ device.clearStandardInternalState?.();
575
+ } else {
576
+ device.clearInternalState();
577
+ }
578
+ throw error;
579
+ }
580
+ if (previousAddress && previousAddress !== message.btc_test_address) {
581
+ await rejectMismatchedAttachPinWallet();
582
+ clearCurrentWalletSession();
583
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
584
+ }
585
+ if (!deviceSessionHasCardano(message)) {
586
+ clearCurrentWalletSession();
587
+ throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
588
+ }
589
+ }
590
+
425
591
  const internalStateArgs = [
426
592
  true,
427
593
  message.btc_test_address,
@@ -436,9 +602,9 @@ export async function getProtocolV2WalletSession(
436
602
  }
437
603
 
438
604
  let unlockedAttachPin: boolean | undefined;
439
- if (readCurrentAttachPinSession) {
605
+ if (readCurrentAttachPinSession || sessionIsAttachPinWallet(response)) {
440
606
  unlockedAttachPin = true;
441
- } else if (mainPinSelected) {
607
+ } else if (mainPinAuthenticated) {
442
608
  unlockedAttachPin = false;
443
609
  }
444
610
 
@@ -447,6 +613,7 @@ export async function getProtocolV2WalletSession(
447
613
  newSession: message.session_id,
448
614
  unlockedAttachPin,
449
615
  resumed,
616
+ walletStatusRefreshed,
450
617
  };
451
618
  }
452
619
 
@@ -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,9 @@ export type SolSignOffchainMessageParams = {
15
15
  messageVersion?: SolanaOffChainMessageVersion;
16
16
  messageFormat?: SolanaOffChainMessageFormat;
17
17
  applicationDomainHex?: string;
18
+ sourceFingerprint?: number;
19
+ /** 32-byte public keys encoded as hex, strictly sorted and unique. */
20
+ requiredSigners?: string[];
18
21
  };
19
22
 
20
23
  export declare function solSignOffchainMessage(
@@ -77,7 +77,8 @@ export type IProtocolV2Resources = {
77
77
  /** STM32 firmware config */
78
78
  export type IFirmwareReleaseInfo = {
79
79
  required: boolean;
80
- url: string;
80
+ /** Legacy release artifact URL. Protocol V2 releases use component URLs. */
81
+ url?: string;
81
82
  /**
82
83
  * Firmware type (bitcoinonly or universal)
83
84
  * This field is not present in the remote config, but will be inferred from the firmware field name
@@ -106,7 +107,8 @@ export type IFirmwareReleaseInfo = {
106
107
  bootloaderChangelog?: {
107
108
  [k in ILocale]: string;
108
109
  };
109
- fingerprint: string;
110
+ /** Legacy release artifact SHA-256. Protocol V2 releases use component fingerprints. */
111
+ fingerprint?: string;
110
112
  expectedSize?: number;
111
113
  version: IVersionArray;
112
114
  changelog: {
@@ -110,6 +110,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
110
110
  initSession?: boolean;
111
111
  deriveCardano?: boolean;
112
112
  rejectAttachPinForMainWallet?: boolean;
113
+ mainPinSelected?: boolean;
113
114
  }
114
115
  ) => {
115
116
  if (device.isProtocolV2()) {
@@ -127,6 +128,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
127
128
  onlyMainPin: options?.onlyMainPin,
128
129
  deriveCardano: options?.deriveCardano,
129
130
  rejectAttachPinForMainWallet: options?.rejectAttachPinForMainWallet,
131
+ mainPinSelected: options?.mainPinSelected,
130
132
  });
131
133
  }
132
134
 
@@ -194,6 +196,7 @@ export const getPassphraseState = async (
194
196
  initSession?: boolean;
195
197
  deriveCardano?: boolean;
196
198
  rejectAttachPinForMainWallet?: boolean;
199
+ mainPinSelected?: boolean;
197
200
  }
198
201
  ): Promise<{
199
202
  passphraseState: string | undefined;
@@ -216,6 +219,7 @@ export const getPassphraseState = async (
216
219
  onlyMainPin: options?.onlyMainPin,
217
220
  deriveCardano: options?.deriveCardano,
218
221
  rejectAttachPinForMainWallet: options?.rejectAttachPinForMainWallet,
222
+ mainPinSelected: options?.mainPinSelected,
219
223
  });
220
224
  }
221
225