@onekeyfe/hd-core 1.2.0-alpha.35 → 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 (50) hide show
  1. package/__tests__/AllNetworkGetAddressBase.tracing.test.ts +74 -0
  2. package/__tests__/DeviceEventRegistration.test.ts +6 -0
  3. package/__tests__/get-device-state.test.ts +104 -12
  4. package/__tests__/open-wallet-session.test.ts +131 -7
  5. package/__tests__/protocol-v2-ui-lifecycle.test.ts +56 -0
  6. package/__tests__/protocol-v2.test.ts +290 -39
  7. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  8. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  9. package/dist/api/OpenWalletSession.d.ts.map +1 -1
  10. package/dist/api/allnetwork/AllNetworkGetAddressBase.d.ts.map +1 -1
  11. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -1
  12. package/dist/core/deviceEventRegistration.d.ts +2 -0
  13. package/dist/core/deviceEventRegistration.d.ts.map +1 -1
  14. package/dist/core/index.d.ts.map +1 -1
  15. package/dist/device/Device.d.ts +22 -4
  16. package/dist/device/Device.d.ts.map +1 -1
  17. package/dist/events/device.d.ts +4 -0
  18. package/dist/events/device.d.ts.map +1 -1
  19. package/dist/events/ui-request.d.ts +27 -2
  20. package/dist/events/ui-request.d.ts.map +1 -1
  21. package/dist/index.d.ts +56 -7
  22. package/dist/index.js +692 -397
  23. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  24. package/dist/protocols/protocol-v2/uiInteraction.d.ts +3 -3
  25. package/dist/protocols/protocol-v2/uiInteraction.d.ts.map +1 -1
  26. package/dist/protocols/protocol-v2/unlockRetry.d.ts.map +1 -1
  27. package/dist/protocols/protocol-v2/walletSession.d.ts +4 -1
  28. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  29. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  30. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  31. package/dist/utils/patch.d.ts +1 -1
  32. package/package.json +4 -4
  33. package/src/api/BaseMethod.ts +1 -1
  34. package/src/api/FirmwareUpdateV4.ts +13 -6
  35. package/src/api/GetPassphraseState.ts +7 -1
  36. package/src/api/OpenWalletSession.ts +18 -3
  37. package/src/api/allnetwork/AllNetworkGetAddressBase.ts +24 -2
  38. package/src/api/device/DeviceUnlock.ts +1 -1
  39. package/src/api/protocol-v2/ProtocolInfoRequest.ts +3 -1
  40. package/src/core/deviceEventRegistration.ts +4 -0
  41. package/src/core/index.ts +46 -4
  42. package/src/data/messages/messages-protocol-v2.json +15 -0
  43. package/src/device/Device.ts +221 -24
  44. package/src/events/device.ts +4 -0
  45. package/src/events/ui-request.ts +32 -2
  46. package/src/protocols/protocol-v2/features.ts +9 -2
  47. package/src/protocols/protocol-v2/uiInteraction.ts +31 -5
  48. package/src/protocols/protocol-v2/unlockRetry.ts +33 -11
  49. package/src/protocols/protocol-v2/walletSession.ts +140 -32
  50. package/src/utils/deviceFeaturesUtils.ts +4 -0
@@ -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
@@ -168,8 +168,15 @@ export async function requestProtocolV2ProtocolInfo({
168
168
  }): Promise<ProtocolInfo> {
169
169
  const response =
170
170
  timeoutMs === undefined
171
- ? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {})
172
- : await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {}, { timeoutMs });
171
+ ? await commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
172
+ eventless_wallet_session: true,
173
+ })
174
+ : await commands.typedCall(
175
+ 'ProtocolInfoRequest',
176
+ 'ProtocolInfo',
177
+ { eventless_wallet_session: true },
178
+ { timeoutMs }
179
+ );
173
180
  return response.message;
174
181
  }
175
182
 
@@ -1,6 +1,6 @@
1
1
  import { UI_REQUEST, createUiMessage } from '../../events';
2
2
 
3
- import type { CoreMessage, ProtocolV2UiCompletion } from '../../events';
3
+ import type { CoreMessage, HardwareUiInteractionMeta, ProtocolV2UiCompletion } from '../../events';
4
4
  import type { Device } from '../../device/Device';
5
5
  import type { KnownDevice } from '../../types';
6
6
 
@@ -28,7 +28,15 @@ type ProtocolV2InteractionMethod = {
28
28
  protocolV2UiInteraction?: ProtocolV2InteractionDescriptor;
29
29
  };
30
30
 
31
- type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'>;
31
+ type InteractionDevice = Pick<Device, 'isProtocolV2' | 'toMessageObject'> &
32
+ Partial<
33
+ Pick<
34
+ Device,
35
+ | 'createProtocolV2UiPhaseMetadata'
36
+ | 'finishProtocolV2UiInteraction'
37
+ | 'hasOpenProtocolV2UiInteraction'
38
+ >
39
+ >;
32
40
  type PostMessage = (message: CoreMessage) => void;
33
41
 
34
42
  export const isProtocolV2UiEnabled = (method: { protocolV2UiMode?: 'auto' | 'none' }) =>
@@ -156,12 +164,17 @@ export class ProtocolV2UiInteractionCoordinator {
156
164
  this.methodInteraction = interaction;
157
165
  if (!interaction) return;
158
166
  const { request, ...metadata } = interaction;
167
+ const interactionMeta = this.device.createProtocolV2UiPhaseMetadata?.(
168
+ request === 'pin' ? 'pin' : 'button',
169
+ 'start'
170
+ );
159
171
 
160
172
  this.emit(
161
173
  request === 'pin' ? UI_REQUEST.REQUEST_PIN : UI_REQUEST.REQUEST_BUTTON,
162
174
  {
163
175
  device: this.device.toMessageObject() as KnownDevice,
164
176
  ...metadata,
177
+ ...(interactionMeta ? { interaction: interactionMeta } : {}),
165
178
  },
166
179
  `method:${request}:${interaction.reason}:${interaction.page ?? ''}:${
167
180
  interaction.operation ?? ''
@@ -169,7 +182,8 @@ export class ProtocolV2UiInteractionCoordinator {
169
182
  );
170
183
  }
171
184
 
172
- enterUnlockInteraction(method?: string) {
185
+ enterUnlockInteraction(method?: string): HardwareUiInteractionMeta | undefined {
186
+ const interaction = this.device.createProtocolV2UiPhaseMetadata?.('pin', 'start');
173
187
  this.emit(
174
188
  UI_REQUEST.REQUEST_PIN,
175
189
  {
@@ -178,9 +192,11 @@ export class ProtocolV2UiInteractionCoordinator {
178
192
  reason: 'device-locked',
179
193
  deviceOnly: true,
180
194
  method,
195
+ ...(interaction ? { interaction } : {}),
181
196
  },
182
197
  `unlock:${method ?? ''}`
183
198
  );
199
+ return interaction;
184
200
  }
185
201
 
186
202
  resumeMethodInteraction() {
@@ -188,9 +204,19 @@ export class ProtocolV2UiInteractionCoordinator {
188
204
  }
189
205
 
190
206
  close() {
191
- if (!this.device.isProtocolV2() || !this.opened || this.closed) return;
207
+ if (
208
+ !this.device.isProtocolV2() ||
209
+ (!this.opened && !this.device.hasOpenProtocolV2UiInteraction?.()) ||
210
+ this.closed
211
+ )
212
+ return;
192
213
  this.closed = true;
193
- this.postMessage(createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW));
214
+ const interaction = this.device.finishProtocolV2UiInteraction?.();
215
+ this.postMessage(
216
+ interaction
217
+ ? createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW, interaction)
218
+ : createUiMessage(UI_REQUEST.CLOSE_UI_WINDOW)
219
+ );
194
220
  }
195
221
 
196
222
  private emit(
@@ -5,6 +5,7 @@ import { refreshProtocolV2DeviceStatus, restoreProtocolV2WalletSession } from '.
5
5
 
6
6
  import type { BaseMethod } from '../../api/BaseMethod';
7
7
  import type { Device } from '../../device/Device';
8
+ import type { HardwareUiInteractionMeta } from '../../events';
8
9
  import type { ProtocolV2UiInteractionCoordinator } from './uiInteraction';
9
10
 
10
11
  const Log = getLogger(LoggerNames.Core);
@@ -46,6 +47,7 @@ export async function runMethodWithUnlockRetry(
46
47
  uiCoordinator?: UiInteractionCoordinator
47
48
  ) {
48
49
  const shouldEmitUi = isProtocolV2UiEnabled(method);
50
+ const shouldCoordinateUi = shouldEmitUi && uiCoordinator !== undefined;
49
51
  const isProtocolV2 = device.isProtocolV2();
50
52
  const requiresFreshStatus =
51
53
  isProtocolV2 &&
@@ -65,19 +67,29 @@ export async function runMethodWithUnlockRetry(
65
67
  device.features?.unlocked === false;
66
68
 
67
69
  if (shouldUnlockBeforeRun) {
68
- if (shouldEmitUi) {
69
- uiCoordinator?.enterUnlockInteraction(method.name);
70
- }
71
- await device.unlockDevice();
70
+ const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
71
+ ? uiCoordinator?.enterUnlockInteraction(method.name)
72
+ : undefined;
73
+ await device.unlockDevice(
74
+ undefined,
75
+ shouldCoordinateUi
76
+ ? { emitUiEvent: false, interaction: unlockInteraction }
77
+ : {
78
+ source: 'unlock-coordinator',
79
+ reason: 'device-locked',
80
+ deviceOnly: true,
81
+ method: method.name,
82
+ }
83
+ );
72
84
  Log.debug('Protocol V2 pre-unlock completed', { method: method.name });
73
85
  await restoreExpectedWalletSessionAfterUnlock(method, device);
74
- if (shouldEmitUi) {
86
+ if (shouldCoordinateUi) {
75
87
  uiCoordinator?.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
76
88
  }
77
89
  return method.run();
78
90
  }
79
91
 
80
- if (shouldEmitUi) {
92
+ if (shouldCoordinateUi) {
81
93
  uiCoordinator?.enterMethodInteraction(resolveProtocolV2UiInteraction(method));
82
94
  }
83
95
  try {
@@ -88,13 +100,23 @@ export async function runMethodWithUnlockRetry(
88
100
  }
89
101
 
90
102
  Log.debug('Protocol V2 unlock retry triggered', { method: method.name });
91
- if (shouldEmitUi) {
92
- uiCoordinator?.enterUnlockInteraction(method.name);
93
- }
94
- await device.unlockDevice();
103
+ const unlockInteraction: HardwareUiInteractionMeta | undefined = shouldCoordinateUi
104
+ ? uiCoordinator?.enterUnlockInteraction(method.name)
105
+ : undefined;
106
+ await device.unlockDevice(
107
+ undefined,
108
+ shouldCoordinateUi
109
+ ? { emitUiEvent: false, interaction: unlockInteraction }
110
+ : {
111
+ source: 'unlock-coordinator',
112
+ reason: 'device-locked',
113
+ deviceOnly: true,
114
+ method: method.name,
115
+ }
116
+ );
95
117
  Log.debug('Protocol V2 unlock completed', { method: method.name });
96
118
  await restoreExpectedWalletSessionAfterUnlock(method, device);
97
- if (shouldEmitUi) {
119
+ if (shouldCoordinateUi) {
98
120
  uiCoordinator?.resumeMethodInteraction();
99
121
  }
100
122
  try {
@@ -1,5 +1,5 @@
1
1
  import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
2
- import { DeviceSessionPinType } from '@onekeyfe/hd-transport';
2
+ import { DeviceSessionPinType, DeviceSessionSeedDomain } from '@onekeyfe/hd-transport';
3
3
 
4
4
  import { DEVICE } from '../../events';
5
5
  import { assertCompleteDeviceSession } from './deviceSession';
@@ -38,32 +38,69 @@ export async function refreshProtocolV2DeviceStatus(device: Device) {
38
38
  return device.updateProtocolV2Status(status);
39
39
  }
40
40
 
41
- const negotiateEventlessWalletSession = async (device: Device) => {
42
- await device.commands.typedCall('ProtocolInfoRequest', 'ProtocolInfo', {
43
- eventless_wallet_session: true,
41
+ export async function ensureProtocolV2WalletSessionUnlocked(device: Device) {
42
+ if (!device.isProtocolV2() || device.isBootloader?.() || device.isRomloader?.()) {
43
+ return false;
44
+ }
45
+
46
+ await refreshProtocolV2DeviceStatus(device);
47
+ if (device.state?.status.unlocked !== false) {
48
+ return false;
49
+ }
50
+
51
+ await device.unlockDevice(DeviceSessionPinType.Main, {
52
+ source: 'unlock-coordinator',
53
+ reason: 'device-locked',
54
+ deviceOnly: true,
44
55
  });
56
+ return true;
57
+ }
58
+
59
+ const negotiateEventlessWalletSession = async (device: Device) => {
60
+ await device.ensureProtocolV2RuntimeContext();
45
61
  };
46
62
 
47
- const getDeviceSession = async (device: Device, request: DeviceSessionGet) =>
48
- device.commands.typedCall('DeviceSessionGet', 'DeviceSession', request);
63
+ const getDeviceSession = async (
64
+ device: Device,
65
+ request: DeviceSessionGet,
66
+ deriveCardano?: boolean
67
+ ) => {
68
+ const payload =
69
+ deriveCardano === undefined
70
+ ? request
71
+ : {
72
+ ...request,
73
+ seed_domains: [
74
+ DeviceSessionSeedDomain.SeedDomain_Standard,
75
+ ...(deriveCardano ? [DeviceSessionSeedDomain.SeedDomain_Cardano] : []),
76
+ ],
77
+ };
78
+ return device.commands.typedCall('DeviceSessionGet', 'DeviceSession', payload);
79
+ };
49
80
 
50
81
  const askDevicePassphrase = async (device: Device, requestPayload: DeviceSessionAskPassphrase) => {
51
82
  await device.commands.typedCall('DeviceSessionAskPassphrase', 'Success', requestPayload);
52
83
  await refreshProtocolV2DeviceStatus(device);
53
84
  };
54
85
 
55
- const selectDeviceSession = async (device: Device, expectedPassphraseState?: string) => {
86
+ const selectDeviceSession = async (
87
+ device: Device,
88
+ expectedPassphraseState?: string,
89
+ deriveCardano?: boolean
90
+ ) => {
56
91
  const existsAttachPinUser = device.features?.attachToPinEnabled === true;
57
92
  const metadata = {
58
93
  source: 'wallet-session-coordinator' as const,
59
94
  reason: expectedPassphraseState ? ('session-recovery' as const) : ('open-wallet' as const),
60
95
  ...(expectedPassphraseState ? { expectedPassphraseState } : {}),
61
96
  };
97
+ const passphraseInteraction = device.createProtocolV2UiPhaseMetadata?.('passphrase', 'start');
62
98
  const response = await device.commands.promptPassphrase(
63
99
  {
64
100
  existsAttachPinUser,
65
101
  deviceOnly: false,
66
102
  ...metadata,
103
+ ...(passphraseInteraction ? { interaction: passphraseInteraction } : {}),
67
104
  },
68
105
  { cancelDeviceOnReject: false }
69
106
  );
@@ -102,9 +139,16 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
102
139
  'Attach PIN wallet selection is unavailable on this device.'
103
140
  );
104
141
  }
105
- device.emit(DEVICE.ATTACH_PIN_ON_DEVICE, device, metadata);
106
- await device.unlockDevice(DeviceSessionPinType.AttachToPin);
107
- return getDeviceSession(device, {});
142
+ const attachPinInteraction = device.createProtocolV2UiPhaseMetadata?.('pin', 'start');
143
+ device.emit(DEVICE.ATTACH_PIN_ON_DEVICE, device, {
144
+ ...metadata,
145
+ ...(attachPinInteraction ? { interaction: attachPinInteraction } : {}),
146
+ });
147
+ await device.unlockDevice(DeviceSessionPinType.AttachToPin, {
148
+ emitUiEvent: false,
149
+ interaction: attachPinInteraction,
150
+ });
151
+ return getDeviceSession(device, {}, deriveCardano);
108
152
  }
109
153
 
110
154
  if (hasHostPassphrase) {
@@ -112,12 +156,15 @@ const selectDeviceSession = async (device: Device, expectedPassphraseState?: str
112
156
  passphrase: hostPassphrase,
113
157
  on_device: false,
114
158
  });
115
- return getDeviceSession(device, {});
159
+ return getDeviceSession(device, {}, deriveCardano);
116
160
  }
117
161
 
118
- device.emit(DEVICE.PASSPHRASE_ON_DEVICE, device, metadata);
162
+ device.emit(DEVICE.PASSPHRASE_ON_DEVICE, device, {
163
+ ...metadata,
164
+ ...(passphraseInteraction ? { interaction: passphraseInteraction } : {}),
165
+ });
119
166
  await askDevicePassphrase(device, { on_device: true });
120
- return getDeviceSession(device, {});
167
+ return getDeviceSession(device, {}, deriveCardano);
121
168
  };
122
169
 
123
170
  export async function getProtocolV2WalletSession(
@@ -128,7 +175,9 @@ export async function getProtocolV2WalletSession(
128
175
  initSession?: boolean;
129
176
  expectedPassphraseState?: string;
130
177
  onlyMainPin?: boolean;
178
+ selectMainWalletBeforeRestore?: boolean;
131
179
  resumeOnly?: boolean;
180
+ deriveCardano?: boolean;
132
181
  }
133
182
  ) {
134
183
  const forceWalletSelection =
@@ -161,17 +210,57 @@ export async function getProtocolV2WalletSession(
161
210
  : undefined;
162
211
  let response;
163
212
  let resumed = false;
213
+ let mainWalletSelected = false;
214
+
215
+ const clearCurrentWalletSession = () => {
216
+ if (options?.onlyMainPin) {
217
+ device.clearStandardInternalState?.();
218
+ } else {
219
+ device.clearInternalState();
220
+ }
221
+ };
222
+
223
+ const rejectMismatchedAttachPinWallet = async () => {
224
+ const features = await refreshProtocolV2DeviceStatus(device);
225
+ if (features.unlockedAttachPin !== true) {
226
+ return;
227
+ }
228
+
229
+ try {
230
+ await device.lockDevice();
231
+ } catch {
232
+ // Reject the mismatched Attach PIN wallet even when older firmware cannot lock.
233
+ }
234
+ clearCurrentWalletSession();
235
+ throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckUnlockTypeError);
236
+ };
237
+
238
+ const selectMainWallet = async (force = false) => {
239
+ if (force || !mainWalletSelected) {
240
+ await device.unlockDevice(DeviceSessionPinType.Main, {
241
+ source: 'wallet-session-coordinator',
242
+ reason: expectedPassphraseState ? 'session-recovery' : 'open-wallet',
243
+ deviceOnly: true,
244
+ });
245
+ mainWalletSelected = true;
246
+ }
247
+ };
164
248
 
165
249
  if (options?.onlyMainPin) {
166
250
  expectedPassphraseState = cachedStandardSession?.passphraseState;
167
251
  if (cachedStandardSession) {
168
252
  try {
169
- if (device.features?.unlockedAttachPin === true) {
170
- await device.unlockDevice(DeviceSessionPinType.Main);
253
+ if (options?.selectMainWalletBeforeRestore) {
254
+ await selectMainWallet();
171
255
  }
172
- response = await getDeviceSession(device, {
173
- session_id: cachedStandardSession.sessionId,
174
- });
256
+ response = await getDeviceSession(
257
+ device,
258
+ {
259
+ session_id: cachedStandardSession.sessionId,
260
+ btc_test_address: cachedStandardSession.passphraseState,
261
+ },
262
+ options?.deriveCardano
263
+ );
175
264
  resumed = true;
176
265
  } catch (error) {
177
266
  device.clearStandardInternalState?.();
@@ -183,12 +272,19 @@ export async function getProtocolV2WalletSession(
183
272
  }
184
273
 
185
274
  if (!response) {
186
- await device.unlockDevice(DeviceSessionPinType.Main);
187
- response = await getDeviceSession(device, {});
275
+ await selectMainWallet();
276
+ response = await getDeviceSession(device, {}, options?.deriveCardano);
188
277
  }
189
278
  } else if (cachedSessionId && expectedPassphraseState) {
190
279
  try {
191
- response = await getDeviceSession(device, { session_id: cachedSessionId });
280
+ response = await getDeviceSession(
281
+ device,
282
+ {
283
+ session_id: cachedSessionId,
284
+ btc_test_address: expectedPassphraseState,
285
+ },
286
+ options?.deriveCardano
287
+ );
192
288
  resumed = true;
193
289
  } catch (error) {
194
290
  device.clearInternalState();
@@ -197,6 +293,18 @@ export async function getProtocolV2WalletSession(
197
293
  }
198
294
  resumed = false;
199
295
  }
296
+ } else if (expectedPassphraseState) {
297
+ try {
298
+ response = await getDeviceSession(
299
+ device,
300
+ { btc_test_address: expectedPassphraseState },
301
+ options?.deriveCardano
302
+ );
303
+ } catch (error) {
304
+ if (options?.resumeOnly || !isWalletSessionInvalidError(error)) {
305
+ throw error;
306
+ }
307
+ }
200
308
  }
201
309
 
202
310
  if (!response) {
@@ -204,7 +312,7 @@ export async function getProtocolV2WalletSession(
204
312
  device.clearInternalState();
205
313
  throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
206
314
  }
207
- response = await selectDeviceSession(device, expectedPassphraseState);
315
+ response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
208
316
  }
209
317
 
210
318
  let { message } = response;
@@ -220,17 +328,18 @@ export async function getProtocolV2WalletSession(
220
328
  }
221
329
  if (expectedPassphraseState && expectedPassphraseState !== message.btc_test_address) {
222
330
  resumed = false;
331
+ await rejectMismatchedAttachPinWallet();
223
332
  if (options?.resumeOnly) {
224
333
  device.clearInternalState();
225
334
  throw ERRORS.TypedError(HardwareErrorCode.WalletSessionInvalid);
226
335
  }
227
336
  if (options?.onlyMainPin) {
228
337
  device.clearStandardInternalState?.();
229
- await device.unlockDevice(DeviceSessionPinType.Main);
230
- response = await getDeviceSession(device, {});
338
+ await selectMainWallet(true);
339
+ response = await getDeviceSession(device, {}, options?.deriveCardano);
231
340
  } else {
232
341
  device.clearInternalState();
233
- response = await selectDeviceSession(device, expectedPassphraseState);
342
+ response = await selectDeviceSession(device, expectedPassphraseState, options?.deriveCardano);
234
343
  }
235
344
  message = response.message;
236
345
  try {
@@ -244,11 +353,8 @@ export async function getProtocolV2WalletSession(
244
353
  throw error;
245
354
  }
246
355
  if (expectedPassphraseState !== message.btc_test_address) {
247
- if (options?.onlyMainPin) {
248
- device.clearStandardInternalState?.();
249
- } else {
250
- device.clearInternalState();
251
- }
356
+ await rejectMismatchedAttachPinWallet();
357
+ clearCurrentWalletSession();
252
358
  throw ERRORS.TypedError(HardwareErrorCode.DeviceCheckPassphraseStateError);
253
359
  }
254
360
  }
@@ -269,17 +375,19 @@ export async function getProtocolV2WalletSession(
269
375
  return {
270
376
  passphraseState: message.btc_test_address,
271
377
  newSession: message.session_id,
272
- unlockedAttachPin: device.features?.unlockedAttachPin ?? undefined,
378
+ unlockedAttachPin: mainWalletSelected ? false : undefined,
273
379
  resumed,
274
380
  };
275
381
  }
276
382
 
277
383
  export async function restoreProtocolV2WalletSession(
278
384
  device: Device,
279
- expectedPassphraseState: string
385
+ expectedPassphraseState: string,
386
+ deriveCardano?: boolean
280
387
  ) {
281
388
  return getProtocolV2WalletSession(device, {
282
389
  expectedPassphraseState,
283
390
  resumeOnly: true,
391
+ deriveCardano,
284
392
  });
285
393
  }
@@ -106,6 +106,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
106
106
  expectPassphraseState?: string;
107
107
  onlyMainPin?: boolean;
108
108
  initSession?: boolean;
109
+ deriveCardano?: boolean;
109
110
  }
110
111
  ) => {
111
112
  if (device.isProtocolV2()) {
@@ -121,6 +122,7 @@ export const getPassphraseStateWithRefreshDeviceInfo = async (
121
122
  initSession: options?.initSession,
122
123
  expectedPassphraseState: options?.expectPassphraseState,
123
124
  onlyMainPin: options?.onlyMainPin,
125
+ deriveCardano: options?.deriveCardano,
124
126
  });
125
127
  }
126
128
 
@@ -185,6 +187,7 @@ export const getPassphraseState = async (
185
187
  expectPassphraseState?: string;
186
188
  onlyMainPin?: boolean;
187
189
  initSession?: boolean;
190
+ deriveCardano?: boolean;
188
191
  }
189
192
  ): Promise<{
190
193
  passphraseState: string | undefined;
@@ -205,6 +208,7 @@ export const getPassphraseState = async (
205
208
  initSession: options?.initSession,
206
209
  expectedPassphraseState: options?.expectPassphraseState,
207
210
  onlyMainPin: options?.onlyMainPin,
211
+ deriveCardano: options?.deriveCardano,
208
212
  });
209
213
  }
210
214