@onekeyfe/hwk-ledger-adapter 1.2.5-alpha.2 → 1.2.5-alpha.3

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.
package/dist/index.mjs CHANGED
@@ -6,41 +6,30 @@ import {
6
6
  DeviceJobQueue,
7
7
  EConnectorInteraction,
8
8
  HardwareErrorCode as HardwareErrorCode3,
9
- OperationRegistry,
10
- SDK,
11
9
  TypedEventEmitter,
12
10
  UI_REQUEST,
13
11
  UI_REQUEST_PREEMPTED_TAG,
14
12
  UiRequestRegistry,
15
- canReplayHardwareMethodAfterTransportFailure,
16
13
  createHwkError,
17
14
  deriveDeviceFingerprint,
18
15
  failure as failure2,
19
- isHardwareOperationId,
20
- isHwkRecoveryHint,
21
- operationMayHaveCompletedParams,
22
16
  rehydrateConnectorError,
23
- requestBleDeviceSelection,
24
- requestSaveDeviceBinding,
25
- resolveSearchTargetReusePolicy,
26
17
  success
27
18
  } from "@onekeyfe/hwk-adapter-core";
28
19
 
29
20
  // src/errors.ts
30
- import {
31
- HardwareErrorCode,
32
- defaultOriginForCode,
33
- defaultRecoveryForCode,
34
- enrichErrorMessage
35
- } from "@onekeyfe/hwk-adapter-core";
36
- function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
21
+ import { HardwareErrorCode, enrichErrorMessage } from "@onekeyfe/hwk-adapter-core";
22
+ var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
23
+ function createMultipleUsbLedgerDevicesError() {
24
+ return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
25
+ code: HardwareErrorCode.DeviceOneDeviceOnly
26
+ });
27
+ }
28
+ function ledgerFailure(code, error, appName, tag, params) {
37
29
  const payload = { error, code };
38
30
  if (appName !== void 0) payload.appName = appName;
39
31
  if (tag !== void 0) payload._tag = tag;
40
32
  if (params !== void 0) payload.params = params;
41
- const resolvedOrigin = origin ?? defaultOriginForCode(code);
42
- if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
43
- payload.recovery = recovery ?? defaultRecoveryForCode(code);
44
33
  return { success: false, payload };
45
34
  }
46
35
  var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
@@ -181,10 +170,7 @@ var ERROR_TAG = {
181
170
  UnknownDevice: "UnknownDeviceError",
182
171
  DeviceSessionRefresher: "DeviceSessionRefresherError",
183
172
  DeviceNotInitialized: "DeviceNotInitializedError",
184
- // DMK class OpeningConnectionError carries `_tag` "ConnectionOpeningError"; both
185
- // spellings are kept so a DMK release that aligns them still classifies.
186
- OpeningConnection: "ConnectionOpeningError",
187
- OpeningConnectionLegacy: "OpeningConnectionError",
173
+ OpeningConnection: "OpeningConnectionError",
188
174
  DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
189
175
  DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
190
176
  Disconnect: "DisconnectError",
@@ -197,16 +183,7 @@ var ERROR_TAG = {
197
183
  // DMK remote-network failures (manager-api HTTP / secure-channel WS).
198
184
  WebSocketConnection: "WebSocketConnectionError",
199
185
  HttpFetch: "FetchError",
200
- NetworkDA: "NetworkDAError",
201
- InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
202
- ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
203
- // DMK OS device actions. SecureChannelError is what mapInstallDAErrors() leaves
204
- // after splitting out device answers, i.e. the relay itself broke.
205
- SecureChannel: "SecureChannelError",
206
- RefusedByUserDA: "RefusedByUserDAError",
207
- AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
208
- OutOfMemoryDA: "OutOfMemoryDAError",
209
- DeviceNotOnboarded: "DeviceNotOnboardedError"
186
+ InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError"
210
187
  };
211
188
  function isDeviceLockedError(err) {
212
189
  if (!err || typeof err !== "object") return false;
@@ -236,6 +213,7 @@ function isBlePairingFailureError(err) {
236
213
  return false;
237
214
  }
238
215
  var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
216
+ ERROR_TAG.DeviceLocked,
239
217
  ERROR_TAG.DeviceNotAdvertising,
240
218
  ERROR_TAG.BlePairingTimeout,
241
219
  ERROR_TAG.BleGattBondingFailed,
@@ -247,7 +225,6 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
247
225
  ERROR_TAG.DeviceSessionRefresher,
248
226
  ERROR_TAG.DeviceNotInitialized,
249
227
  ERROR_TAG.OpeningConnection,
250
- ERROR_TAG.OpeningConnectionLegacy,
251
228
  ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
252
229
  ERROR_TAG.DeviceDisconnectedWhileSending,
253
230
  ERROR_TAG.Disconnect,
@@ -262,10 +239,7 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
262
239
  // Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
263
240
  ERROR_TAG.DeviceNotInDiscoveryCache
264
241
  ]);
265
- var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
266
- ERROR_TAG.OpeningConnection,
267
- ERROR_TAG.OpeningConnectionLegacy
268
- ]);
242
+ var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
269
243
  var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
270
244
  ERROR_TAG.DeviceNotRecognized,
271
245
  ERROR_TAG.DeviceSessionNotFound,
@@ -277,18 +251,17 @@ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
277
251
  ERROR_TAG.WebHIDDisconnect
278
252
  ]);
279
253
  function isConnectionLevelError(err) {
280
- return hasErrorTag(err, CONNECTION_LEVEL_TAGS);
254
+ if (!err || typeof err !== "object") return false;
255
+ const tag = err._tag;
256
+ if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
257
+ const e = err;
258
+ if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
259
+ if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
260
+ return false;
281
261
  }
282
262
  function isKnownConnectionTag(tag) {
283
263
  return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
284
264
  }
285
- var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
286
- ERROR_TAG.OpeningConnection,
287
- ERROR_TAG.OpeningConnectionLegacy
288
- ]);
289
- function isConnectionOpeningTag(tag) {
290
- return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
291
- }
292
265
  function hasStatusCode(err, codeSet) {
293
266
  if (!err || typeof err !== "object") return false;
294
267
  const e = err;
@@ -324,28 +297,28 @@ function hasInvalidArgumentCode(err) {
324
297
  if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
325
298
  return false;
326
299
  }
327
- function hasErrorTag(err, tags) {
300
+ function isDeviceNotFoundError(err) {
328
301
  if (!err || typeof err !== "object") return false;
302
+ const tag = err._tag;
303
+ if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
329
304
  const e = err;
330
- if (typeof e._tag === "string" && tags.has(e._tag)) return true;
331
- if (e.originalError != null && hasErrorTag(e.originalError, tags)) return true;
332
- if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
305
+ if (e.originalError != null && isDeviceNotFoundError(e.originalError)) return true;
306
+ if (e.error != null && e._tag && isDeviceNotFoundError(e.error)) return true;
333
307
  return false;
334
308
  }
335
- function isDeviceNotFoundError(err) {
336
- return hasErrorTag(err, DEVICE_NOT_FOUND_TAGS);
337
- }
338
309
  function isDeviceBusyError(err) {
339
- return hasErrorTag(err, DEVICE_BUSY_TAGS);
310
+ if (!err || typeof err !== "object") return false;
311
+ const tag = err._tag;
312
+ if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
313
+ const e = err;
314
+ if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
315
+ if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
316
+ return false;
340
317
  }
341
- var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
342
- ERROR_TAG.UserRefusedOnDevice,
343
- ERROR_TAG.RefusedByUserDA
344
- ]);
345
318
  function isUserRejectedError(err) {
346
319
  if (!err || typeof err !== "object") return false;
347
320
  const e = err;
348
- if (hasErrorTag(err, USER_REJECTED_TAGS)) return true;
321
+ if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
349
322
  if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
350
323
  if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
351
324
  return false;
@@ -376,33 +349,15 @@ function isAppNotInstalledError(err) {
376
349
  if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
377
350
  return false;
378
351
  }
379
- var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
380
352
  function isOutOfMemoryError(err) {
381
- return hasErrorTag(err, OUT_OF_MEMORY_TAGS);
382
- }
383
- var APP_ALREADY_INSTALLED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.AppAlreadyInstalledDA]);
384
- function isAppAlreadyInstalledError(err) {
385
- return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
386
- }
387
- var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.DeviceNotOnboarded]);
388
- function isDeviceNotOnboardedError(err) {
389
- return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
390
- }
391
- var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
392
- ERROR_TAG.InvalidFirmwareMetadataResponse,
393
- ERROR_TAG.ApplicationsMetadataTask
394
- ]);
395
- function isFirmwareMetadataError(err) {
396
- return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
397
- }
398
- var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
399
- function isSecureChannelError(err) {
400
- return hasErrorTag(err, SECURE_CHANNEL_TAGS);
353
+ if (!err || typeof err !== "object") return false;
354
+ const e = err;
355
+ return e._tag === "OutOfMemoryDAError";
401
356
  }
402
357
  var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
403
358
  ERROR_TAG.WebSocketConnection,
404
359
  ERROR_TAG.HttpFetch,
405
- ERROR_TAG.NetworkDA
360
+ ERROR_TAG.InvalidFirmwareMetadataResponse
406
361
  ]);
407
362
  function isNetworkError(err) {
408
363
  if (!err || typeof err !== "object") return false;
@@ -452,34 +407,23 @@ function isTransportStuck(err) {
452
407
  function isStuckAppStateError(err) {
453
408
  return isAppStuckByApdu(err) || isTransportStuck(err);
454
409
  }
455
- var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
456
- function nestedErrorMessage(nested) {
457
- if (!nested || typeof nested !== "object") return void 0;
458
- const { message } = nested;
459
- if (typeof message !== "string") return void 0;
460
- const trimmed = message.trim();
461
- if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
462
- return message;
463
- }
464
410
  function mapLedgerError(err, opts) {
465
411
  let originalMessage = "Unknown Ledger error";
466
412
  if (err instanceof Error) {
467
413
  originalMessage = err.message;
468
414
  } else if (err && typeof err === "object") {
469
415
  const e = err;
470
- originalMessage = String(
471
- e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
472
- );
416
+ originalMessage = String(e.message ?? e._tag ?? e.type ?? JSON.stringify(err));
473
417
  }
474
418
  let code;
475
419
  if (isDeviceLockedError(err)) {
476
420
  code = HardwareErrorCode.DeviceLocked;
477
421
  } else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
478
422
  code = HardwareErrorCode.DeviceNotFound;
479
- } else if (isBlePairingFailureError(err)) {
480
- code = HardwareErrorCode.BlePairingTimeout;
481
423
  } else if (isDeviceBusyError(err)) {
482
424
  code = HardwareErrorCode.DeviceBusy;
425
+ } else if (isBlePairingFailureError(err)) {
426
+ code = HardwareErrorCode.BlePairingTimeout;
483
427
  } else if (isUserAbortedError(err)) {
484
428
  code = HardwareErrorCode.UserAborted;
485
429
  } else if (isUserRejectedError(err)) {
@@ -488,18 +432,10 @@ function mapLedgerError(err, opts) {
488
432
  code = HardwareErrorCode.WrongApp;
489
433
  } else if (isAppNotInstalledError(err)) {
490
434
  code = HardwareErrorCode.AppNotInstalled;
491
- } else if (isAppAlreadyInstalledError(err)) {
492
- code = HardwareErrorCode.AppAlreadyInstalled;
493
435
  } else if (isOutOfMemoryError(err)) {
494
436
  code = HardwareErrorCode.DeviceOutOfMemory;
495
- } else if (isDeviceNotOnboardedError(err)) {
496
- code = HardwareErrorCode.DeviceNotInitialized;
497
- } else if (isFirmwareMetadataError(err)) {
498
- code = HardwareErrorCode.LedgerFirmwareMetadataError;
499
437
  } else if (isNetworkError(err)) {
500
438
  code = HardwareErrorCode.NetworkError;
501
- } else if (isSecureChannelError(err)) {
502
- code = HardwareErrorCode.LedgerSecureChannelError;
503
439
  } else if (isDeviceDisconnectedError(err)) {
504
440
  code = HardwareErrorCode.DeviceDisconnected;
505
441
  } else if (isTimeoutError(err)) {
@@ -512,32 +448,11 @@ function mapLedgerError(err, opts) {
512
448
  }
513
449
  const errAppName = err && typeof err === "object" ? err.appName : void 0;
514
450
  const appName = errAppName ?? opts?.defaultAppName;
515
- return {
516
- code,
517
- message: enrichErrorMessage(code, originalMessage),
518
- origin: defaultOriginForCode(code),
519
- appName
520
- };
521
- }
522
-
523
- // src/utils/queueKey.ts
524
- var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
525
- function ledgerQueueKey({
526
- operationId,
527
- connectId
528
- }) {
529
- return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
451
+ return { code, message: enrichErrorMessage(code, originalMessage), appName };
530
452
  }
531
453
 
532
454
  // src/adapter/methods/allNetworkGetAddress.ts
533
- import {
534
- HardwareErrorCode as HardwareErrorCode2,
535
- failure,
536
- isConnectionLost,
537
- isUserRefusal,
538
- resolveHardwareOperationTarget,
539
- runAllNetworkGetAddress
540
- } from "@onekeyfe/hwk-adapter-core";
455
+ import { HardwareErrorCode as HardwareErrorCode2, failure, runAllNetworkGetAddress } from "@onekeyfe/hwk-adapter-core";
541
456
 
542
457
  // src/utils/sdkEventBus.ts
543
458
  var listeners = /* @__PURE__ */ new Set();
@@ -594,103 +509,65 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
594
509
  var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
595
510
  function createAllNetworkGetAddress({
596
511
  callChain,
597
- getChainFingerprint,
598
- retainOperation,
599
- errorToFailure,
600
- createCancelScope
512
+ getChainFingerprint
601
513
  }) {
602
514
  return async function allNetworkGetAddress(connectId, _deviceId, params) {
603
- debugLog("[LedgerAdapter][REQ]", {
604
- method: "allNetworkGetAddress",
605
- connectId,
606
- itemCount: params.bundle.length
607
- });
608
- const target = resolveHardwareOperationTarget(connectId, params.operationId, "ledger");
609
- if (!target.success) return target;
610
- const effectiveTargetId = target.payload.targetId ?? "";
611
- let releaseOperationRetention;
612
- try {
613
- releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
614
- } catch (error) {
615
- return errorToFailure(error);
616
- }
617
- const cancelScope = createCancelScope(
618
- ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
619
- );
515
+ debugLog("[LedgerAdapter][REQ]", { method: "allNetworkGetAddress", connectId, params });
620
516
  const installContext = {};
621
517
  const commonParams = {
622
- autoInstallApp: params.autoInstallApp,
623
- operationId: target.payload.operationId,
624
- knownConnections: params.knownConnections,
625
- extra: params.extra,
626
- allowDeviceSelection: params.allowDeviceSelection,
627
- supportedTransports: params.supportedTransports
518
+ autoInstallApp: params.autoInstallApp
628
519
  };
629
520
  const chainFingerprints = /* @__PURE__ */ new Map();
630
- try {
631
- const result = await runAllNetworkGetAddress({
632
- connectId: effectiveTargetId,
633
- deviceId: _deviceId,
634
- params,
635
- normalizeItem: normalizeLedgerAllNetworkItem,
636
- buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
637
- callItem: async ({ method, chain, item }) => {
638
- if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
639
- const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
640
- return callAllNetworkMethod(
641
- callChain,
642
- effectiveTargetId,
643
- itemDeviceId,
644
- method,
645
- item,
646
- commonParams,
647
- installContext
648
- );
649
- },
650
- attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
651
- getChainFingerprint,
652
- effectiveTargetId,
521
+ const result = await runAllNetworkGetAddress({
522
+ connectId,
523
+ deviceId: _deviceId,
524
+ params,
525
+ normalizeItem: normalizeLedgerAllNetworkItem,
526
+ buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
527
+ callItem: async ({ method, chain, item }) => {
528
+ const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
529
+ return callAllNetworkMethod(
530
+ callChain,
531
+ connectId,
532
+ itemDeviceId,
533
+ method,
653
534
  item,
654
- chain,
655
- payload,
656
- chainFingerprints,
657
- installContext,
658
- cancelScope.signal
659
- ),
660
- shouldAbortBundle: isTopLevelAllNetworkFailure,
661
- buildTopLevelFailure: (response) => {
662
- const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
663
- return failure(
664
- code,
665
- response.payload?.error ?? "All-network get-address aborted",
666
- response.payload?.params
667
- );
668
- }
669
- });
670
- debugLog("[LedgerAdapter][RES]", {
671
- method: "allNetworkGetAddress",
672
- success: result.success,
673
- payload: result
674
- });
675
- return result;
676
- } finally {
677
- cancelScope.release();
678
- releaseOperationRetention?.();
679
- }
535
+ commonParams,
536
+ installContext
537
+ );
538
+ },
539
+ attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
540
+ getChainFingerprint,
541
+ connectId,
542
+ item,
543
+ chain,
544
+ payload,
545
+ chainFingerprints
546
+ ),
547
+ shouldAbortBundle: isTopLevelAllNetworkFailure,
548
+ buildTopLevelFailure: (response) => {
549
+ const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
550
+ return failure(
551
+ code,
552
+ response.payload?.error ?? "All-network get-address aborted",
553
+ response.payload?.params
554
+ );
555
+ }
556
+ });
557
+ debugLog("[LedgerAdapter][RES]", {
558
+ method: "allNetworkGetAddress",
559
+ success: result.success,
560
+ payload: result
561
+ });
562
+ return result;
680
563
  };
681
564
  }
682
- function buildCancelledFailure(signal) {
683
- const reason = signal.reason;
684
- const code = typeof reason?.code === "number" ? reason.code : HardwareErrorCode2.UserAborted;
685
- const message = typeof reason?.message === "string" ? reason.message : "";
686
- return failure(code, message || "All-network get-address cancelled");
687
- }
688
565
  function isTopLevelAllNetworkFailure(response) {
689
566
  if (response.success) {
690
567
  return false;
691
568
  }
692
569
  const code = response.payload?.code;
693
- return code === HardwareErrorCode2.DeviceMismatch || isUserRefusal(code) || isConnectionLost(code);
570
+ return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
694
571
  }
695
572
  function getItemDeviceId(item) {
696
573
  const { deviceId } = item;
@@ -720,18 +597,8 @@ function normalizeLedgerAllNetworkItem(method, item) {
720
597
  const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
721
598
  return coin ? { ...item, coin } : item;
722
599
  }
723
- async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
724
- const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
725
- if (!knownFingerprint && cancelSignal.aborted) {
726
- const cancelled = buildCancelledFailure(cancelSignal);
727
- return { ...item, success: false, payload: cancelled.payload };
728
- }
729
- const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
730
- getChainFingerprint,
731
- context.connection?.connectId ?? connectId,
732
- chain,
733
- context
734
- );
600
+ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
601
+ const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(getChainFingerprint, connectId, chain);
735
602
  if (!fingerprint) {
736
603
  return buildFingerprintBootstrapFailure(item, chain);
737
604
  }
@@ -752,8 +619,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
752
619
  }
753
620
  };
754
621
  }
755
- async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
756
- const response = await getChainFingerprint(connectId, chain, context);
622
+ async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
623
+ const response = await getChainFingerprint(connectId, chain);
757
624
  return response.success ? response.payload : "";
758
625
  }
759
626
  function buildFingerprintBootstrapFailure(item, chain) {
@@ -846,16 +713,6 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
846
713
  function formatDeviceMismatchError(expected, actual) {
847
714
  return `Wrong device: expected ${expected}, got ${actual}`;
848
715
  }
849
- var LOG_REDACTED_RESULT_KEYS = ["ufvk", "orchardFvk"];
850
- function redactResultForLog(result) {
851
- if (!result || typeof result !== "object") return result;
852
- const keys = LOG_REDACTED_RESULT_KEYS.filter((key) => key in result);
853
- if (!keys.length) return result;
854
- return { ...result, ...Object.fromEntries(keys.map((key) => [key, "[redacted]"])) };
855
- }
856
- function isLostConnectionError(err) {
857
- return isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err);
858
- }
859
716
  var BTC_HIGH_INDEX_THRESHOLD = 100;
860
717
  function btcAccountIndexFromPath(path) {
861
718
  const segments = path.replace(/^m\//, "").split("/");
@@ -867,31 +724,9 @@ function btcAccountIndexFromPath(path) {
867
724
  var _LedgerAdapter = class _LedgerAdapter {
868
725
  constructor(connector, options) {
869
726
  this.vendor = "ledger";
870
- // Cancel frees the DMK action and its intent-queue slot, but cannot retract a
871
- // confirmation screen the device is already showing.
872
- this.cancelCapability = "stops-waiting";
873
727
  this.emitter = new TypedEventEmitter();
874
- this._operations = new OperationRegistry({
875
- vendor: "ledger",
876
- onEnded: (operation, reason) => {
877
- const binding = this._pendingOperationBindings.get(operation.operationId);
878
- if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
879
- this._finishBleBinding("cancelled");
880
- }
881
- this._pendingOperationBindings.delete(operation.operationId);
882
- this.emitter.emit(SDK.OPERATION_ENDED, {
883
- type: SDK.OPERATION_ENDED,
884
- payload: { operationId: operation.operationId, reason }
885
- });
886
- if (reason === "timeout") {
887
- void this._releaseOperationConnection(operation);
888
- }
889
- }
890
- });
891
728
  this._discoveredDevices = /* @__PURE__ */ new Map();
892
729
  this._sessions = /* @__PURE__ */ new Map();
893
- this._pendingOperationBindings = /* @__PURE__ */ new Map();
894
- this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
895
730
  this._uiRegistry = new UiRequestRegistry();
896
731
  // BTC App rejects account index >= 100 unless display=true. Cached per
897
732
  // adapter instance: first 100+ path asks the user once via UI request,
@@ -905,31 +740,10 @@ var _LedgerAdapter = class _LedgerAdapter {
905
740
  this._deviceAuthenticityQueueTail = Promise.resolve();
906
741
  // Shared across concurrent callers — only `cancel()` aborts.
907
742
  this._doConnectAbortController = null;
908
- this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
909
- this._connectorIdleWaiters = /* @__PURE__ */ new Set();
910
- this._resetPromise = null;
911
- this._stateGeneration = 0;
912
- this._connectorTeardownTail = Promise.resolve();
913
- this._pendingConnectorTeardowns = 0;
914
- this._activeOperationJobs = /* @__PURE__ */ new Set();
915
- this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
916
743
  this._installProgressLastEmittedValue = -Infinity;
917
744
  this.allNetworkGetAddress = createAllNetworkGetAddress({
918
745
  callChain: this.callChain.bind(this),
919
- getChainFingerprint: async (connectId, chain, context) => {
920
- try {
921
- const fingerprint = await this._computeChainFingerprint(
922
- chain,
923
- (method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
924
- );
925
- return success(fingerprint);
926
- } catch (error) {
927
- return this.errorToFailure(error);
928
- }
929
- },
930
- retainOperation: (operationId) => this._operations.retain(operationId),
931
- errorToFailure: (error) => this.errorToFailure(error),
932
- createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
746
+ getChainFingerprint: (connectId, chain) => this.getChainFingerprint(connectId, "", chain)
933
747
  });
934
748
  // ---------------------------------------------------------------------------
935
749
  // Private helpers
@@ -939,7 +753,7 @@ var _LedgerAdapter = class _LedgerAdapter {
939
753
  *
940
754
  * - If a session already exists for the given connectId, reuse it.
941
755
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
942
- * - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
756
+ * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
943
757
  */
944
758
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
945
759
  this._connectingPromise = null;
@@ -956,12 +770,6 @@ var _LedgerAdapter = class _LedgerAdapter {
956
770
  });
957
771
  };
958
772
  this.deviceDisconnectHandler = (data) => {
959
- const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
960
- if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
961
- this._pendingOperationDisconnects.add(activeOperation.operationId);
962
- } else {
963
- this._operations.endByConnectionKey(data.connectId, "disconnect");
964
- }
965
773
  this._discoveredDevices.delete(data.connectId);
966
774
  this._sessions.delete(data.connectId);
967
775
  this.emitter.emit(DEVICE.DISCONNECT, {
@@ -970,8 +778,10 @@ var _LedgerAdapter = class _LedgerAdapter {
970
778
  });
971
779
  };
972
780
  // Forward connector `ui-event` to the public hw.emitter so consumers only
973
- // need to subscribe in one place. The AppInstallProgress variant re-keys
974
- // sessionId to connectId via `_sessions`, and drops if no mapping exists.
781
+ // need to subscribe in one place. For the AppInstallProgress variant we
782
+ // re-key sessionId → connectId via the live _sessions map; if no mapping
783
+ // exists (race during teardown) we drop. All other variants pass through
784
+ // unchanged.
975
785
  this.uiEventForwarder = (event) => {
976
786
  if (event.type === EConnectorInteraction.AppInstallProgress) {
977
787
  let connectId;
@@ -1016,28 +826,11 @@ var _LedgerAdapter = class _LedgerAdapter {
1016
826
  this._jobQueue = new DeviceJobQueue();
1017
827
  this.registerEventListeners();
1018
828
  }
1019
- _finishBleBinding(status) {
1020
- const selectionRequestId = this._bindingSelectionRequestId;
1021
- this._bindingSelectionRequestId = void 0;
1022
- if (selectionRequestId)
1023
- this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
1024
- type: UI_REQUEST.DEVICE_BINDING_STATUS,
1025
- payload: { selectionRequestId, status }
1026
- });
1027
- }
1028
- _isBleConnection() {
1029
- return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
1030
- }
1031
829
  // Transport
1032
830
  get activeTransport() {
1033
- return this._isBleConnection() ? "ble" : "hid";
831
+ return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
1034
832
  }
1035
833
  getAvailableTransports() {
1036
- if (this.connector.availableTransports) {
1037
- return this.connector.availableTransports.map(
1038
- (transport) => transport === "ble" ? "ble" : "hid"
1039
- );
1040
- }
1041
834
  return this.activeTransport ? [this.activeTransport] : [];
1042
835
  }
1043
836
  // Connector is bound at construction; switching requires a new adapter.
@@ -1054,39 +847,21 @@ var _LedgerAdapter = class _LedgerAdapter {
1054
847
  * The next operation will re-discover and re-connect automatically.
1055
848
  */
1056
849
  resetState() {
1057
- void this._resetStateAndDisconnectSessions();
1058
- }
1059
- _resetStateAndDisconnectSessions() {
1060
- if (this._resetPromise) return this._resetPromise;
1061
- const sessionIds = new Set(this._sessions.values());
1062
- this._stateGeneration += 1;
1063
- this._finishBleBinding("cancelled");
1064
- this._operations.endAll("runtime-reset");
1065
- this._doConnectAbortController?.abort();
1066
850
  this._discoveredDevices.clear();
1067
851
  this._sessions.clear();
1068
- this._verifiedBleReconnectTargets.clear();
1069
852
  this._connectingPromise = null;
1070
- this._doConnectAbortController = null;
1071
853
  this._uiRegistry.reset();
1072
854
  this._jobQueue.clear();
1073
855
  this._btcHighIndexConfirmedThisSession = false;
1074
- const resetPromise = this._runConnectorTeardown(async () => {
1075
- for (const sessionId of sessionIds) {
1076
- await this.connector.disconnect(sessionId).catch(() => void 0);
1077
- }
1078
- });
1079
- this._resetPromise = resetPromise;
1080
- return resetPromise.finally(() => {
1081
- if (this._resetPromise === resetPromise) {
1082
- this._resetPromise = null;
1083
- }
1084
- });
1085
856
  }
1086
857
  async dispose() {
1087
- await this._resetStateAndDisconnectSessions();
858
+ this._uiRegistry.reset();
859
+ this._jobQueue.clear();
1088
860
  this.unregisterEventListeners();
1089
861
  this.connector.reset();
862
+ this._discoveredDevices.clear();
863
+ this._sessions.clear();
864
+ this._btcHighIndexConfirmedThisSession = false;
1090
865
  this.emitter.removeAllListeners();
1091
866
  }
1092
867
  uiResponse(response) {
@@ -1096,32 +871,17 @@ var _LedgerAdapter = class _LedgerAdapter {
1096
871
  // Device management
1097
872
  // ---------------------------------------------------------------------------
1098
873
  async searchDevices(options) {
1099
- return this._searchDevices(options);
1100
- }
1101
- async _searchDevices(options, signal) {
1102
874
  debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
1103
875
  try {
1104
876
  if (options?.resetSession) {
1105
- await this._resetStateAndDisconnectSessions();
1106
- } else {
1107
- await this._connectorTeardownTail;
1108
- }
1109
- await this._ensureDevicePermission(void 0, void 0, signal);
1110
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1111
- const stateGeneration = this._stateGeneration;
1112
- const devices = await this.connector.searchDevices(
1113
- options?.transportType ? {
1114
- transportType: options.transportType,
1115
- waitForAll: options.waitForAllTransports
1116
- } : void 0
1117
- );
1118
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1119
- if (stateGeneration !== this._stateGeneration) {
1120
- throw createHwkError({
1121
- code: HardwareErrorCode3.UserAborted,
1122
- message: "Ledger discovery was reset"
1123
- });
877
+ this._doConnectAbortController?.abort();
878
+ this._sessions.clear();
879
+ this._connectingPromise = null;
880
+ this._doConnectAbortController = null;
881
+ this._btcHighIndexConfirmedThisSession = false;
1124
882
  }
883
+ await this._ensureDevicePermission();
884
+ const devices = await this.connector.searchDevices();
1125
885
  this._discoveredDevices.clear();
1126
886
  for (const d of devices) {
1127
887
  if (d.connectId) {
@@ -1129,7 +889,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1129
889
  }
1130
890
  }
1131
891
  if (this._discoveredDevices.size === 0) {
1132
- await this._ensureDevicePermission(void 0, void 0, signal);
892
+ await this._ensureDevicePermission();
1133
893
  }
1134
894
  const result = Array.from(this._discoveredDevices.values());
1135
895
  debugLog("[LedgerAdapter][RES]", {
@@ -1148,288 +908,41 @@ var _LedgerAdapter = class _LedgerAdapter {
1148
908
  throw err;
1149
909
  }
1150
910
  }
1151
- async searchDeviceTargets(options) {
1152
- const devices = await this.searchDevices(options);
1153
- return devices.map((device) => ({
1154
- searchTargetId: device.connectId,
1155
- searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
1156
- vendor: "ledger",
1157
- connectionType: device.connectionType,
1158
- kind: "physical",
1159
- label: device.label,
1160
- model: device.model,
1161
- modelName: device.modelName,
1162
- serialNumber: device.serialNumber
1163
- }));
1164
- }
1165
- async listConnectionTargets(options) {
1166
- const targets = await this.searchDeviceTargets(options);
1167
- return targets.map(({ searchTargetId, ...target }) => ({
1168
- ...target,
1169
- targetId: searchTargetId
1170
- }));
1171
- }
1172
911
  // USB single-session invariant: evict all sessions, best-effort (see connectDevice).
1173
- async _evictAllSessions(preserveOperationId) {
1174
- this._operations.endAll("explicit", preserveOperationId);
912
+ async _evictAllSessions() {
1175
913
  if (this._sessions.size === 0) return;
1176
914
  const stale = [...this._sessions.values()];
1177
915
  this._sessions.clear();
1178
- await this._runConnectorTeardown(async () => {
1179
- for (const sid of stale) {
1180
- try {
1181
- await this.connector.disconnect(sid);
1182
- } catch {
1183
- }
916
+ for (const sid of stale) {
917
+ try {
918
+ await this.connector.disconnect(sid);
919
+ } catch {
1184
920
  }
1185
- });
921
+ }
1186
922
  }
1187
923
  static _createDeviceBusyError(method) {
1188
924
  return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
1189
925
  code: HardwareErrorCode3.DeviceBusy
1190
926
  });
1191
927
  }
1192
- async connectDevice(searchTargetId) {
1193
- try {
1194
- return await this._jobQueue.enqueue(
1195
- searchTargetId || "__ledger_connect__",
1196
- async (signal) => {
1197
- const connected = await this._connectTarget(searchTargetId, void 0, signal);
1198
- if (!connected.success) return connected;
1199
- return this._createOperation(searchTargetId, connected.payload);
1200
- },
1201
- {
1202
- label: "connectDevice",
1203
- rejectIfBusy: true,
1204
- busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
1205
- }
1206
- );
1207
- } catch (error) {
1208
- return this.errorToFailure(error);
1209
- }
1210
- }
1211
- async bindBleDevice(params) {
1212
- if (params.identity.vendor !== "ledger" || !params.identity.value) {
1213
- return failure2(HardwareErrorCode3.InvalidParams, "Ledger wallet identity is required");
1214
- }
1215
- const { chain, value: expectedFingerprint } = params.identity;
1216
- try {
1217
- return await this._jobQueue.enqueue(
1218
- expectedFingerprint,
1219
- async (signal) => {
1220
- if (!this.getAvailableTransports().includes("ble")) {
1221
- throw createHwkError({
1222
- code: HardwareErrorCode3.TransportNotAvailable,
1223
- message: "Ledger Bluetooth transport is not available"
1224
- });
1225
- }
1226
- this._activeConnectionType = "ble";
1227
- await this._ensureDevicePermission(void 0, void 0, signal);
1228
- const attempt = {
1229
- extra: params.extra,
1230
- bindingReason: "manual-rebind"
1231
- };
1232
- try {
1233
- for (; ; ) {
1234
- const connectId = await this._connectFirstOrSelect(
1235
- [],
1236
- void 0,
1237
- true,
1238
- void 0,
1239
- attempt,
1240
- signal
1241
- );
1242
- const sessionId = this._sessions.get(connectId);
1243
- if (!sessionId) {
1244
- throw createHwkError({
1245
- code: HardwareErrorCode3.DeviceDisconnected,
1246
- message: "Selected Ledger connection ended"
1247
- });
1248
- }
1249
- let saved = false;
1250
- try {
1251
- const installContext = {
1252
- connection: { connectId, sessionId }
1253
- };
1254
- const fingerprint = await this._computeChainFingerprint(
1255
- chain,
1256
- (method, callParams) => this._runConnectorCall(
1257
- connectId,
1258
- method,
1259
- callParams,
1260
- signal,
1261
- void 0,
1262
- void 0,
1263
- { autoInstallApp: true },
1264
- installContext
1265
- )
1266
- );
1267
- if (fingerprint !== expectedFingerprint) {
1268
- attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
1269
- attempt.rejectedConnectIds.add(connectId);
1270
- attempt.rejectedConnectId = connectId;
1271
- } else {
1272
- const persisted = await this._publishVerifiedBleBinding(
1273
- connectId,
1274
- chain,
1275
- fingerprint,
1276
- attempt,
1277
- void 0,
1278
- signal
1279
- );
1280
- if (!persisted) {
1281
- throw createHwkError({
1282
- code: HardwareErrorCode3.UnknownError,
1283
- message: "Bluetooth binding could not be saved",
1284
- origin: "host"
1285
- });
1286
- }
1287
- saved = true;
1288
- return success(connectId);
1289
- }
1290
- } finally {
1291
- if (!saved && this._sessions.get(connectId) === sessionId) {
1292
- this._sessions.delete(connectId);
1293
- const teardown = this._runConnectorTeardown(
1294
- () => this.connector.disconnect(sessionId)
1295
- ).catch(() => void 0);
1296
- if (!signal.aborted) await teardown;
1297
- }
1298
- }
1299
- }
1300
- } catch (error) {
1301
- this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
1302
- throw error;
1303
- }
1304
- },
1305
- {
1306
- label: "bindBleDevice",
1307
- rejectIfBusy: true,
1308
- busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
1309
- }
1310
- );
1311
- } catch (error) {
1312
- return this.errorToFailure(error);
1313
- }
1314
- }
1315
- async acquireOperation(connectId, context) {
1316
- try {
1317
- return await this._jobQueue.enqueue(
1318
- connectId || "__ledger_acquire__",
1319
- async (signal) => {
1320
- const transport = this._isBleConnection() ? "ble" : "usb";
1321
- const hint = context.knownConnections?.find(
1322
- (connection) => connection.transport === transport
1323
- );
1324
- const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
1325
- await this._ensureDevicePermission(target, void 0, signal);
1326
- const attempt = {
1327
- ...context,
1328
- extra: context.extra ? { ...context.extra } : void 0
1329
- };
1330
- try {
1331
- const resolvedConnectId = await this.ensureConnected(
1332
- target,
1333
- signal,
1334
- true,
1335
- void 0,
1336
- attempt
1337
- );
1338
- _LedgerAdapter._throwIfAborted(signal);
1339
- const result = this._createOperation(connectId, resolvedConnectId);
1340
- if (result.success && attempt.selectedConnection) {
1341
- this._pendingOperationBindings.set(result.payload, attempt);
1342
- }
1343
- return result;
1344
- } catch (error) {
1345
- this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
1346
- throw error;
1347
- }
1348
- },
1349
- {
1350
- label: "acquireOperation",
1351
- rejectIfBusy: true,
1352
- busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
1353
- }
1354
- );
1355
- } catch (error) {
1356
- return this.errorToFailure(error);
1357
- }
1358
- }
1359
- _createOperation(searchTargetId, resolvedConnectId) {
1360
- this._operations.endByConnectionKey(resolvedConnectId, "explicit");
1361
- const sessionId = this._sessions.get(resolvedConnectId);
1362
- if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
1363
- const connectionType = this._isBleConnection() ? "ble" : "usb";
1364
- const device = this._discoveredDevices.get(resolvedConnectId) ?? {
1365
- vendor: "ledger",
1366
- model: "unknown",
1367
- firmwareVersion: "",
1368
- deviceId: "",
1369
- connectId: resolvedConnectId,
1370
- connectionType
1371
- };
1372
- const operation = this._operations.create({
1373
- searchTargetId,
1374
- connectId: resolvedConnectId,
1375
- device,
1376
- connectionType,
1377
- connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
1378
- });
1379
- return success(operation.operationId);
1380
- }
1381
- async _connectTarget(connectId, preserveOperationId, signal) {
928
+ async connectDevice(connectId) {
1382
929
  debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
1383
930
  try {
1384
- this._assertConnectorReady("connectDevice");
1385
- const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
1386
- if (discoveredType === "usb" || discoveredType === "ble") {
1387
- this._activeConnectionType = discoveredType;
1388
- }
1389
- if (this._isBleConnection() && !connectId) {
931
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
1390
932
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1391
933
  code: HardwareErrorCode3.DeviceNotFound
1392
934
  });
1393
935
  }
1394
- await this._ensureDevicePermission(connectId, void 0, signal);
1395
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1396
- const isMultiTransport = (this.connector.availableTransports?.length ?? 0) > 1;
1397
- if (!isMultiTransport && this._isBleConnection()) {
1398
- const previousSessionId = this._sessions.get(connectId);
1399
- this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
1400
- if (previousSessionId) {
1401
- this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
1402
- this._sessions.delete(connectId);
1403
- await this.connector.disconnect(previousSessionId).catch(() => void 0);
1404
- }
1405
- } else {
1406
- await this._evictAllSessions(preserveOperationId);
1407
- }
1408
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1409
- const stateGeneration = this._stateGeneration;
1410
- const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
1411
- let session;
1412
- this._connectingConnectId = connectId;
1413
- try {
1414
- session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
1415
- transportType: this._activeConnectionType ?? "usb"
1416
- }) : await this.connector.connect(connectId);
1417
- if (signal?.aborted || stateGeneration !== this._stateGeneration) {
1418
- await this.connector.disconnect(session.sessionId).catch(() => void 0);
1419
- throw Object.assign(new Error("Ledger connection aborted"), {
1420
- code: HardwareErrorCode3.UserAborted
1421
- });
1422
- }
1423
- } finally {
1424
- if (this._connectingConnectId === connectId) this._connectingConnectId = void 0;
1425
- releaseOperation();
936
+ if (!isLedgerBleConnectionType(this.connector.connectionType)) {
937
+ await this._evictAllSessions();
1426
938
  }
1427
- const resolvedConnectId = session.deviceInfo?.connectId || connectId;
1428
- this._sessions.set(resolvedConnectId, session.sessionId);
939
+ await this._ensureDevicePermission(connectId);
940
+ const session = await this.connector.connect(connectId);
941
+ this._sessions.set(connectId, session.sessionId);
1429
942
  if (session.deviceInfo) {
1430
- this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
943
+ this._discoveredDevices.set(connectId, session.deviceInfo);
1431
944
  }
1432
- const result = success(resolvedConnectId);
945
+ const result = success(connectId);
1433
946
  debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
1434
947
  return result;
1435
948
  } catch (err) {
@@ -1442,60 +955,30 @@ var _LedgerAdapter = class _LedgerAdapter {
1442
955
  return failureResult;
1443
956
  }
1444
957
  }
1445
- async releaseOperation(operationId) {
1446
- const operation = this._operations.find(operationId);
1447
- if (!operation) {
1448
- this._operations.resolve(operationId);
1449
- return;
1450
- }
1451
- const endedOperation = this._operations.end(operationId, "explicit");
1452
- if (!endedOperation) return;
1453
- const { connectId } = operation;
958
+ async disconnectDevice(connectId) {
1454
959
  debugLog("[LedgerAdapter][REQ]", {
1455
- method: "releaseOperation",
960
+ method: "disconnectDevice",
1456
961
  connectId,
1457
962
  params: { connectId }
1458
963
  });
1459
964
  try {
1460
- await this._releaseOperationConnection(endedOperation);
1461
- debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
965
+ const sessionId = this._sessions.get(connectId);
966
+ if (sessionId) {
967
+ await this.connector.disconnect(sessionId);
968
+ this._sessions.delete(connectId);
969
+ }
970
+ debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
1462
971
  } catch (err) {
1463
972
  const e = err;
1464
973
  debugLog("[LedgerAdapter][RES]", {
1465
- method: "releaseOperation",
974
+ method: "disconnectDevice",
1466
975
  success: false,
1467
976
  error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
1468
977
  });
1469
978
  throw err;
1470
979
  }
1471
980
  }
1472
- async _releaseOperationConnection(operation) {
1473
- const sessionIds = /* @__PURE__ */ new Set();
1474
- for (const [connectId, sessionId] of this._sessions) {
1475
- if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
1476
- this._sessions.delete(connectId);
1477
- sessionIds.add(sessionId);
1478
- }
1479
- }
1480
- for (const sessionId of sessionIds) {
1481
- await this._runConnectorTeardown(
1482
- () => this.connector.disconnect(sessionId).catch(() => void 0)
1483
- );
1484
- }
1485
- }
1486
- async _releaseLostOperationConnection(operationId) {
1487
- const endedOperation = this._operations.end(operationId, "disconnect");
1488
- if (!endedOperation) return;
1489
- this._discoveredDevices.delete(endedOperation.connectId);
1490
- await this._releaseOperationConnection(endedOperation);
1491
- }
1492
- async getDeviceInfo(connectIdOrOperationId, deviceId) {
1493
- let connectId;
1494
- try {
1495
- connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
1496
- } catch (error) {
1497
- return this.errorToFailure(error);
1498
- }
981
+ async getDeviceInfo(connectId, deviceId) {
1499
982
  debugLog("[LedgerAdapter][REQ]", {
1500
983
  method: "getDeviceInfo",
1501
984
  connectId,
@@ -1530,9 +1013,12 @@ var _LedgerAdapter = class _LedgerAdapter {
1530
1013
  success: false,
1531
1014
  error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
1532
1015
  });
1533
- return this.errorToFailure(err);
1016
+ throw err;
1534
1017
  }
1535
1018
  }
1019
+ getSupportedChains() {
1020
+ return ["evm", "btc", "sol", "tron"];
1021
+ }
1536
1022
  // ---------------------------------------------------------------------------
1537
1023
  // Chain call helper
1538
1024
  // ---------------------------------------------------------------------------
@@ -1567,23 +1053,13 @@ var _LedgerAdapter = class _LedgerAdapter {
1567
1053
  if (params && typeof params === "object") {
1568
1054
  const {
1569
1055
  autoInstallApp,
1570
- operationId,
1571
1056
  passphraseState: _passphraseState,
1572
1057
  useEmptyPassphrase: _useEmptyPassphrase,
1573
- knownConnections,
1574
- extra,
1575
- allowDeviceSelection,
1576
- supportedTransports,
1577
1058
  ...rest
1578
1059
  } = params;
1579
1060
  return {
1580
1061
  commonParams: {
1581
- autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
1582
- operationId: typeof operationId === "string" ? operationId : void 0,
1583
- knownConnections,
1584
- extra,
1585
- allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0,
1586
- supportedTransports
1062
+ autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
1587
1063
  },
1588
1064
  rest
1589
1065
  };
@@ -1759,28 +1235,9 @@ var _LedgerAdapter = class _LedgerAdapter {
1759
1235
  );
1760
1236
  }
1761
1237
  // ---------------------------------------------------------------------------
1762
- // Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
1763
- // ---------------------------------------------------------------------------
1764
- zcashGetFullViewingKey(connectId, deviceId, params) {
1765
- return this.callChainWithMergedParams(
1766
- connectId,
1767
- deviceId,
1768
- "zcash",
1769
- "zcashGetFullViewingKey",
1770
- params
1771
- );
1772
- }
1773
- zcashGetShieldedAddress(connectId, deviceId, params) {
1774
- return this.callChainWithMergedParams(
1775
- connectId,
1776
- deviceId,
1777
- "zcash",
1778
- "zcashGetShieldedAddress",
1779
- params
1780
- );
1781
- }
1782
- // ---------------------------------------------------------------------------
1783
- // App management: OS-level app install/list, no fingerprint or chain dispatch.
1238
+ // App management — OS-level Ledger app install / list. Bypasses fingerprint
1239
+ // and chain-handler dispatch; installApp progress is forwarded to the adapter
1240
+ // emitter via 'ui-event' AppInstallProgress events.
1784
1241
  // ---------------------------------------------------------------------------
1785
1242
  async installApp(connectId, appName) {
1786
1243
  try {
@@ -1866,7 +1323,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1866
1323
  );
1867
1324
  }
1868
1325
  await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
1869
- await this._resetStateAndDisconnectSessions();
1326
+ this.resetState();
1870
1327
  }
1871
1328
  const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
1872
1329
  if (!result.isGenuine) {
@@ -1892,10 +1349,10 @@ var _LedgerAdapter = class _LedgerAdapter {
1892
1349
  if (relayUrl) {
1893
1350
  try {
1894
1351
  await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
1895
- await this._resetStateAndDisconnectSessions();
1352
+ this.resetState();
1896
1353
  } catch {
1897
1354
  this.connector.reset();
1898
- await this._resetStateAndDisconnectSessions();
1355
+ this.resetState();
1899
1356
  }
1900
1357
  }
1901
1358
  }
@@ -1907,20 +1364,6 @@ var _LedgerAdapter = class _LedgerAdapter {
1907
1364
  this.emitter.off(event, listener);
1908
1365
  }
1909
1366
  cancel(connectId) {
1910
- const namedOperationIsLive = (id) => {
1911
- try {
1912
- this._operations.resolve(id);
1913
- return true;
1914
- } catch {
1915
- return false;
1916
- }
1917
- };
1918
- if (isHardwareOperationId(connectId) && !namedOperationIsLive(connectId)) {
1919
- debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
1920
- connectId
1921
- });
1922
- return;
1923
- }
1924
1367
  const userAbortReason = Object.assign(new Error("User aborted operation"), {
1925
1368
  code: HardwareErrorCode3.UserAborted,
1926
1369
  _tag: ERROR_TAG.UserAborted
@@ -1931,50 +1374,14 @@ var _LedgerAdapter = class _LedgerAdapter {
1931
1374
  this._lastCancelReason = void 0;
1932
1375
  }
1933
1376
  }, 2e3);
1934
- const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
1935
- let operationId;
1936
- if (isHardwareOperationId(connectId)) {
1937
- operationId = connectId;
1938
- } else if (!connectId && isHardwareOperationId(activeJobId)) {
1939
- operationId = activeJobId;
1940
- }
1941
- let resolvedConnectId = connectId;
1942
- if (operationId) {
1943
- try {
1944
- resolvedConnectId = this._operations.resolve(operationId).connectId;
1945
- } catch {
1946
- resolvedConnectId = void 0;
1947
- }
1948
- }
1949
- const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
1950
- const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
1951
- if (!connectId) {
1952
- this._uiRegistry.cancel();
1953
- } else if (pendingOperationId) {
1954
- this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
1955
- }
1956
- this._finishBleBinding("cancelled");
1957
- if (!connectId) this._pendingOperationBindings.clear();
1958
- else if (pendingOperationId) {
1959
- this._pendingOperationBindings.delete(pendingOperationId);
1960
- }
1961
- const queueKeys = /* @__PURE__ */ new Set();
1962
- if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
1963
- if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
1964
- if (queueKeys.size) {
1965
- let cancelledAnyJob = false;
1966
- for (const key of queueKeys) {
1967
- cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
1968
- }
1969
- debugLog("[LedgerAdapter] cancel routed to queue keys", {
1970
- queueKeys: [...queueKeys],
1971
- cancelledAnyJob
1972
- });
1377
+ this._uiRegistry.cancel();
1378
+ if (connectId) {
1379
+ this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
1973
1380
  } else {
1974
1381
  this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
1975
1382
  }
1976
- if (resolvedConnectId) {
1977
- const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
1383
+ if (connectId) {
1384
+ const sessionId = this._sessions.get(connectId) ?? connectId;
1978
1385
  void this.connector.cancel(sessionId);
1979
1386
  } else {
1980
1387
  for (const sid of this._sessions.values()) void this.connector.cancel(sid);
@@ -1982,105 +1389,45 @@ var _LedgerAdapter = class _LedgerAdapter {
1982
1389
  if (this._connectingPromise) {
1983
1390
  this._doConnectAbortController?.abort(userAbortReason);
1984
1391
  }
1985
- const connectingConnectId = this._connectingConnectId;
1986
- if (connectingConnectId) void this.connector.cancel(connectingConnectId);
1987
1392
  }
1988
1393
  // ---------------------------------------------------------------------------
1989
1394
  // Chain fingerprint
1990
1395
  // ---------------------------------------------------------------------------
1991
- /** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
1992
1396
  async getChainFingerprint(connectId, deviceId, chain) {
1993
1397
  try {
1994
1398
  const fingerprint = await this._computeChainFingerprint(
1995
1399
  chain,
1996
1400
  (method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
1997
1401
  );
1998
- if (deviceId) {
1999
- if (fingerprint !== deviceId) {
2000
- return failure2(
2001
- HardwareErrorCode3.DeviceMismatch,
2002
- formatDeviceMismatchError(deviceId, fingerprint)
2003
- );
2004
- }
2005
- if (isHardwareOperationId(connectId)) {
2006
- const operation = this._operations.resolve(connectId);
2007
- await this._publishVerifiedBleBinding(
2008
- operation.connectId,
2009
- chain,
2010
- fingerprint,
2011
- void 0,
2012
- connectId
2013
- );
2014
- }
2015
- }
2016
1402
  return success(fingerprint);
2017
1403
  } catch (err) {
2018
1404
  debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
2019
1405
  return this.errorToFailure(err);
2020
1406
  }
2021
1407
  }
2022
- /** Discovery may select a BLE target before a later call verifies its wallet. */
2023
- async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, operationId, signal) {
2024
- const binding = operationId ? this._pendingOperationBindings.get(operationId) : attempt;
2025
- if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) {
2026
- return false;
2027
- }
1408
+ /**
1409
+ * Verify fingerprint using an existing sessionId directly.
1410
+ * Safe to call inside connectorCall without causing queue deadlock.
1411
+ */
1412
+ async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
1413
+ if (!deviceId) return { success: true };
2028
1414
  try {
2029
- const outcome = await requestSaveDeviceBinding(
2030
- this.emitter,
2031
- this._uiRegistry,
2032
- {
2033
- selectionRequestId: binding.selectedConnection.requestId,
2034
- connection: { transport: "ble", connectId },
2035
- identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
2036
- extra: binding.extra,
2037
- operationId
2038
- },
2039
- signal
2040
- );
2041
- if (operationId) this._operations.resolve(operationId);
2042
- if (!outcome.saved) {
2043
- debugLog("[LedgerAdapter] BLE binding not persisted by host", {
2044
- connectId,
2045
- reason: outcome.reason
2046
- });
2047
- }
2048
- return outcome.saved;
2049
- } catch (error) {
2050
- if (operationId) {
2051
- this._pendingOperationBindings.delete(operationId);
2052
- this._operations.end(operationId, "explicit");
2053
- }
2054
- const sessionId = this._sessions.get(connectId);
2055
- this._sessions.delete(connectId);
2056
- if (sessionId) {
2057
- const teardown = this._runConnectorTeardown(
2058
- () => this.connector.disconnect(sessionId)
2059
- ).catch(() => void 0);
2060
- if (!signal?.aborted) await teardown;
2061
- }
2062
- throw error;
2063
- } finally {
2064
- if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
2065
- this._bindingSelectionRequestId = void 0;
1415
+ const fingerprint = await this._computeChainFingerprint(
1416
+ chain,
1417
+ (method, params) => this._callConnector(sessionId, method, params)
1418
+ );
1419
+ if (fingerprint === deviceId) {
1420
+ return { success: true };
2066
1421
  }
2067
- if (operationId) {
2068
- this._pendingOperationBindings.delete(operationId);
1422
+ return { success: false, expected: deviceId, actual: fingerprint };
1423
+ } catch (err) {
1424
+ const mapped = mapLedgerError(err);
1425
+ if (mapped.code === HardwareErrorCode3.WrongApp || mapped.code === HardwareErrorCode3.DeviceLocked) {
1426
+ return { success: true };
2069
1427
  }
1428
+ throw err;
2070
1429
  }
2071
1430
  }
2072
- /** Verify on the acquired session without re-entering the job queue. */
2073
- async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
2074
- if (!deviceId) return { success: true };
2075
- const fingerprint = await this._computeChainFingerprint(
2076
- chain,
2077
- (method, params) => this._callConnector(sessionId, method, params)
2078
- );
2079
- if (fingerprint === deviceId) {
2080
- return { success: true };
2081
- }
2082
- return { success: false, expected: deviceId, actual: fingerprint };
2083
- }
2084
1431
  /**
2085
1432
  * Compute the chain fingerprint via a caller-supplied call strategy.
2086
1433
  *
@@ -2108,23 +1455,11 @@ var _LedgerAdapter = class _LedgerAdapter {
2108
1455
  address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
2109
1456
  } else if (chain === "tron") {
2110
1457
  address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
2111
- } else if (chain === "zcash") {
2112
- address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
2113
1458
  } else {
2114
1459
  throw new Error(`Unsupported chain for fingerprint: ${chain}`);
2115
1460
  }
2116
1461
  return deriveDeviceFingerprint(address);
2117
1462
  }
2118
- /**
2119
- * Operation owning the running job, so a mid-call UI request can name it.
2120
- * Undefined at cold start when no operation owns the job.
2121
- */
2122
- _activeOperationId() {
2123
- const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
2124
- if (!activeJobId) return void 0;
2125
- if (isHardwareOperationId(activeJobId)) return activeJobId;
2126
- return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
2127
- }
2128
1463
  // Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
2129
1464
  // The user must press Confirm after unlocking, which triggers a search retry.
2130
1465
  // If `signal` is provided, an abort cancels the pending UI request so the
@@ -2134,18 +1469,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2134
1469
  if (signal?.aborted) {
2135
1470
  _LedgerAdapter._throwIfAborted(signal);
2136
1471
  }
2137
- const operationId = this._activeOperationId();
2138
1472
  const waitPromise = this._uiRegistry.wait(
2139
- UI_REQUEST.REQUEST_DEVICE_CONNECT,
2140
- { operationId }
1473
+ UI_REQUEST.REQUEST_DEVICE_CONNECT
2141
1474
  );
2142
1475
  this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_CONNECT, {
2143
1476
  type: UI_REQUEST.REQUEST_DEVICE_CONNECT,
2144
1477
  payload: {
2145
1478
  vendor: "ledger",
2146
1479
  reason: "device-not-found",
2147
- message: "Please connect and unlock your Ledger device",
2148
- operationId
1480
+ message: "Please connect and unlock your Ledger device"
2149
1481
  }
2150
1482
  });
2151
1483
  let payload;
@@ -2206,18 +1538,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2206
1538
  return { ...params, showOnDevice: true };
2207
1539
  }
2208
1540
  async _waitForBtcHighIndexConfirm(path, accountIndex) {
2209
- const operationId = this._activeOperationId();
2210
1541
  const waitPromise = this._uiRegistry.wait(
2211
- UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
2212
- { operationId }
1542
+ UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
2213
1543
  );
2214
1544
  this.emitter.emit(UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
2215
1545
  type: UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
2216
1546
  payload: {
2217
1547
  vendor: "ledger",
2218
1548
  path,
2219
- accountIndex,
2220
- operationId
1549
+ accountIndex
2221
1550
  }
2222
1551
  });
2223
1552
  try {
@@ -2234,14 +1563,12 @@ var _LedgerAdapter = class _LedgerAdapter {
2234
1563
  // Ask the user whether to install a missing app (autoInstallApp flow).
2235
1564
  // Same register-then-emit ordering as the BTC high-index gate.
2236
1565
  async _waitForInstallAppConfirm(appName) {
2237
- const operationId = this._activeOperationId();
2238
1566
  const waitPromise = this._uiRegistry.wait(
2239
- UI_REQUEST.REQUEST_INSTALL_APP,
2240
- { operationId }
1567
+ UI_REQUEST.REQUEST_INSTALL_APP
2241
1568
  );
2242
1569
  this.emitter.emit(UI_REQUEST.REQUEST_INSTALL_APP, {
2243
1570
  type: UI_REQUEST.REQUEST_INSTALL_APP,
2244
- payload: { vendor: "ledger", appName, operationId }
1571
+ payload: { vendor: "ledger", appName }
2245
1572
  });
2246
1573
  try {
2247
1574
  const payload = await waitPromise;
@@ -2257,17 +1584,16 @@ var _LedgerAdapter = class _LedgerAdapter {
2257
1584
  // Layer 1 entry. Caller signal only races the outer awaiter; the shared
2258
1585
  // `_doConnect` runs under its own internal controller so caller A's cancel
2259
1586
  // doesn't kill caller B's await.
2260
- async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
1587
+ async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
2261
1588
  if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
2262
- this._assertConnectorReady("connectDevice");
2263
- if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
1589
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
2264
1590
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
2265
1591
  code: HardwareErrorCode3.DeviceNotFound
2266
1592
  });
2267
1593
  }
2268
1594
  if (connectId && this._sessions.has(connectId)) return connectId;
2269
1595
  if (!connectId && this._sessions.size > 0) {
2270
- if (!this._isBleConnection() && this._sessions.size > 1) {
1596
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
2271
1597
  throw Object.assign(
2272
1598
  new Error(
2273
1599
  "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
@@ -2277,24 +1603,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2277
1603
  }
2278
1604
  return this._sessions.keys().next().value;
2279
1605
  }
2280
- if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
2281
- const controller = new AbortController();
2282
- this._doConnectAbortController = controller;
2283
- const innerSignal = controller.signal;
1606
+ if (!this._connectingPromise) {
1607
+ this._doConnectAbortController = new AbortController();
1608
+ const innerSignal = this._doConnectAbortController.signal;
2284
1609
  this._connectingPromise = (async () => {
2285
1610
  try {
2286
- return await this._doConnect(
2287
- innerSignal,
2288
- connectId,
2289
- allowUsbEphemeralFallback,
2290
- preserveOperationId,
2291
- context
2292
- );
1611
+ return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback);
2293
1612
  } finally {
2294
- if (this._doConnectAbortController === controller) {
2295
- this._connectingPromise = null;
2296
- this._doConnectAbortController = null;
2297
- }
1613
+ this._connectingPromise = null;
1614
+ this._doConnectAbortController = null;
2298
1615
  }
2299
1616
  })();
2300
1617
  }
@@ -2303,57 +1620,22 @@ var _LedgerAdapter = class _LedgerAdapter {
2303
1620
  // Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
2304
1621
  // Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
2305
1622
  // throw DeviceNotFound so the user is kicked out of the loop.
2306
- async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
2307
- _LedgerAdapter._throwIfAborted(internalSignal);
2308
- if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
2309
- this._activeConnectionType = "usb";
2310
- const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
2311
- _LedgerAdapter._throwIfAborted(internalSignal);
2312
- if (usbDevices.length > 0) {
2313
- const knownUsb = context?.knownConnections?.find(
2314
- (connection) => connection.transport === "usb"
2315
- );
2316
- const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
2317
- return this._connectFirstOrSelect(
2318
- usbDevices,
2319
- usbTarget,
2320
- allowUsbEphemeralFallback,
2321
- preserveOperationId,
2322
- context,
2323
- internalSignal
2324
- );
2325
- }
2326
- if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
2327
- throw createHwkError({
2328
- code: HardwareErrorCode3.DeviceNotFound,
2329
- message: "This Ledger model has no Bluetooth transport; connect it over USB"
2330
- });
2331
- }
2332
- this._activeConnectionType = "ble";
2333
- const knownBle = context?.knownConnections?.find(
2334
- (connection) => connection.transport === "ble"
2335
- );
2336
- if (knownBle?.transport === "ble") {
2337
- return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
2338
- }
2339
- if (targetConnectId && context?.knownConnections === void 0) {
2340
- throw createHwkError({
2341
- code: HardwareErrorCode3.DeviceNotFound,
2342
- message: "Ledger connection metadata is required before starting Bluetooth binding"
2343
- });
1623
+ async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
1624
+ if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
1625
+ try {
1626
+ return await this._connectDeviceOrThrow(targetConnectId);
1627
+ } catch (err) {
1628
+ if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
1629
+ throw err;
1630
+ }
1631
+ this._discoveredDevices.delete(targetConnectId);
1632
+ if (isDeviceDisconnectedError(err)) {
1633
+ try {
1634
+ this.connector.reset?.();
1635
+ } catch {
1636
+ }
1637
+ }
2344
1638
  }
2345
- const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
2346
- return this._connectFirstOrSelect(
2347
- bleDevices,
2348
- void 0,
2349
- allowUsbEphemeralFallback,
2350
- preserveOperationId,
2351
- context,
2352
- internalSignal
2353
- );
2354
- }
2355
- if (this._isBleConnection() && targetConnectId) {
2356
- return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
2357
1639
  }
2358
1640
  let confirms = 0;
2359
1641
  while (!internalSignal.aborted) {
@@ -2361,28 +1643,22 @@ var _LedgerAdapter = class _LedgerAdapter {
2361
1643
  type: EConnectorInteraction.Searching,
2362
1644
  payload: { sessionId: "" }
2363
1645
  });
2364
- let devices = await this._searchDevices(void 0, internalSignal);
2365
- _LedgerAdapter._throwIfAborted(internalSignal);
1646
+ let devices = await this.searchDevices();
2366
1647
  if (devices.length === 0) {
2367
1648
  for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
2368
1649
  await new Promise((resolve) => {
2369
1650
  setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
2370
1651
  });
2371
- _LedgerAdapter._throwIfAborted(internalSignal);
2372
- devices = await this._searchDevices(void 0, internalSignal);
2373
- _LedgerAdapter._throwIfAborted(internalSignal);
1652
+ devices = await this.searchDevices();
2374
1653
  if (devices.length > 0) break;
2375
1654
  }
2376
1655
  }
2377
- if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
1656
+ if (devices.length > 0) {
2378
1657
  try {
2379
1658
  return await this._connectFirstOrSelect(
2380
1659
  devices,
2381
1660
  targetConnectId,
2382
- allowUsbEphemeralFallback,
2383
- preserveOperationId,
2384
- context,
2385
- internalSignal
1661
+ allowUsbEphemeralFallback
2386
1662
  );
2387
1663
  } catch (err) {
2388
1664
  if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
@@ -2411,133 +1687,45 @@ var _LedgerAdapter = class _LedgerAdapter {
2411
1687
  _LedgerAdapter._throwIfAborted(internalSignal);
2412
1688
  throw new Error("_doConnect aborted");
2413
1689
  }
2414
- async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
2415
- _LedgerAdapter._throwIfAborted(signal);
1690
+ async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
2416
1691
  if (targetConnectId) {
2417
1692
  const target = devices.find(
2418
1693
  (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
2419
1694
  );
2420
1695
  if (target) {
2421
- return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
1696
+ return this._connectDeviceOrThrow(target.connectId);
2422
1697
  }
2423
- if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
1698
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1 && allowUsbEphemeralFallback) {
2424
1699
  debugLog(
2425
1700
  `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
2426
1701
  );
2427
- return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
1702
+ return this._connectDeviceOrThrow(devices[0].connectId);
2428
1703
  }
2429
- if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
2430
- const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
2431
- code: HardwareErrorCode3.DeviceNotFound
2432
- });
2433
- if (this._isBleConnection()) {
2434
- err._tag = ERROR_TAG.DeviceNotAdvertising;
2435
- }
2436
- throw err;
1704
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1705
+ code: HardwareErrorCode3.DeviceNotFound
1706
+ });
1707
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1708
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
2437
1709
  }
1710
+ throw err;
2438
1711
  }
2439
- const requiresBleSelection = this._isBleConnection();
2440
- if (requiresBleSelection && !allowUsbEphemeralFallback) {
1712
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
2441
1713
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
2442
1714
  code: HardwareErrorCode3.DeviceNotFound
2443
1715
  });
2444
1716
  }
2445
- if (requiresBleSelection && context?.allowDeviceSelection !== false) {
2446
- const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
2447
- if (context) context.bindingSessionId = bindingSessionId;
2448
- const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
2449
- const knownUsb = context?.knownConnections?.find(
2450
- (connection) => connection.transport === "usb"
2451
- );
2452
- const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
2453
- const { device, requestId } = await requestBleDeviceSelection({
2454
- emitter: this.emitter,
2455
- registry: this._uiRegistry,
2456
- signal,
2457
- allowUsbFallback,
2458
- scan: async () => {
2459
- if (allowUsbFallback) {
2460
- const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
2461
- const candidate = usbDevices.find(
2462
- (device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
2463
- ) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
2464
- if (candidate) return [candidate];
2465
- }
2466
- return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
2467
- },
2468
- request: {
2469
- devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
2470
- bindingSessionId,
2471
- rejectedConnectId: context?.rejectedConnectId,
2472
- context: {
2473
- kind: "bind-connection",
2474
- transport: "ble",
2475
- reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
2476
- },
2477
- extra: context?.extra,
2478
- operationId: preserveOperationId
2479
- }
2480
- });
2481
- if (device.connectionType === "usb") {
2482
- this._activeConnectionType = "usb";
2483
- if (context) context.selectedConnection = void 0;
2484
- if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
2485
- this._bindingSelectionRequestId = requestId;
2486
- this._finishBleBinding("cancelled");
2487
- return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
2488
- }
2489
- if (context) context.selectedConnection = { connectId: device.connectId, requestId };
2490
- this._bindingSelectionRequestId = requestId;
2491
- return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
2492
- }
2493
- if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
2494
- if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
2495
- throw createHwkError({
2496
- code: HardwareErrorCode3.DeviceNotFound,
2497
- message: "Select a Ledger device before continuing"
2498
- });
2499
- }
2500
- const requestId = this._uiRegistry.createRequestId();
2501
- const operationId = this._activeOperationId();
2502
- const waitPromise = this._uiRegistry.wait(
2503
- UI_REQUEST.REQUEST_SELECT_DEVICE,
2504
- { requestId, operationId }
2505
- );
2506
- this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
2507
- type: UI_REQUEST.REQUEST_SELECT_DEVICE,
2508
- payload: {
2509
- devices,
2510
- requestId,
2511
- operationId,
2512
- context: requiresBleSelection ? {
2513
- kind: "bind-connection",
2514
- transport: "ble",
2515
- reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
2516
- } : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
2517
- extra: context?.extra
2518
- }
2519
- });
2520
- const { sdkConnectId } = await this._abortable(signal, waitPromise);
2521
- _LedgerAdapter._throwIfAborted(signal);
2522
- const selected = devices.find((device) => device.connectId === sdkConnectId);
2523
- if (!selected) {
2524
- throw Object.assign(new Error("Selected Ledger is no longer available"), {
2525
- code: HardwareErrorCode3.DeviceNotFound
2526
- });
2527
- }
2528
- if (context && requiresBleSelection)
2529
- context.selectedConnection = { connectId: selected.connectId, requestId };
2530
- return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
1717
+ if (devices.length > 1) {
1718
+ throw createMultipleUsbLedgerDevicesError();
2531
1719
  }
2532
1720
  if (devices.length !== 1) {
2533
1721
  throw Object.assign(new Error("Ledger device not found."), {
2534
1722
  code: HardwareErrorCode3.DeviceNotFound
2535
1723
  });
2536
1724
  }
2537
- return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
1725
+ return this._connectDeviceOrThrow(devices[0].connectId);
2538
1726
  }
2539
- async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
2540
- const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
1727
+ async _connectDeviceOrThrow(chosenConnectId) {
1728
+ const result = await this.connectDevice(chosenConnectId);
2541
1729
  if (!result.success) {
2542
1730
  const payload = result.payload;
2543
1731
  const rethrow = Object.assign(new Error(payload.error), {
@@ -2548,7 +1736,7 @@ var _LedgerAdapter = class _LedgerAdapter {
2548
1736
  }
2549
1737
  throw rethrow;
2550
1738
  }
2551
- return result.payload;
1739
+ return chosenConnectId;
2552
1740
  }
2553
1741
  /**
2554
1742
  * Call the connector with automatic session resolution and disconnect retry.
@@ -2578,133 +1766,33 @@ var _LedgerAdapter = class _LedgerAdapter {
2578
1766
  * lives in one place.
2579
1767
  */
2580
1768
  async _callConnector(sessionId, method, params, signal) {
2581
- this._assertConnectorReady(method);
2582
- if (signal?.aborted) throw this._abortReason(signal);
2583
- const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
2584
- let promise;
2585
- try {
2586
- promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
2587
- } catch (error) {
2588
- releaseOperation();
2589
- throw error;
2590
- }
1769
+ const promise = this.connector.call(sessionId, method, params);
2591
1770
  const result = signal ? await this._abortable(signal, promise) : await promise;
2592
1771
  return this._unwrapConnectorResult(result);
2593
1772
  }
2594
- _assertConnectorReady(method) {
2595
- if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
2596
- throw _LedgerAdapter._createDeviceBusyError(method);
2597
- }
2598
- }
2599
- _runConnectorTeardown(task) {
2600
- const previous = this._connectorTeardownTail;
2601
- let releaseTail = () => void 0;
2602
- this._connectorTeardownTail = new Promise((resolve) => {
2603
- releaseTail = resolve;
2604
- });
2605
- this._pendingConnectorTeardowns += 1;
2606
- return (async () => {
2607
- try {
2608
- await previous;
2609
- await this._waitForConnectorOperationsToDrain();
2610
- await task();
2611
- } finally {
2612
- this._pendingConnectorTeardowns -= 1;
2613
- releaseTail();
2614
- }
2615
- })();
2616
- }
2617
- _waitForConnectorOperationsToDrain() {
2618
- if (this._unsettledConnectorOperations.size === 0) {
2619
- return Promise.resolve();
2620
- }
2621
- return new Promise((resolve) => {
2622
- this._connectorIdleWaiters.add(resolve);
2623
- });
2624
- }
2625
- _retainConnectorOperation(key) {
2626
- this._unsettledConnectorOperations.set(
2627
- key,
2628
- (this._unsettledConnectorOperations.get(key) ?? 0) + 1
2629
- );
2630
- let released = false;
2631
- return () => {
2632
- if (released) return;
2633
- released = true;
2634
- const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
2635
- if (remaining > 0) {
2636
- this._unsettledConnectorOperations.set(key, remaining);
2637
- } else {
2638
- this._unsettledConnectorOperations.delete(key);
2639
- }
2640
- if (this._unsettledConnectorOperations.size === 0) {
2641
- for (const resolve of this._connectorIdleWaiters) resolve();
2642
- this._connectorIdleWaiters.clear();
2643
- }
2644
- };
2645
- }
2646
1773
  async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
2647
- const positionalOperationId = isHardwareOperationId(connectId) ? connectId : void 0;
2648
- if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
2649
- throw createHwkError({
2650
- code: HardwareErrorCode3.InvalidParams,
2651
- message: "Conflicting Ledger operation ids",
2652
- params: {
2653
- positionalOperationId,
2654
- commonOperationId: commonParams.operationId
2655
- }
2656
- });
2657
- }
2658
- const operationId = commonParams?.operationId ?? positionalOperationId;
2659
- const operation = operationId ? this._operations.resolve(operationId) : void 0;
2660
- const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
2661
- const effectiveConnectId = operation?.connectId ?? connectId;
2662
- debugLog("[LedgerAdapter][REQ]", {
2663
- method,
2664
- connectId: effectiveConnectId || "(empty)",
2665
- params
2666
- });
2667
- const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
1774
+ debugLog("[LedgerAdapter][REQ]", { method, connectId: connectId || "(empty)", params });
1775
+ const queueKey = connectId || "__ledger_default__";
2668
1776
  try {
2669
1777
  const result = await this._jobQueue.enqueue(
2670
1778
  queueKey,
2671
- async (signal) => {
2672
- if (operationId) this._activeOperationJobs.add(operationId);
2673
- try {
2674
- return await this._runConnectorCall(
2675
- effectiveConnectId,
2676
- method,
2677
- params,
2678
- signal,
2679
- fingerprint,
2680
- permissionDeviceId,
2681
- commonParams,
2682
- installContext ?? {},
2683
- operationId
2684
- );
2685
- } catch (error) {
2686
- this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
2687
- throw error;
2688
- } finally {
2689
- if (operationId) {
2690
- this._activeOperationJobs.delete(operationId);
2691
- if (this._pendingOperationDisconnects.delete(operationId)) {
2692
- await this._releaseLostOperationConnection(operationId);
2693
- }
2694
- }
2695
- }
2696
- },
1779
+ async (signal) => this._runConnectorCall(
1780
+ connectId,
1781
+ method,
1782
+ params,
1783
+ signal,
1784
+ fingerprint,
1785
+ permissionDeviceId,
1786
+ commonParams,
1787
+ installContext
1788
+ ),
2697
1789
  {
2698
1790
  label: method,
2699
1791
  rejectIfBusy: true,
2700
1792
  busyError: _LedgerAdapter._createDeviceBusyError(method)
2701
1793
  }
2702
1794
  );
2703
- debugLog("[LedgerAdapter][RES]", {
2704
- method,
2705
- success: true,
2706
- payload: redactResultForLog(result)
2707
- });
1795
+ debugLog("[LedgerAdapter][RES]", { method, success: true, payload: result });
2708
1796
  return result;
2709
1797
  } catch (err) {
2710
1798
  const e = err;
@@ -2718,22 +1806,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2718
1806
  }
2719
1807
  });
2720
1808
  throw err;
2721
- } finally {
2722
- releaseOperationRetention?.();
2723
1809
  }
2724
1810
  }
2725
- /** Hermes/RN polyfills don't always populate signal.reason; fall back. */
2726
- _abortReason(signal) {
2727
- return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
2728
- }
2729
1811
  /**
2730
1812
  * Race a promise against an abort signal. On abort, rejects with
2731
1813
  * signal.reason → instance _lastCancelReason → generic Error('Aborted').
2732
1814
  */
2733
1815
  _abortable(signal, promise) {
2734
- const getAbortReason = () => this._abortReason(signal);
1816
+ const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
2735
1817
  if (signal.aborted) {
2736
- void promise.catch(() => void 0);
2737
1818
  return Promise.reject(getAbortReason());
2738
1819
  }
2739
1820
  return new Promise((resolve, reject) => {
@@ -2760,15 +1841,13 @@ var _LedgerAdapter = class _LedgerAdapter {
2760
1841
  }
2761
1842
  }
2762
1843
  /** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
2763
- async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
1844
+ async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
2764
1845
  _LedgerAdapter._throwIfAborted(signal);
2765
- if (!operationId) {
2766
- await this._ensureDevicePermission(
2767
- connectId,
2768
- permissionDeviceId ?? fingerprint?.deviceId,
2769
- signal
2770
- );
2771
- }
1846
+ await this._ensureDevicePermission(
1847
+ connectId,
1848
+ permissionDeviceId ?? fingerprint?.deviceId,
1849
+ signal
1850
+ );
2772
1851
  _LedgerAdapter._throwIfAborted(signal);
2773
1852
  let effectiveParams = params;
2774
1853
  if (method === "btcGetPublicKey") {
@@ -2782,135 +1861,36 @@ var _LedgerAdapter = class _LedgerAdapter {
2782
1861
  effectiveParams = gatedParams;
2783
1862
  }
2784
1863
  const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
2785
- let businessCallStarted = false;
2786
- const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
2787
- const knownTransport = this._isBleConnection() ? "ble" : "usb";
2788
- const hintedConnectId = commonParams?.knownConnections?.find(
2789
- (connection) => connection.transport === knownTransport
2790
- );
2791
- const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
2792
- const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
2793
- const connectionAttempt = { ...commonParams };
2794
- const bundleConnection = installContext?.connection;
2795
- if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
2796
- throw createHwkError({
2797
- code: HardwareErrorCode3.DeviceDisconnected,
2798
- message: "Ledger all-network connection ended"
2799
- });
2800
- }
2801
- let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
2802
- preferredConnectId,
1864
+ const resolvedConnectId = await this.ensureConnected(
1865
+ connectId,
2803
1866
  signal,
2804
- allowUsbEphemeralFallback,
2805
- void 0,
2806
- connectionAttempt
1867
+ allowUsbEphemeralFallback
2807
1868
  );
2808
- let sessionId = this._sessions.get(resolvedConnectId);
2809
- if (sessionId && installContext && !installContext.connection) {
2810
- installContext.connection = { connectId: resolvedConnectId, sessionId };
2811
- }
1869
+ const sessionId = this._sessions.get(resolvedConnectId);
2812
1870
  if (!sessionId) {
2813
- if (operationId) {
2814
- this._operations.end(operationId, "disconnect");
2815
- throw createHwkError({
2816
- code: HardwareErrorCode3.OperationEnded,
2817
- message: "Ledger operation connection is no longer active",
2818
- params: { operationId, reason: "disconnect" }
2819
- });
2820
- }
2821
1871
  throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
2822
1872
  _tag: ERROR_TAG.DeviceSessionNotFound
2823
1873
  });
2824
1874
  }
2825
1875
  try {
2826
1876
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2827
- for (; ; ) {
2828
- const fp = await this._abortable(
2829
- signal,
2830
- this._verifyDeviceFingerprintWithSession(
2831
- sessionId,
2832
- fingerprint.deviceId,
2833
- fingerprint.chain
2834
- )
2835
- );
2836
- if (fp.success) break;
2837
- const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
2838
- if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
2839
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2840
- code: HardwareErrorCode3.DeviceMismatch
2841
- });
2842
- }
2843
- binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
2844
- binding.rejectedConnectIds.add(resolvedConnectId);
2845
- binding.rejectedConnectId = resolvedConnectId;
2846
- this._sessions.delete(resolvedConnectId);
2847
- await this.connector.disconnect(sessionId);
2848
- if (operationId) this._pendingOperationDisconnects.delete(operationId);
2849
- _LedgerAdapter._throwIfAborted(signal);
2850
- resolvedConnectId = await this._connectFirstOrSelect(
2851
- [],
2852
- void 0,
2853
- true,
2854
- operationId,
2855
- binding,
2856
- signal
2857
- );
2858
- const selectedSession = this._sessions.get(resolvedConnectId);
2859
- const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
2860
- if (!selectedSession || !selectedDevice)
2861
- throw createHwkError({
2862
- code: HardwareErrorCode3.DeviceDisconnected,
2863
- message: "Selected Ledger connection ended"
2864
- });
2865
- sessionId = selectedSession;
2866
- if (operationId)
2867
- this._operations.rebind(operationId, {
2868
- connectId: resolvedConnectId,
2869
- device: selectedDevice,
2870
- // Same source as `_createOperation`: the selected transport, not
2871
- // the device snapshot a session connect overwrote.
2872
- connectionType: this._isBleConnection() ? "ble" : "usb",
2873
- connectionKeys: [sessionId]
2874
- });
2875
- if (installContext)
2876
- installContext.connection = { connectId: resolvedConnectId, sessionId };
2877
- }
2878
- await this._publishVerifiedBleBinding(
2879
- resolvedConnectId,
2880
- fingerprint.chain,
2881
- fingerprint.deviceId,
2882
- connectionAttempt,
2883
- operationId,
2884
- signal
1877
+ const fp = await this._abortable(
1878
+ signal,
1879
+ this._verifyDeviceFingerprintWithSession(
1880
+ sessionId,
1881
+ fingerprint.deviceId,
1882
+ fingerprint.chain
1883
+ )
2885
1884
  );
2886
- if (!operationId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
2887
- this._verifiedBleReconnectTargets.set(connectId, {
2888
- connectId: resolvedConnectId,
2889
- chain: fingerprint.chain,
2890
- fingerprint: fingerprint.deviceId
1885
+ if (!fp.success) {
1886
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1887
+ code: HardwareErrorCode3.DeviceMismatch
2891
1888
  });
2892
1889
  }
2893
1890
  }
2894
- businessCallStarted = true;
2895
1891
  return await this._callConnector(sessionId, method, effectiveParams, signal);
2896
1892
  } catch (err) {
2897
1893
  if (signal.aborted) throw err;
2898
- if (isLostConnectionError(err)) {
2899
- this._discoveredDevices.delete(resolvedConnectId);
2900
- if (operationId) await this._releaseLostOperationConnection(operationId);
2901
- else {
2902
- this._sessions.delete(resolvedConnectId);
2903
- await this.connector.disconnect(sessionId).catch(() => void 0);
2904
- }
2905
- const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
2906
- const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
2907
- throw createHwkError({
2908
- code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(err).code,
2909
- message: "Ledger operation connection was lost; start a new operation",
2910
- params: ambiguous ? operationMayHaveCompletedParams(method, { operationId }) : interactionParams,
2911
- recovery: ambiguous ? { scope: "unknown" } : void 0
2912
- });
2913
- }
2914
1894
  const errObj = err;
2915
1895
  debugLog("[LedgerAdapter] connectorCall error:", method, {
2916
1896
  message: errObj?.message,
@@ -2922,60 +1902,6 @@ var _LedgerAdapter = class _LedgerAdapter {
2922
1902
  isNotAdvertising: isDeviceNotAdvertisingError(err),
2923
1903
  isStuckApp: isStuckAppStateError(err)
2924
1904
  });
2925
- const assertSessionCurrent = (message) => {
2926
- if (this._sessions.get(resolvedConnectId) === sessionId) return;
2927
- throw createHwkError({
2928
- code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
2929
- message
2930
- });
2931
- };
2932
- if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
2933
- await this._waitForDeviceConnect(signal);
2934
- assertSessionCurrent("Ledger connection ended while waiting for unlock");
2935
- return this._runConnectorCall(
2936
- resolvedConnectId,
2937
- method,
2938
- effectiveParams,
2939
- signal,
2940
- fingerprint,
2941
- permissionDeviceId,
2942
- commonParams,
2943
- installContext,
2944
- operationId,
2945
- lockedRetryBudget - 1
2946
- );
2947
- }
2948
- if (businessCallStarted && isStuckAppStateError(err)) {
2949
- await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2950
- assertSessionCurrent("Ledger connection ended during the app transition");
2951
- try {
2952
- return await this._callConnector(sessionId, method, effectiveParams, signal);
2953
- } catch (retryErr) {
2954
- if (isStuckAppStateError(retryErr)) throw err;
2955
- if (isLostConnectionError(retryErr)) {
2956
- if (operationId) await this._releaseLostOperationConnection(operationId);
2957
- else {
2958
- this._sessions.delete(resolvedConnectId);
2959
- this._discoveredDevices.delete(resolvedConnectId);
2960
- await this.connector.disconnect(sessionId).catch(() => void 0);
2961
- }
2962
- const ambiguous = !canReplayHardwareMethodAfterTransportFailure(method);
2963
- throw createHwkError({
2964
- code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(retryErr).code,
2965
- message: `Ledger ${method} may have completed before the connection was lost`,
2966
- params: ambiguous ? operationMayHaveCompletedParams(method, { operationId, reason: "disconnect" }) : { operationId, reason: "disconnect" },
2967
- recovery: ambiguous ? { scope: "unknown" } : void 0
2968
- });
2969
- }
2970
- throw retryErr;
2971
- }
2972
- }
2973
- if (!operationId && errObj?.code === HardwareErrorCode3.DeviceMismatch) {
2974
- this._sessions.delete(resolvedConnectId);
2975
- this._discoveredDevices.delete(resolvedConnectId);
2976
- await this.connector.disconnect(sessionId).catch(() => void 0);
2977
- throw err;
2978
- }
2979
1905
  const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
2980
1906
  const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
2981
1907
  if (autoInstallApp && isAppMissing) {
@@ -3031,14 +1957,157 @@ var _LedgerAdapter = class _LedgerAdapter {
3031
1957
  fingerprint,
3032
1958
  permissionDeviceId,
3033
1959
  commonParams,
3034
- installContext,
3035
- operationId
1960
+ installContext
1961
+ );
1962
+ }
1963
+ }
1964
+ if (isStuckAppStateError(err)) {
1965
+ try {
1966
+ this._sessions.delete(resolvedConnectId);
1967
+ this._discoveredDevices.delete(resolvedConnectId);
1968
+ this.connector.reset?.();
1969
+ } catch {
1970
+ }
1971
+ debugLog(
1972
+ "[LedgerAdapter] stuck-app retry: method=",
1973
+ method,
1974
+ "delayMs=",
1975
+ _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
1976
+ "_tag=",
1977
+ err?._tag
1978
+ );
1979
+ try {
1980
+ const retryResult = await this._retryAfterStuckApp(
1981
+ resolvedConnectId,
1982
+ method,
1983
+ effectiveParams,
1984
+ signal,
1985
+ err,
1986
+ fingerprint
3036
1987
  );
1988
+ debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
1989
+ return retryResult;
1990
+ } catch (retryErr) {
1991
+ if (signal.aborted) throw retryErr;
1992
+ if (isStuckAppStateError(retryErr)) {
1993
+ debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
1994
+ throw err;
1995
+ }
1996
+ debugLog(
1997
+ "[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
1998
+ method,
1999
+ "retryErrTag=",
2000
+ retryErr?._tag
2001
+ );
2002
+ throw retryErr;
2003
+ }
2004
+ }
2005
+ if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
2006
+ let lastErr = err;
2007
+ for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
2008
+ if (signal.aborted) throw lastErr;
2009
+ try {
2010
+ this._sessions.delete(resolvedConnectId);
2011
+ this._discoveredDevices.delete(resolvedConnectId);
2012
+ if (isDeviceDisconnectedError(lastErr)) {
2013
+ try {
2014
+ this.connector.reset?.();
2015
+ } catch {
2016
+ }
2017
+ }
2018
+ const reConnectId = await this.ensureConnected(
2019
+ resolvedConnectId,
2020
+ signal,
2021
+ allowUsbEphemeralFallback
2022
+ );
2023
+ const reSessionId = this._sessions.get(reConnectId);
2024
+ if (!reSessionId) throw lastErr;
2025
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2026
+ const fp = await this._abortable(
2027
+ signal,
2028
+ this._verifyDeviceFingerprintWithSession(
2029
+ reSessionId,
2030
+ fingerprint.deviceId,
2031
+ fingerprint.chain
2032
+ )
2033
+ );
2034
+ if (!fp.success) {
2035
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2036
+ code: HardwareErrorCode3.DeviceMismatch
2037
+ });
2038
+ }
2039
+ }
2040
+ return await this._callConnector(reSessionId, method, effectiveParams, signal);
2041
+ } catch (retryErr) {
2042
+ if (signal.aborted) throw retryErr;
2043
+ lastErr = retryErr;
2044
+ const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
2045
+ if (!canRetry) {
2046
+ throw retryErr;
2047
+ }
2048
+ }
3037
2049
  }
2050
+ throw lastErr;
2051
+ }
2052
+ if (isTimeoutError(err)) {
2053
+ debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
2054
+ this._discoveredDevices.delete(resolvedConnectId);
2055
+ return this._retryWithFreshConnection(
2056
+ resolvedConnectId,
2057
+ method,
2058
+ effectiveParams,
2059
+ signal,
2060
+ err,
2061
+ fingerprint
2062
+ );
2063
+ }
2064
+ if (isConnectionLevelError(err)) {
2065
+ debugLog("[LedgerAdapter] connection-level fail-closed reset");
2066
+ this._sessions.delete(resolvedConnectId);
2067
+ this._discoveredDevices.delete(resolvedConnectId);
2068
+ this.connector.reset();
2069
+ const codeNum = err?.code;
2070
+ throw Object.assign(err, {
2071
+ code: codeNum ?? HardwareErrorCode3.DeviceDisconnected
2072
+ });
3038
2073
  }
3039
2074
  throw err;
3040
2075
  }
3041
2076
  }
2077
+ /**
2078
+ * Stuck-app recovery: pause for the device's UI transition, then retry once.
2079
+ *
2080
+ * Caller has already cleared the session + reset connector. We wait so Stax
2081
+ * finishes its post-CloseApp animation, then go through ensureConnected +
2082
+ * fingerprint check + call exactly once. Caller decides what to do on a
2083
+ * second stuck-app hit.
2084
+ */
2085
+ async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
2086
+ await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2087
+ const retryConnectId = await this.ensureConnected(
2088
+ resolvedConnectId,
2089
+ signal,
2090
+ !!fingerprint?.deviceId && !fingerprint.skipFingerprint
2091
+ );
2092
+ const retrySessionId = this._sessions.get(retryConnectId);
2093
+ if (!retrySessionId) throw originalErr;
2094
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2095
+ const fp = await this._abortable(
2096
+ signal,
2097
+ this._verifyDeviceFingerprintWithSession(
2098
+ retrySessionId,
2099
+ fingerprint.deviceId,
2100
+ fingerprint.chain
2101
+ )
2102
+ );
2103
+ if (!fp.success) {
2104
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2105
+ code: HardwareErrorCode3.DeviceMismatch
2106
+ });
2107
+ }
2108
+ }
2109
+ return this._callConnector(retrySessionId, method, params, signal);
2110
+ }
3042
2111
  _sleepAbortable(ms, signal) {
3043
2112
  return new Promise((resolve, reject) => {
3044
2113
  if (signal.aborted) {
@@ -3057,22 +2126,111 @@ var _LedgerAdapter = class _LedgerAdapter {
3057
2126
  });
3058
2127
  }
3059
2128
  /**
3060
- * Ensure OS-level Bluetooth/USB permission; times out after 60s if the host never
3061
- * replies. Without connectId the request is environment-level, else device-level.
2129
+ * Clear stale session, reconnect, and retry the call.
2130
+ *
2131
+ * Timeout recovery starts with a full connector reset. After an APDU
2132
+ * timeout, DMK/transport state may still emit malformed responses; retrying
2133
+ * on the same DMK can poison the next chain switch.
2134
+ */
2135
+ async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
2136
+ this.connector.reset();
2137
+ this._sessions.clear();
2138
+ this._discoveredDevices.clear();
2139
+ this._connectingPromise = null;
2140
+ const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
2141
+ const retryConnectId = await this.ensureConnected(
2142
+ targetConnectId,
2143
+ signal,
2144
+ allowUsbEphemeralFallback
2145
+ );
2146
+ const retrySessionId = this._sessions.get(retryConnectId);
2147
+ if (!retrySessionId) {
2148
+ throw originalErr;
2149
+ }
2150
+ try {
2151
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2152
+ const fp = await this._abortable(
2153
+ signal,
2154
+ this._verifyDeviceFingerprintWithSession(
2155
+ retrySessionId,
2156
+ fingerprint.deviceId,
2157
+ fingerprint.chain
2158
+ )
2159
+ );
2160
+ if (!fp.success) {
2161
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2162
+ code: HardwareErrorCode3.DeviceMismatch
2163
+ });
2164
+ }
2165
+ }
2166
+ return await this._callConnector(retrySessionId, method, params, signal);
2167
+ } catch (retryErr) {
2168
+ if (signal.aborted) throw retryErr;
2169
+ this.connector.reset();
2170
+ this._sessions.clear();
2171
+ this._discoveredDevices.clear();
2172
+ this._connectingPromise = null;
2173
+ if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
2174
+ throw retryErr;
2175
+ }
2176
+ debugLog(
2177
+ "[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
2178
+ );
2179
+ this.connector.reset();
2180
+ this._sessions.clear();
2181
+ this._discoveredDevices.clear();
2182
+ this._connectingPromise = null;
2183
+ const finalConnectId = await this.ensureConnected(
2184
+ targetConnectId,
2185
+ signal,
2186
+ allowUsbEphemeralFallback
2187
+ );
2188
+ const finalSessionId = this._sessions.get(finalConnectId);
2189
+ if (!finalSessionId) {
2190
+ throw originalErr;
2191
+ }
2192
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2193
+ const fp = await this._abortable(
2194
+ signal,
2195
+ this._verifyDeviceFingerprintWithSession(
2196
+ finalSessionId,
2197
+ fingerprint.deviceId,
2198
+ fingerprint.chain
2199
+ )
2200
+ );
2201
+ if (!fp.success) {
2202
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2203
+ code: HardwareErrorCode3.DeviceMismatch
2204
+ });
2205
+ }
2206
+ }
2207
+ return this._callConnector(finalSessionId, method, params, signal);
2208
+ }
2209
+ }
2210
+ /**
2211
+ * Ensure OS-level device permission (Bluetooth / USB) before proceeding.
2212
+ *
2213
+ * Emits `REQUEST_DEVICE_PERMISSION` and awaits the consumer's
2214
+ * `RECEIVE_DEVICE_PERMISSION` reply (60s budget covers "probe → system
2215
+ * prompt → user tap" plus a generous margin). If the consumer never wires
2216
+ * a handler or never replies, the wait times out and the operation fails
2217
+ * fast so scanners/callers don't hang silently.
2218
+ *
2219
+ * - No connectId (searchDevices): environment-level permission
2220
+ * - With connectId (business methods): device-level permission
3062
2221
  */
3063
2222
  async _ensureDevicePermission(connectId, deviceId, signal) {
3064
2223
  if (signal?.aborted) {
3065
2224
  _LedgerAdapter._throwIfAborted(signal);
3066
2225
  }
3067
2226
  const transportType = this.activeTransport ?? "hid";
3068
- const operationId = this._activeOperationId();
3069
2227
  const waitPromise = this._uiRegistry.wait(
3070
2228
  UI_REQUEST.REQUEST_DEVICE_PERMISSION,
3071
- { timeoutMs: 6e4, operationId }
2229
+ { timeoutMs: 6e4 }
3072
2230
  );
3073
2231
  this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
3074
2232
  type: UI_REQUEST.REQUEST_DEVICE_PERMISSION,
3075
- payload: { transportType, connectId, deviceId, operationId }
2233
+ payload: { transportType, connectId, deviceId }
3076
2234
  });
3077
2235
  let response;
3078
2236
  const onAbort = () => {
@@ -3118,15 +2276,7 @@ var _LedgerAdapter = class _LedgerAdapter {
3118
2276
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
3119
2277
  const e = err;
3120
2278
  const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
3121
- return ledgerFailure(
3122
- e.code,
3123
- e.message ?? "Unknown error",
3124
- e.appName,
3125
- tag,
3126
- params,
3127
- void 0,
3128
- isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
3129
- );
2279
+ return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
3130
2280
  }
3131
2281
  const mapped = mapLedgerError(err);
3132
2282
  return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
@@ -3153,7 +2303,7 @@ var _LedgerAdapter = class _LedgerAdapter {
3153
2303
  deviceId: device.deviceId,
3154
2304
  connectId: device.connectId,
3155
2305
  label: device.name,
3156
- connectionType: device.connectionType ?? this.connector.connectionType,
2306
+ connectionType: this.connector.connectionType,
3157
2307
  rssi: device.rssi,
3158
2308
  isConnectable: device.isConnectable,
3159
2309
  serialNumber: device.serialNumber,
@@ -3179,8 +2329,8 @@ var LedgerAdapter = _LedgerAdapter;
3179
2329
 
3180
2330
  // src/connector/LedgerConnectorBase.ts
3181
2331
  import {
3182
- EConnectorInteraction as EConnectorInteraction3,
3183
- HardwareErrorCode as HardwareErrorCode7,
2332
+ EConnectorInteraction as EConnectorInteraction4,
2333
+ HardwareErrorCode as HardwareErrorCode8,
3184
2334
  isKnownNonTargetHardwareVendor,
3185
2335
  serializeConnectorError
3186
2336
  } from "@onekeyfe/hwk-adapter-core";
@@ -3453,7 +2603,7 @@ var LedgerDeviceManager = class {
3453
2603
 
3454
2604
  // src/signer/SignerManager.ts
3455
2605
  import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
3456
- import { ContextModuleBuilder, ContextModuleChainID } from "@ledgerhq/context-module";
2606
+ import { ContextModuleBuilder } from "@ledgerhq/context-module";
3457
2607
 
3458
2608
  // src/signer/SignerEth.ts
3459
2609
  import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
@@ -3719,7 +2869,7 @@ var SignerManager = class _SignerManager {
3719
2869
  return (args) => new SignerEthBuilder(args);
3720
2870
  }
3721
2871
  static _createContextModule() {
3722
- const contextModule = new ContextModuleBuilder({}).setChain(ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
2872
+ const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
3723
2873
  return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
3724
2874
  }
3725
2875
  static wrapBlindSigningReportNonBlocking(contextModule) {
@@ -3742,40 +2892,19 @@ import { HardwareErrorCode as HardwareErrorCode4, padHex64, stripHex } from "@on
3742
2892
 
3743
2893
  // src/connector/chains/utils.ts
3744
2894
  import { EConnectorInteraction as EConnectorInteraction2 } from "@onekeyfe/hwk-adapter-core";
3745
- function normalizePath(path) {
3746
- return path.startsWith("m/") ? path.slice(2) : path;
3747
- }
3748
- function collapseSignerInteraction(interaction) {
3749
- switch (interaction) {
3750
- case "confirm-open-app":
3751
- return EConnectorInteraction2.ConfirmOpenApp;
3752
- case "unlock-device":
3753
- return EConnectorInteraction2.UnlockDevice;
3754
- case "interaction-complete":
3755
- return EConnectorInteraction2.InteractionComplete;
3756
- default:
3757
- return EConnectorInteraction2.ConfirmOnDevice;
3758
- }
3759
- }
3760
- function wireSignerToSession(ctx, sessionId, chain, signer) {
3761
- signer.onInteraction = (interaction) => {
3762
- debugLog(`[LedgerConnector] ${chain}.onInteraction:`, interaction);
3763
- ctx.emit("ui-event", {
3764
- type: collapseSignerInteraction(interaction),
3765
- payload: { sessionId }
3766
- });
3767
- };
3768
- signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
3769
- return signer;
3770
- }
3771
- async function runSignerCall(ctx, sessionId, call) {
3772
- try {
3773
- return await call();
3774
- } catch (err) {
3775
- ctx.invalidateSession(sessionId);
3776
- throw ctx.wrapError(err);
3777
- } finally {
3778
- ctx.clearCanceller(sessionId);
2895
+ function normalizePath(path) {
2896
+ return path.startsWith("m/") ? path.slice(2) : path;
2897
+ }
2898
+ function collapseSignerInteraction(interaction) {
2899
+ switch (interaction) {
2900
+ case "confirm-open-app":
2901
+ return EConnectorInteraction2.ConfirmOpenApp;
2902
+ case "unlock-device":
2903
+ return EConnectorInteraction2.UnlockDevice;
2904
+ case "interaction-complete":
2905
+ return EConnectorInteraction2.InteractionComplete;
2906
+ default:
2907
+ return EConnectorInteraction2.ConfirmOnDevice;
3779
2908
  }
3780
2909
  }
3781
2910
 
@@ -4210,8 +3339,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
4210
3339
  }
4211
3340
 
4212
3341
  // src/connector/chains/sol.ts
4213
- import { bytesToHex, hexToBytes as hexToBytes3, prepareSolanaOffchainMessageV1 } from "@onekeyfe/hwk-adapter-core";
4214
- import bs58 from "bs58";
3342
+ import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
4215
3343
 
4216
3344
  // src/signer/SignerSol.ts
4217
3345
  var SignerSol = class {
@@ -4295,21 +3423,8 @@ async function solSignMessage(ctx, sessionId, params) {
4295
3423
  const path = normalizePath(params.path);
4296
3424
  const messageBytes = hexToBytes3(params.message);
4297
3425
  try {
4298
- if (params.messageVersion === 1) {
4299
- const preparedMessage = prepareSolanaOffchainMessageV1({
4300
- message: messageBytes,
4301
- requiredSigners: params.requiredSigners
4302
- });
4303
- const { SignMessageVersion } = await ctx.importLedgerKit(
4304
- "@ledgerhq/device-signer-kit-solana"
4305
- );
4306
- const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
4307
- version: SignMessageVersion.Raw
4308
- });
4309
- return { signature: decodeBase58Signature(result2.signature) };
4310
- }
4311
3426
  const result = await solSigner.signMessage(path, messageBytes);
4312
- return { signature: decodeBase58EnvelopeSignature(result.signature) };
3427
+ return { signature: result.signature };
4313
3428
  } catch (err) {
4314
3429
  ctx.invalidateSession(sessionId);
4315
3430
  throw ctx.wrapError(err);
@@ -4317,27 +3432,11 @@ async function solSignMessage(ctx, sessionId, params) {
4317
3432
  ctx.clearCanceller(sessionId);
4318
3433
  }
4319
3434
  }
4320
- function decodeBase58Signature(signature) {
4321
- const bytes = bs58.decode(signature);
4322
- if (bytes.length !== 64) {
4323
- throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
4324
- }
4325
- return bytesToHex(bytes);
4326
- }
4327
- function decodeBase58EnvelopeSignature(envelope) {
4328
- const bytes = bs58.decode(envelope);
4329
- if (bytes.length < 65 || bytes[0] !== 1) {
4330
- throw new Error("Ledger Solana signature envelope is invalid");
4331
- }
4332
- return bytesToHex(bytes.subarray(1, 65));
4333
- }
4334
3435
  async function _createSolSigner(ctx, sessionId) {
4335
3436
  const dmk = await ctx.getOrCreateDmk();
4336
- const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
4337
- "@ledgerhq/context-module"
4338
- );
3437
+ const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit("@ledgerhq/context-module");
4339
3438
  const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
4340
- const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
3439
+ const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
4341
3440
  const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
4342
3441
  const signer = new SignerSol(sdkSigner);
4343
3442
  signer.onInteraction = (interaction) => {
@@ -4353,147 +3452,361 @@ async function _createSolSigner(ctx, sessionId) {
4353
3452
  return signer;
4354
3453
  }
4355
3454
 
4356
- // src/connector/chains/zcash.ts
4357
- import { bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
3455
+ // src/connector/chains/tron.ts
3456
+ import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
3457
+ import Trx from "@ledgerhq/hw-app-trx";
4358
3458
 
4359
- // src/signer/SignerZcash.ts
4360
- var SignerZcash = class {
4361
- // eslint-disable-next-line no-useless-constructor, no-empty-function
4362
- constructor(_sdk) {
4363
- this._sdk = _sdk;
3459
+ // src/connector/chains/legacyChainCall.ts
3460
+ import { EConnectorInteraction as EConnectorInteraction3 } from "@onekeyfe/hwk-adapter-core";
3461
+
3462
+ // src/app/AppManager.ts
3463
+ import {
3464
+ CloseAppCommand,
3465
+ GetAppAndVersionCommand,
3466
+ OpenAppCommand,
3467
+ isSuccessCommandResult
3468
+ } from "@ledgerhq/device-management-kit";
3469
+ import { HardwareErrorCode as HardwareErrorCode6 } from "@onekeyfe/hwk-adapter-core";
3470
+ var APP_NAME_MAP = {
3471
+ ETH: "Ethereum",
3472
+ BTC: "Bitcoin",
3473
+ SOL: "Solana",
3474
+ TRX: "Tron",
3475
+ XRP: "XRP",
3476
+ ADA: "Cardano",
3477
+ DOT: "Polkadot",
3478
+ ATOM: "Cosmos"
3479
+ };
3480
+ var DASHBOARD_APP_NAME = "BOLOS";
3481
+ var AppManager = class {
3482
+ constructor(dmk, options) {
3483
+ this._dmk = dmk;
3484
+ this._waitMs = options?.waitMs ?? 1e3;
3485
+ this._maxRetries = options?.maxRetries ?? 10;
4364
3486
  }
4365
- /** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
4366
- async getFullViewingKey(derivationPath, options) {
4367
- const action = this._sdk.getFullViewingKey(derivationPath, options);
4368
- return deviceActionToPromise(
4369
- action,
4370
- this.onInteraction,
4371
- void 0,
4372
- this.onRegisterCanceller
3487
+ /**
3488
+ * Return the Ledger app name for a given chain ticker,
3489
+ * or undefined if the chain is not supported.
3490
+ */
3491
+ static getAppName(chain) {
3492
+ return APP_NAME_MAP[chain];
3493
+ }
3494
+ /**
3495
+ * Ensure the target app is open on the device identified by `sessionId`.
3496
+ *
3497
+ * Flow:
3498
+ * 1. Check the currently running app.
3499
+ * 2. If it is already the target, return immediately.
3500
+ * 3. If a different app is running (not dashboard), close it first.
3501
+ * 4. Open the target app.
3502
+ * 5. Poll until the device confirms the target app is running.
3503
+ */
3504
+ /**
3505
+ * @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
3506
+ * device is about to display "Open <app>" on screen and wait for the
3507
+ * user's button press. UI consumers should show their "open app" prompt
3508
+ * in response. NOT called when the target app is already open (no user
3509
+ * interaction needed in that case).
3510
+ *
3511
+ * Important: OpenAppCommand is blocking. It does not resolve until the user
3512
+ * has physically confirmed on the device, so anything that runs AFTER
3513
+ * `await this._openApp(...)` lands AFTER the prompt is already gone.
3514
+ * Hence the callback must fire BEFORE that await.
3515
+ */
3516
+ async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
3517
+ const currentApp = await this._getCurrentApp(sessionId);
3518
+ if (currentApp === targetAppName) {
3519
+ return;
3520
+ }
3521
+ if (!this._isDashboard(currentApp)) {
3522
+ await this._closeCurrentApp(sessionId);
3523
+ await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
3524
+ }
3525
+ onConfirmOnDevice?.();
3526
+ await this._openApp(sessionId, targetAppName);
3527
+ await this._waitForApp(sessionId, targetAppName);
3528
+ }
3529
+ // ---------------------------------------------------------------------------
3530
+ // Private helpers
3531
+ // ---------------------------------------------------------------------------
3532
+ async _getCurrentApp(sessionId) {
3533
+ const result = await this._dmk.sendCommand({
3534
+ sessionId,
3535
+ command: new GetAppAndVersionCommand()
3536
+ });
3537
+ if (isSuccessCommandResult(result)) {
3538
+ debugLog("[AppManager] currentApp:", result.data.name);
3539
+ return result.data.name;
3540
+ }
3541
+ const errResult = result;
3542
+ const dmkErr = errResult.error ?? {};
3543
+ const original = dmkErr.originalError;
3544
+ debugLog(
3545
+ "[AppManager] _getCurrentApp failed sessionId=",
3546
+ sessionId,
3547
+ "tag=",
3548
+ dmkErr._tag,
3549
+ "errorCode=",
3550
+ dmkErr.errorCode,
3551
+ "message=",
3552
+ dmkErr.message,
3553
+ "originalErrorMessage=",
3554
+ original?.message ?? String(original ?? "")
3555
+ );
3556
+ throw Object.assign(
3557
+ new Error(
3558
+ dmkErr.message ?? "Failed to get current app from device"
3559
+ ),
3560
+ {
3561
+ _tag: dmkErr._tag,
3562
+ errorCode: dmkErr.errorCode,
3563
+ originalError: original
3564
+ }
4373
3565
  );
4374
3566
  }
4375
- /** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
4376
- async getShieldedAddress(derivationPath, options) {
4377
- const action = this._sdk.getShieldedAddress(derivationPath, options);
4378
- return deviceActionToPromise(
4379
- action,
4380
- this.onInteraction,
4381
- void 0,
4382
- this.onRegisterCanceller
3567
+ async _openApp(sessionId, appName) {
3568
+ const result = await this._dmk.sendCommand({
3569
+ sessionId,
3570
+ command: new OpenAppCommand({ appName })
3571
+ });
3572
+ if (!isSuccessCommandResult(result)) {
3573
+ const dmkErr = result.error;
3574
+ const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
3575
+ const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
3576
+ debugLog(
3577
+ "[AppManager] openApp failed:",
3578
+ appName,
3579
+ "errorCode:",
3580
+ errorCode,
3581
+ "tag:",
3582
+ dmkErr._tag
3583
+ );
3584
+ let code;
3585
+ if (errorCode === "6807" || /unknown application/i.test(message)) {
3586
+ code = HardwareErrorCode6.AppNotInstalled;
3587
+ } else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
3588
+ code = HardwareErrorCode6.UserRejected;
3589
+ }
3590
+ throw Object.assign(new Error(`Failed to open "${appName}"`), {
3591
+ _tag: ERROR_TAG.OpenAppCommand,
3592
+ code,
3593
+ errorCode,
3594
+ statusCode: errorCode,
3595
+ appName,
3596
+ originalError: dmkErr
3597
+ });
3598
+ }
3599
+ }
3600
+ async _closeCurrentApp(sessionId) {
3601
+ debugLog("[AppManager] closeCurrentApp");
3602
+ await this._dmk.sendCommand({
3603
+ sessionId,
3604
+ command: new CloseAppCommand()
3605
+ });
3606
+ }
3607
+ /**
3608
+ * Poll the device until the expected app is reported as running,
3609
+ * or throw after `_maxRetries` attempts.
3610
+ */
3611
+ async _waitForApp(sessionId, expectedAppName) {
3612
+ let lastSeen = "";
3613
+ for (let i = 0; i < this._maxRetries; i++) {
3614
+ await this._wait();
3615
+ const current = await this._getCurrentApp(sessionId);
3616
+ lastSeen = current;
3617
+ if (current === expectedAppName) {
3618
+ return;
3619
+ }
3620
+ }
3621
+ debugLog(
3622
+ "[AppManager] waitForApp exhausted: expected=",
3623
+ expectedAppName,
3624
+ "lastSeen=",
3625
+ lastSeen
3626
+ );
3627
+ throw new Error(
3628
+ `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
4383
3629
  );
4384
3630
  }
3631
+ _isDashboard(appName) {
3632
+ return appName === DASHBOARD_APP_NAME;
3633
+ }
3634
+ _wait() {
3635
+ return new Promise((resolve) => setTimeout(resolve, this._waitMs));
3636
+ }
4385
3637
  };
4386
3638
 
4387
- // src/connector/chains/zcash.ts
4388
- async function zcashGetFullViewingKey(ctx, sessionId, params) {
4389
- const signer = await _createZcashSigner(ctx, sessionId);
4390
- const path = normalizePath(params.path);
4391
- const mode = params.mode ?? "ufvk";
4392
- return runSignerCall(ctx, sessionId, async () => {
4393
- const result = await signer.getFullViewingKey(path, { mode });
4394
- if (result.mode === "ufvk") {
4395
- return { path: params.path, mode, ufvk: result.fullViewingKey };
4396
- }
4397
- return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
4398
- });
3639
+ // src/connector/chains/legacyChainCall.ts
3640
+ function isLegacyWrongAppError(err, _appName) {
3641
+ return isWrongAppError(err);
4399
3642
  }
4400
- async function zcashGetShieldedAddress(ctx, sessionId, params) {
4401
- const signer = await _createZcashSigner(ctx, sessionId);
4402
- const path = normalizePath(params.path);
4403
- return runSignerCall(ctx, sessionId, async () => {
4404
- const result = await signer.getShieldedAddress(path, {
4405
- checkOnDevice: params.showOnDevice ?? false
3643
+ async function withLegacyChainCall(ctx, sessionId, options, action) {
3644
+ const { appName, needsConfirmation } = options;
3645
+ let openAppPromptShown = false;
3646
+ const onAppOpenPrompt = () => {
3647
+ openAppPromptShown = true;
3648
+ ctx.emit("ui-event", {
3649
+ type: EConnectorInteraction3.ConfirmOpenApp,
3650
+ payload: { sessionId }
4406
3651
  });
4407
- return { address: result.address, path: params.path };
4408
- });
3652
+ };
3653
+ const closeOpenAppUiIfShown = () => {
3654
+ if (openAppPromptShown) {
3655
+ ctx.emit("ui-event", {
3656
+ type: EConnectorInteraction3.InteractionComplete,
3657
+ payload: { sessionId }
3658
+ });
3659
+ openAppPromptShown = false;
3660
+ }
3661
+ };
3662
+ try {
3663
+ await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
3664
+ } catch (err) {
3665
+ debugLog(
3666
+ "[LegacyChainCall] pre-flight ensureAppOpen failed:",
3667
+ appName,
3668
+ err?.message
3669
+ );
3670
+ closeOpenAppUiIfShown();
3671
+ throw ctx.wrapError(err, { defaultAppName: appName });
3672
+ }
3673
+ const runOnce = async () => {
3674
+ let confirmEmitted = false;
3675
+ if (needsConfirmation) {
3676
+ ctx.emit("ui-event", {
3677
+ type: EConnectorInteraction3.ConfirmOnDevice,
3678
+ payload: { sessionId }
3679
+ });
3680
+ confirmEmitted = true;
3681
+ }
3682
+ try {
3683
+ return await action(sessionId);
3684
+ } finally {
3685
+ if (confirmEmitted || openAppPromptShown) {
3686
+ ctx.emit("ui-event", {
3687
+ type: EConnectorInteraction3.InteractionComplete,
3688
+ payload: { sessionId }
3689
+ });
3690
+ openAppPromptShown = false;
3691
+ }
3692
+ }
3693
+ };
3694
+ try {
3695
+ return await runOnce();
3696
+ } catch (err) {
3697
+ if (!isLegacyWrongAppError(err, appName)) {
3698
+ debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
3699
+ ctx.invalidateSession(sessionId);
3700
+ throw ctx.wrapError(err, { defaultAppName: appName });
3701
+ }
3702
+ debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
3703
+ try {
3704
+ await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
3705
+ } catch (switchErr) {
3706
+ debugLog(
3707
+ "[LegacyChainCall] retry ensureAppOpen failed:",
3708
+ appName,
3709
+ switchErr?.message
3710
+ );
3711
+ closeOpenAppUiIfShown();
3712
+ throw ctx.wrapError(switchErr, { defaultAppName: appName });
3713
+ }
3714
+ ctx.clearAllSigners();
3715
+ const result = await runOnce();
3716
+ debugLog("[LegacyChainCall] retry succeeded:", appName);
3717
+ return result;
3718
+ }
4409
3719
  }
4410
- async function _createZcashSigner(ctx, sessionId) {
3720
+ async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
4411
3721
  const dmk = await ctx.getOrCreateDmk();
4412
- const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
4413
- const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
4414
- return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
3722
+ const appManager = new AppManager(dmk);
3723
+ await appManager.ensureAppOpen(sessionId, appName, onPrompt);
4415
3724
  }
4416
3725
 
4417
- // src/connector/chains/tron.ts
4418
- import { HardwareErrorCode as HardwareErrorCode6, bytesToHex as bytesToHex3, hexToBytes as hexToBytes4 } from "@onekeyfe/hwk-adapter-core";
4419
-
4420
- // src/signer/SignerTron.ts
4421
- var SignerTron = class {
4422
- // eslint-disable-next-line no-useless-constructor, no-empty-function
4423
- constructor(_sdk) {
4424
- this._sdk = _sdk;
4425
- }
4426
- /** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
4427
- async getAddress(derivationPath, options) {
4428
- const action = this._sdk.getAddress(derivationPath, options);
4429
- const result = await deviceActionToPromise(
4430
- action,
4431
- this.onInteraction,
4432
- void 0,
4433
- this.onRegisterCanceller
4434
- );
4435
- return { address: result.address, publicKey: result.publicKey };
3726
+ // src/transport/DmkTransport.ts
3727
+ import Transport from "@ledgerhq/hw-transport";
3728
+ var DmkTransport = class extends Transport {
3729
+ constructor(dmk, sessionId) {
3730
+ super();
3731
+ this._dmk = dmk;
3732
+ this._sessionId = sessionId;
4436
3733
  }
4437
- /** Sign a protobuf-encoded raw transaction. */
4438
- async signTransaction(derivationPath, transaction, options) {
4439
- const action = this._sdk.signTransaction(derivationPath, transaction, options);
4440
- return deviceActionToPromise(
4441
- action,
4442
- this.onInteraction,
4443
- void 0,
4444
- this.onRegisterCanceller
4445
- );
3734
+ async exchange(apdu) {
3735
+ const response = await this._dmk.sendApdu({
3736
+ sessionId: this._sessionId,
3737
+ apdu: new Uint8Array(apdu)
3738
+ });
3739
+ const { data, statusCode } = response;
3740
+ const result = Buffer.alloc(data.length + 2);
3741
+ if (data.length > 0) {
3742
+ result.set(data, 0);
3743
+ }
3744
+ result.set(statusCode, data.length);
3745
+ return result;
4446
3746
  }
4447
- /** Sign a personal message (TIP-191). */
4448
- async signPersonalMessage(derivationPath, message, options) {
4449
- const action = this._sdk.signPersonalMessage(derivationPath, message, options);
4450
- return deviceActionToPromise(
4451
- action,
4452
- this.onInteraction,
4453
- void 0,
4454
- this.onRegisterCanceller
4455
- );
3747
+ async close() {
4456
3748
  }
4457
3749
  };
4458
3750
 
4459
3751
  // src/connector/chains/tron.ts
4460
3752
  async function tronGetAddress(ctx, sessionId, params) {
4461
- const tronSigner = await _createTronSigner(ctx, sessionId);
4462
3753
  const path = normalizePath(params.path);
4463
- return runSignerCall(ctx, sessionId, async () => {
4464
- const result = await tronSigner.getAddress(path, {
4465
- checkOnDevice: params.showOnDevice ?? false
4466
- });
4467
- return { address: result.address, publicKey: result.publicKey, path: params.path };
4468
- });
3754
+ const showOnDevice = params.showOnDevice ?? false;
3755
+ return withLegacyChainCall(
3756
+ ctx,
3757
+ sessionId,
3758
+ {
3759
+ appName: "Tron",
3760
+ // Only show "confirm on device" UI when the device is actually going
3761
+ // to display the address for the user to verify.
3762
+ needsConfirmation: showOnDevice
3763
+ },
3764
+ async (sid) => {
3765
+ const trx = await _createTrx(ctx, sid);
3766
+ const result = await trx.getAddress(path, showOnDevice);
3767
+ return { address: result.address, publicKey: result.publicKey, path: params.path };
3768
+ }
3769
+ );
4469
3770
  }
4470
3771
  async function tronSignTransaction(ctx, sessionId, params) {
4471
3772
  if (!params.rawTxHex) {
4472
3773
  throw Object.assign(
4473
3774
  new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
4474
- { code: HardwareErrorCode6.InvalidParams }
3775
+ { code: HardwareErrorCode7.InvalidParams }
4475
3776
  );
4476
3777
  }
4477
- const tronSigner = await _createTronSigner(ctx, sessionId);
4478
3778
  const path = normalizePath(params.path);
4479
- return runSignerCall(ctx, sessionId, async () => {
4480
- const signature = await tronSigner.signTransaction(path, hexToBytes4(params.rawTxHex));
4481
- return { signature: bytesToHex3(signature) };
4482
- });
3779
+ return withLegacyChainCall(
3780
+ ctx,
3781
+ sessionId,
3782
+ { appName: "Tron", needsConfirmation: true },
3783
+ async (sid) => {
3784
+ const trx = await _createTrx(ctx, sid);
3785
+ const signature = await trx.signTransaction(
3786
+ path,
3787
+ params.rawTxHex,
3788
+ params.tokenSignatures ?? []
3789
+ );
3790
+ return { signature };
3791
+ }
3792
+ );
4483
3793
  }
4484
3794
  async function tronSignMessage(ctx, sessionId, params) {
4485
- const tronSigner = await _createTronSigner(ctx, sessionId);
4486
3795
  const path = normalizePath(params.path);
4487
- return runSignerCall(ctx, sessionId, async () => {
4488
- const signature = await tronSigner.signPersonalMessage(path, hexToBytes4(params.messageHex));
4489
- return { signature: bytesToHex3(signature) };
4490
- });
3796
+ return withLegacyChainCall(
3797
+ ctx,
3798
+ sessionId,
3799
+ { appName: "Tron", needsConfirmation: true },
3800
+ async (sid) => {
3801
+ const trx = await _createTrx(ctx, sid);
3802
+ const signature = await trx.signPersonalMessage(path, params.messageHex);
3803
+ return { signature };
3804
+ }
3805
+ );
4491
3806
  }
4492
- async function _createTronSigner(ctx, sessionId) {
3807
+ async function _createTrx(ctx, sessionId) {
4493
3808
  const dmk = await ctx.getOrCreateDmk();
4494
- const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
4495
- const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
4496
- return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
3809
+ return new Trx(new DmkTransport(dmk, sessionId));
4497
3810
  }
4498
3811
 
4499
3812
  // src/device-apps/customActions.ts
@@ -4672,7 +3985,7 @@ var DeviceApps = class {
4672
3985
  seTargetId: v.seTargetId,
4673
3986
  mcuTargetId: v.mcuTargetId,
4674
3987
  seVersion: v.seVersion,
4675
- seFlagsHex: bytesToHex4(v.seFlags),
3988
+ seFlagsHex: bytesToHex2(v.seFlags),
4676
3989
  mcuSephVersion: v.mcuSephVersion,
4677
3990
  mcuBootloaderVersion: v.mcuBootloaderVersion,
4678
3991
  hwVersion: v.hwVersion
@@ -4746,7 +4059,7 @@ var DeviceApps = class {
4746
4059
  }
4747
4060
  }
4748
4061
  };
4749
- function bytesToHex4(bytes) {
4062
+ function bytesToHex2(bytes) {
4750
4063
  if (!bytes) return "";
4751
4064
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
4752
4065
  }
@@ -4789,11 +4102,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
4789
4102
  evm: "Ethereum",
4790
4103
  btc: "Bitcoin",
4791
4104
  sol: "Solana",
4792
- tron: "Tron",
4793
- zcash: "Zcash"
4105
+ tron: "Tron"
4794
4106
  };
4795
4107
  var HARDWARE_ERROR_CODE_VALUES = new Set(
4796
- Object.values(HardwareErrorCode7).filter((value) => typeof value === "number")
4108
+ Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
4797
4109
  );
4798
4110
  var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
4799
4111
  var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
@@ -4830,10 +4142,6 @@ async function defaultLedgerKitImporter(pkg) {
4830
4142
  return import("@ledgerhq/device-signer-kit-bitcoin");
4831
4143
  case "@ledgerhq/device-signer-kit-solana":
4832
4144
  return import("@ledgerhq/device-signer-kit-solana");
4833
- case "@ledgerhq/device-signer-kit-tron":
4834
- return import("@ledgerhq/device-signer-kit-tron");
4835
- case "@ledgerhq/device-signer-kit-zcash":
4836
- return import("@ledgerhq/device-signer-kit-zcash");
4837
4145
  case "@ledgerhq/context-module":
4838
4146
  return import("@ledgerhq/context-module");
4839
4147
  default:
@@ -4893,7 +4201,6 @@ var LedgerConnectorBase = class {
4893
4201
  this._ctx = {
4894
4202
  emit: (event, data) => this._emit(event, data),
4895
4203
  invalidateSession: (sid) => this._invalidateSession(sid),
4896
- teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
4897
4204
  wrapError: (err, opts) => this._wrapError(err, opts),
4898
4205
  getOrCreateDmk: () => this._getOrCreateDmk(),
4899
4206
  getDeviceManager: () => this._getDeviceManager(),
@@ -4993,7 +4300,7 @@ var LedgerConnectorBase = class {
4993
4300
  `Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
4994
4301
  );
4995
4302
  err._tag = ERROR_TAG.BlePairingTimeout;
4996
- err.code = HardwareErrorCode7.BlePairingTimeout;
4303
+ err.code = HardwareErrorCode8.BlePairingTimeout;
4997
4304
  reject(err);
4998
4305
  }, HANG_CEILING_MS);
4999
4306
  });
@@ -5026,7 +4333,7 @@ var LedgerConnectorBase = class {
5026
4333
  "Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
5027
4334
  );
5028
4335
  err._tag = ERROR_TAG.DeviceNotAdvertising;
5029
- err.code = HardwareErrorCode7.DeviceNotFound;
4336
+ err.code = HardwareErrorCode8.DeviceNotFound;
5030
4337
  throw err;
5031
4338
  };
5032
4339
  const doConnect = async (path) => {
@@ -5083,14 +4390,14 @@ var LedgerConnectorBase = class {
5083
4390
  this._resetSignersAndSessions();
5084
4391
  if (isLedgerBleConnectionType(this.connectionType)) {
5085
4392
  const tag = err?._tag;
5086
- if (isKnownConnectionTag(tag) && !isConnectionOpeningTag(tag)) {
4393
+ if (isKnownConnectionTag(tag)) {
5087
4394
  throw err;
5088
4395
  }
5089
4396
  const wrapped = new Error(
5090
4397
  "Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
5091
4398
  );
5092
4399
  wrapped._tag = ERROR_TAG.BleGattBondingFailed;
5093
- wrapped.code = HardwareErrorCode7.BlePairingTimeout;
4400
+ wrapped.code = HardwareErrorCode8.BlePairingTimeout;
5094
4401
  wrapped.originalError = err;
5095
4402
  throw wrapped;
5096
4403
  }
@@ -5168,7 +4475,7 @@ var LedgerConnectorBase = class {
5168
4475
  this._unwatchSessionState(sessionId);
5169
4476
  this._signerManager?.invalidate(sessionId);
5170
4477
  this._cancellers.get(sessionId)?.({
5171
- code: HardwareErrorCode7.DeviceDisconnected,
4478
+ code: HardwareErrorCode8.DeviceDisconnected,
5172
4479
  tag: "DeviceDisconnected",
5173
4480
  message: "Device disconnected"
5174
4481
  });
@@ -5194,7 +4501,7 @@ var LedgerConnectorBase = class {
5194
4501
  success: false,
5195
4502
  error: serializeConnectorError(
5196
4503
  Object.assign(new Error("Ledger app is unresponsive"), {
5197
- code: HardwareErrorCode7.DeviceAppStuck,
4504
+ code: HardwareErrorCode8.DeviceAppStuck,
5198
4505
  _tag: ERROR_TAG.DeviceAppStuck,
5199
4506
  originalError: err
5200
4507
  })
@@ -5206,7 +4513,7 @@ var LedgerConnectorBase = class {
5206
4513
  success: false,
5207
4514
  error: serializeConnectorError(
5208
4515
  Object.assign(new Error("Device communication interrupted, please retry"), {
5209
- code: HardwareErrorCode7.TransportError,
4516
+ code: HardwareErrorCode8.TransportError,
5210
4517
  _tag: ERROR_TAG.DeviceTransportStuck,
5211
4518
  originalError: err
5212
4519
  })
@@ -5252,11 +4559,6 @@ var LedgerConnectorBase = class {
5252
4559
  return solSignTransaction(ctx, sessionId, params);
5253
4560
  case "solSignMessage":
5254
4561
  return solSignMessage(ctx, sessionId, params);
5255
- // ZCASH
5256
- case "zcashGetFullViewingKey":
5257
- return zcashGetFullViewingKey(ctx, sessionId, params);
5258
- case "zcashGetShieldedAddress":
5259
- return zcashGetShieldedAddress(ctx, sessionId, params);
5260
4562
  // TRON
5261
4563
  case "tronGetAddress":
5262
4564
  return tronGetAddress(ctx, sessionId, params);
@@ -5281,7 +4583,7 @@ var LedgerConnectorBase = class {
5281
4583
  try {
5282
4584
  return await apps.install(p.appName, ({ progress }) => {
5283
4585
  ctx.emit("ui-event", {
5284
- type: EConnectorInteraction3.AppInstallProgress,
4586
+ type: EConnectorInteraction4.AppInstallProgress,
5285
4587
  payload: {
5286
4588
  sessionId,
5287
4589
  appName: p.appName,
@@ -5290,7 +4592,7 @@ var LedgerConnectorBase = class {
5290
4592
  });
5291
4593
  });
5292
4594
  } catch (err) {
5293
- ctx.teardownSecureChannelSession(sessionId);
4595
+ ctx.invalidateSession(sessionId);
5294
4596
  throw ctx.wrapError(err);
5295
4597
  } finally {
5296
4598
  ctx.clearCanceller(sessionId);
@@ -5380,7 +4682,7 @@ var LedgerConnectorBase = class {
5380
4682
  );
5381
4683
  return { isGenuine: output.isGenuine, deviceId };
5382
4684
  } catch (err) {
5383
- ctx.teardownSecureChannelSession(sessionId);
4685
+ ctx.invalidateSession(sessionId);
5384
4686
  throw ctx.wrapError(err);
5385
4687
  } finally {
5386
4688
  ctx.clearCanceller(sessionId);
@@ -5499,26 +4801,9 @@ var LedgerConnectorBase = class {
5499
4801
  }
5500
4802
  return this._deviceAppsManager;
5501
4803
  }
5502
- // DeviceAppsManager is a per-call factory with no cached session state, so
5503
- // there is nothing to invalidate for it here.
5504
4804
  _invalidateSession(sessionId) {
5505
4805
  this._signerManager?.invalidate(sessionId);
5506
4806
  }
5507
- /**
5508
- * Fire the canceller so DMK closes the secure-channel WebSocket. The DMK device
5509
- * session is kept: unlock/retry recovery reuses it.
5510
- */
5511
- _teardownSecureChannelSession(sessionId) {
5512
- const cancel = this._cancellers.get(sessionId);
5513
- this._cancellers.delete(sessionId);
5514
- if (cancel) {
5515
- try {
5516
- cancel();
5517
- } catch {
5518
- }
5519
- }
5520
- this._invalidateSession(sessionId);
5521
- }
5522
4807
  /**
5523
4808
  * Replace an old session with a new one after app switch.
5524
4809
  * Emits device-connect so the adapter updates its _sessions Map.
@@ -5603,7 +4888,7 @@ var LedgerConnectorBase = class {
5603
4888
  * at every catch site. Falls through unchanged for unknown methods.
5604
4889
  */
5605
4890
  _ctxForMethod(method) {
5606
- const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
4891
+ const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
5607
4892
  const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
5608
4893
  if (!defaultAppName) return this._ctx;
5609
4894
  return {
@@ -5637,209 +4922,6 @@ var LedgerConnectorBase = class {
5637
4922
  return error;
5638
4923
  }
5639
4924
  };
5640
-
5641
- // src/transport/DmkTransport.ts
5642
- import Transport from "@ledgerhq/hw-transport";
5643
- var DmkTransport = class extends Transport {
5644
- constructor(dmk, sessionId) {
5645
- super();
5646
- this._dmk = dmk;
5647
- this._sessionId = sessionId;
5648
- }
5649
- async exchange(apdu) {
5650
- const response = await this._dmk.sendApdu({
5651
- sessionId: this._sessionId,
5652
- apdu: new Uint8Array(apdu)
5653
- });
5654
- const { data, statusCode } = response;
5655
- const result = Buffer.alloc(data.length + 2);
5656
- if (data.length > 0) {
5657
- result.set(data, 0);
5658
- }
5659
- result.set(statusCode, data.length);
5660
- return result;
5661
- }
5662
- async close() {
5663
- }
5664
- };
5665
-
5666
- // src/app/AppManager.ts
5667
- import {
5668
- CloseAppCommand,
5669
- GetAppAndVersionCommand,
5670
- OpenAppCommand,
5671
- isSuccessCommandResult
5672
- } from "@ledgerhq/device-management-kit";
5673
- import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
5674
- var APP_NAME_MAP = {
5675
- ETH: "Ethereum",
5676
- BTC: "Bitcoin",
5677
- SOL: "Solana",
5678
- TRX: "Tron",
5679
- ZEC: "Zcash",
5680
- XRP: "XRP",
5681
- ADA: "Cardano",
5682
- DOT: "Polkadot",
5683
- ATOM: "Cosmos"
5684
- };
5685
- var DASHBOARD_APP_NAME = "BOLOS";
5686
- var AppManager = class {
5687
- constructor(dmk, options) {
5688
- this._dmk = dmk;
5689
- this._waitMs = options?.waitMs ?? 1e3;
5690
- this._maxRetries = options?.maxRetries ?? 10;
5691
- }
5692
- /**
5693
- * Return the Ledger app name for a given chain ticker,
5694
- * or undefined if the chain is not supported.
5695
- */
5696
- static getAppName(chain) {
5697
- return APP_NAME_MAP[chain];
5698
- }
5699
- /**
5700
- * Ensure the target app is open on the device identified by `sessionId`.
5701
- *
5702
- * Flow:
5703
- * 1. Check the currently running app.
5704
- * 2. If it is already the target, return immediately.
5705
- * 3. If a different app is running (not dashboard), close it first.
5706
- * 4. Open the target app.
5707
- * 5. Poll until the device confirms the target app is running.
5708
- */
5709
- /**
5710
- * @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
5711
- * device is about to display "Open <app>" on screen and wait for the
5712
- * user's button press. UI consumers should show their "open app" prompt
5713
- * in response. NOT called when the target app is already open (no user
5714
- * interaction needed in that case).
5715
- *
5716
- * Important: OpenAppCommand is blocking. It does not resolve until the user
5717
- * has physically confirmed on the device, so anything that runs AFTER
5718
- * `await this._openApp(...)` lands AFTER the prompt is already gone.
5719
- * Hence the callback must fire BEFORE that await.
5720
- */
5721
- async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
5722
- const currentApp = await this._getCurrentApp(sessionId);
5723
- if (currentApp === targetAppName) {
5724
- return;
5725
- }
5726
- if (!this._isDashboard(currentApp)) {
5727
- await this._closeCurrentApp(sessionId);
5728
- await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
5729
- }
5730
- onConfirmOnDevice?.();
5731
- await this._openApp(sessionId, targetAppName);
5732
- await this._waitForApp(sessionId, targetAppName);
5733
- }
5734
- // ---------------------------------------------------------------------------
5735
- // Private helpers
5736
- // ---------------------------------------------------------------------------
5737
- async _getCurrentApp(sessionId) {
5738
- const result = await this._dmk.sendCommand({
5739
- sessionId,
5740
- command: new GetAppAndVersionCommand()
5741
- });
5742
- if (isSuccessCommandResult(result)) {
5743
- debugLog("[AppManager] currentApp:", result.data.name);
5744
- return result.data.name;
5745
- }
5746
- const errResult = result;
5747
- const dmkErr = errResult.error ?? {};
5748
- const original = dmkErr.originalError;
5749
- debugLog(
5750
- "[AppManager] _getCurrentApp failed sessionId=",
5751
- sessionId,
5752
- "tag=",
5753
- dmkErr._tag,
5754
- "errorCode=",
5755
- dmkErr.errorCode,
5756
- "message=",
5757
- dmkErr.message,
5758
- "originalErrorMessage=",
5759
- original?.message ?? String(original ?? "")
5760
- );
5761
- throw Object.assign(
5762
- new Error(
5763
- dmkErr.message ?? "Failed to get current app from device"
5764
- ),
5765
- {
5766
- _tag: dmkErr._tag,
5767
- errorCode: dmkErr.errorCode,
5768
- originalError: original
5769
- }
5770
- );
5771
- }
5772
- async _openApp(sessionId, appName) {
5773
- const result = await this._dmk.sendCommand({
5774
- sessionId,
5775
- command: new OpenAppCommand({ appName })
5776
- });
5777
- if (!isSuccessCommandResult(result)) {
5778
- const dmkErr = result.error;
5779
- const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
5780
- const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
5781
- debugLog(
5782
- "[AppManager] openApp failed:",
5783
- appName,
5784
- "errorCode:",
5785
- errorCode,
5786
- "tag:",
5787
- dmkErr._tag
5788
- );
5789
- let code;
5790
- if (errorCode === "6807" || /unknown application/i.test(message)) {
5791
- code = HardwareErrorCode8.AppNotInstalled;
5792
- } else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
5793
- code = HardwareErrorCode8.UserRejected;
5794
- }
5795
- throw Object.assign(new Error(`Failed to open "${appName}"`), {
5796
- _tag: ERROR_TAG.OpenAppCommand,
5797
- code,
5798
- errorCode,
5799
- statusCode: errorCode,
5800
- appName,
5801
- originalError: dmkErr
5802
- });
5803
- }
5804
- }
5805
- async _closeCurrentApp(sessionId) {
5806
- debugLog("[AppManager] closeCurrentApp");
5807
- await this._dmk.sendCommand({
5808
- sessionId,
5809
- command: new CloseAppCommand()
5810
- });
5811
- }
5812
- /**
5813
- * Poll the device until the expected app is reported as running,
5814
- * or throw after `_maxRetries` attempts.
5815
- */
5816
- async _waitForApp(sessionId, expectedAppName) {
5817
- let lastSeen = "";
5818
- for (let i = 0; i < this._maxRetries; i++) {
5819
- await this._wait();
5820
- const current = await this._getCurrentApp(sessionId);
5821
- lastSeen = current;
5822
- if (current === expectedAppName) {
5823
- return;
5824
- }
5825
- }
5826
- debugLog(
5827
- "[AppManager] waitForApp exhausted: expected=",
5828
- expectedAppName,
5829
- "lastSeen=",
5830
- lastSeen
5831
- );
5832
- throw new Error(
5833
- `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
5834
- );
5835
- }
5836
- _isDashboard(appName) {
5837
- return appName === DASHBOARD_APP_NAME;
5838
- }
5839
- _wait() {
5840
- return new Promise((resolve) => setTimeout(resolve, this._waitMs));
5841
- }
5842
- };
5843
4925
  export {
5844
4926
  AppManager,
5845
4927
  DmkTransport,
@@ -5850,7 +4932,6 @@ export {
5850
4932
  SignerEth,
5851
4933
  SignerManager,
5852
4934
  SignerSol,
5853
- SignerZcash,
5854
4935
  debugLog,
5855
4936
  deviceActionToPromise,
5856
4937
  isDeviceLockedError,