@onekeyfe/hwk-ledger-adapter 1.2.4 → 1.2.5-alpha.0

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.js CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  SignerEth: () => SignerEth,
40
40
  SignerManager: () => SignerManager,
41
41
  SignerSol: () => SignerSol,
42
+ SignerZcash: () => SignerZcash,
42
43
  debugLog: () => debugLog,
43
44
  deviceActionToPromise: () => deviceActionToPromise,
44
45
  isDeviceLockedError: () => isDeviceLockedError,
@@ -57,17 +58,14 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
57
58
 
58
59
  // src/errors.ts
59
60
  var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
60
- var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
61
- function createMultipleUsbLedgerDevicesError() {
62
- return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
63
- code: import_hwk_adapter_core.HardwareErrorCode.DeviceOneDeviceOnly
64
- });
65
- }
66
- function ledgerFailure(code, error, appName, tag, params) {
61
+ function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
67
62
  const payload = { error, code };
68
63
  if (appName !== void 0) payload.appName = appName;
69
64
  if (tag !== void 0) payload._tag = tag;
70
65
  if (params !== void 0) payload.params = params;
66
+ const resolvedOrigin = origin ?? (0, import_hwk_adapter_core.defaultOriginForCode)(code);
67
+ if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
68
+ payload.recovery = recovery ?? (0, import_hwk_adapter_core.defaultRecoveryForCode)(code);
71
69
  return { success: false, payload };
72
70
  }
73
71
  var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
@@ -208,7 +206,10 @@ var ERROR_TAG = {
208
206
  UnknownDevice: "UnknownDeviceError",
209
207
  DeviceSessionRefresher: "DeviceSessionRefresherError",
210
208
  DeviceNotInitialized: "DeviceNotInitializedError",
211
- OpeningConnection: "OpeningConnectionError",
209
+ // DMK class OpeningConnectionError carries `_tag` "ConnectionOpeningError"; both
210
+ // spellings are kept so a DMK release that aligns them still classifies.
211
+ OpeningConnection: "ConnectionOpeningError",
212
+ OpeningConnectionLegacy: "OpeningConnectionError",
212
213
  DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
213
214
  DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
214
215
  Disconnect: "DisconnectError",
@@ -221,7 +222,16 @@ var ERROR_TAG = {
221
222
  // DMK remote-network failures (manager-api HTTP / secure-channel WS).
222
223
  WebSocketConnection: "WebSocketConnectionError",
223
224
  HttpFetch: "FetchError",
224
- InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError"
225
+ NetworkDA: "NetworkDAError",
226
+ InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
227
+ ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
228
+ // DMK OS device actions. SecureChannelError is what mapInstallDAErrors() leaves
229
+ // after splitting out device answers, i.e. the relay itself broke.
230
+ SecureChannel: "SecureChannelError",
231
+ RefusedByUserDA: "RefusedByUserDAError",
232
+ AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
233
+ OutOfMemoryDA: "OutOfMemoryDAError",
234
+ DeviceNotOnboarded: "DeviceNotOnboardedError"
225
235
  };
226
236
  function isDeviceLockedError(err) {
227
237
  if (!err || typeof err !== "object") return false;
@@ -251,7 +261,6 @@ function isBlePairingFailureError(err) {
251
261
  return false;
252
262
  }
253
263
  var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
254
- ERROR_TAG.DeviceLocked,
255
264
  ERROR_TAG.DeviceNotAdvertising,
256
265
  ERROR_TAG.BlePairingTimeout,
257
266
  ERROR_TAG.BleGattBondingFailed,
@@ -263,6 +272,7 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
263
272
  ERROR_TAG.DeviceSessionRefresher,
264
273
  ERROR_TAG.DeviceNotInitialized,
265
274
  ERROR_TAG.OpeningConnection,
275
+ ERROR_TAG.OpeningConnectionLegacy,
266
276
  ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
267
277
  ERROR_TAG.DeviceDisconnectedWhileSending,
268
278
  ERROR_TAG.Disconnect,
@@ -277,7 +287,10 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
277
287
  // Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
278
288
  ERROR_TAG.DeviceNotInDiscoveryCache
279
289
  ]);
280
- var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
290
+ var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
291
+ ERROR_TAG.OpeningConnection,
292
+ ERROR_TAG.OpeningConnectionLegacy
293
+ ]);
281
294
  var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
282
295
  ERROR_TAG.DeviceNotRecognized,
283
296
  ERROR_TAG.DeviceSessionNotFound,
@@ -289,17 +302,18 @@ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
289
302
  ERROR_TAG.WebHIDDisconnect
290
303
  ]);
291
304
  function isConnectionLevelError(err) {
292
- if (!err || typeof err !== "object") return false;
293
- const tag = err._tag;
294
- if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
295
- const e = err;
296
- if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
297
- if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
298
- return false;
305
+ return hasErrorTag(err, CONNECTION_LEVEL_TAGS);
299
306
  }
300
307
  function isKnownConnectionTag(tag) {
301
308
  return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
302
309
  }
310
+ var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
311
+ ERROR_TAG.OpeningConnection,
312
+ ERROR_TAG.OpeningConnectionLegacy
313
+ ]);
314
+ function isConnectionOpeningTag(tag) {
315
+ return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
316
+ }
303
317
  function hasStatusCode(err, codeSet) {
304
318
  if (!err || typeof err !== "object") return false;
305
319
  const e = err;
@@ -335,28 +349,28 @@ function hasInvalidArgumentCode(err) {
335
349
  if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
336
350
  return false;
337
351
  }
338
- function isDeviceNotFoundError(err) {
352
+ function hasErrorTag(err, tags) {
339
353
  if (!err || typeof err !== "object") return false;
340
- const tag = err._tag;
341
- if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
342
354
  const e = err;
343
- if (e.originalError != null && isDeviceNotFoundError(e.originalError)) return true;
344
- if (e.error != null && e._tag && isDeviceNotFoundError(e.error)) return true;
355
+ if (typeof e._tag === "string" && tags.has(e._tag)) return true;
356
+ if (e.originalError != null && hasErrorTag(e.originalError, tags)) return true;
357
+ if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
345
358
  return false;
346
359
  }
360
+ function isDeviceNotFoundError(err) {
361
+ return hasErrorTag(err, DEVICE_NOT_FOUND_TAGS);
362
+ }
347
363
  function isDeviceBusyError(err) {
348
- if (!err || typeof err !== "object") return false;
349
- const tag = err._tag;
350
- if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
351
- const e = err;
352
- if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
353
- if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
354
- return false;
364
+ return hasErrorTag(err, DEVICE_BUSY_TAGS);
355
365
  }
366
+ var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
367
+ ERROR_TAG.UserRefusedOnDevice,
368
+ ERROR_TAG.RefusedByUserDA
369
+ ]);
356
370
  function isUserRejectedError(err) {
357
371
  if (!err || typeof err !== "object") return false;
358
372
  const e = err;
359
- if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
373
+ if (hasErrorTag(err, USER_REJECTED_TAGS)) return true;
360
374
  if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
361
375
  if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
362
376
  return false;
@@ -387,15 +401,33 @@ function isAppNotInstalledError(err) {
387
401
  if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
388
402
  return false;
389
403
  }
404
+ var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
390
405
  function isOutOfMemoryError(err) {
391
- if (!err || typeof err !== "object") return false;
392
- const e = err;
393
- return e._tag === "OutOfMemoryDAError";
406
+ return hasErrorTag(err, OUT_OF_MEMORY_TAGS);
407
+ }
408
+ var APP_ALREADY_INSTALLED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.AppAlreadyInstalledDA]);
409
+ function isAppAlreadyInstalledError(err) {
410
+ return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
411
+ }
412
+ var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.DeviceNotOnboarded]);
413
+ function isDeviceNotOnboardedError(err) {
414
+ return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
415
+ }
416
+ var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
417
+ ERROR_TAG.InvalidFirmwareMetadataResponse,
418
+ ERROR_TAG.ApplicationsMetadataTask
419
+ ]);
420
+ function isFirmwareMetadataError(err) {
421
+ return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
422
+ }
423
+ var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
424
+ function isSecureChannelError(err) {
425
+ return hasErrorTag(err, SECURE_CHANNEL_TAGS);
394
426
  }
395
427
  var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
396
428
  ERROR_TAG.WebSocketConnection,
397
429
  ERROR_TAG.HttpFetch,
398
- ERROR_TAG.InvalidFirmwareMetadataResponse
430
+ ERROR_TAG.NetworkDA
399
431
  ]);
400
432
  function isNetworkError(err) {
401
433
  if (!err || typeof err !== "object") return false;
@@ -445,23 +477,34 @@ function isTransportStuck(err) {
445
477
  function isStuckAppStateError(err) {
446
478
  return isAppStuckByApdu(err) || isTransportStuck(err);
447
479
  }
480
+ var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
481
+ function nestedErrorMessage(nested) {
482
+ if (!nested || typeof nested !== "object") return void 0;
483
+ const { message } = nested;
484
+ if (typeof message !== "string") return void 0;
485
+ const trimmed = message.trim();
486
+ if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
487
+ return message;
488
+ }
448
489
  function mapLedgerError(err, opts) {
449
490
  let originalMessage = "Unknown Ledger error";
450
491
  if (err instanceof Error) {
451
492
  originalMessage = err.message;
452
493
  } else if (err && typeof err === "object") {
453
494
  const e = err;
454
- originalMessage = String(e.message ?? e._tag ?? e.type ?? JSON.stringify(err));
495
+ originalMessage = String(
496
+ e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
497
+ );
455
498
  }
456
499
  let code;
457
500
  if (isDeviceLockedError(err)) {
458
501
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceLocked;
459
502
  } else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
460
503
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound;
461
- } else if (isDeviceBusyError(err)) {
462
- code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
463
504
  } else if (isBlePairingFailureError(err)) {
464
505
  code = import_hwk_adapter_core.HardwareErrorCode.BlePairingTimeout;
506
+ } else if (isDeviceBusyError(err)) {
507
+ code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
465
508
  } else if (isUserAbortedError(err)) {
466
509
  code = import_hwk_adapter_core.HardwareErrorCode.UserAborted;
467
510
  } else if (isUserRejectedError(err)) {
@@ -470,10 +513,18 @@ function mapLedgerError(err, opts) {
470
513
  code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
471
514
  } else if (isAppNotInstalledError(err)) {
472
515
  code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
516
+ } else if (isAppAlreadyInstalledError(err)) {
517
+ code = import_hwk_adapter_core.HardwareErrorCode.AppAlreadyInstalled;
473
518
  } else if (isOutOfMemoryError(err)) {
474
519
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceOutOfMemory;
520
+ } else if (isDeviceNotOnboardedError(err)) {
521
+ code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotInitialized;
522
+ } else if (isFirmwareMetadataError(err)) {
523
+ code = import_hwk_adapter_core.HardwareErrorCode.LedgerFirmwareMetadataError;
475
524
  } else if (isNetworkError(err)) {
476
525
  code = import_hwk_adapter_core.HardwareErrorCode.NetworkError;
526
+ } else if (isSecureChannelError(err)) {
527
+ code = import_hwk_adapter_core.HardwareErrorCode.LedgerSecureChannelError;
477
528
  } else if (isDeviceDisconnectedError(err)) {
478
529
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
479
530
  } else if (isTimeoutError(err)) {
@@ -486,7 +537,21 @@ function mapLedgerError(err, opts) {
486
537
  }
487
538
  const errAppName = err && typeof err === "object" ? err.appName : void 0;
488
539
  const appName = errAppName ?? opts?.defaultAppName;
489
- return { code, message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage), appName };
540
+ return {
541
+ code,
542
+ message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage),
543
+ origin: (0, import_hwk_adapter_core.defaultOriginForCode)(code),
544
+ appName
545
+ };
546
+ }
547
+
548
+ // src/utils/queueKey.ts
549
+ var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
550
+ function ledgerQueueKey({
551
+ operationId,
552
+ connectId
553
+ }) {
554
+ return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
490
555
  }
491
556
 
492
557
  // src/adapter/methods/allNetworkGetAddress.ts
@@ -547,65 +612,103 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
547
612
  var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
548
613
  function createAllNetworkGetAddress({
549
614
  callChain,
550
- getChainFingerprint
615
+ getChainFingerprint,
616
+ retainOperation,
617
+ errorToFailure,
618
+ createCancelScope
551
619
  }) {
552
620
  return async function allNetworkGetAddress(connectId, _deviceId, params) {
553
- debugLog("[LedgerAdapter][REQ]", { method: "allNetworkGetAddress", connectId, params });
621
+ debugLog("[LedgerAdapter][REQ]", {
622
+ method: "allNetworkGetAddress",
623
+ connectId,
624
+ itemCount: params.bundle.length
625
+ });
626
+ const target = (0, import_hwk_adapter_core2.resolveHardwareOperationTarget)(connectId, params.operationId, "ledger");
627
+ if (!target.success) return target;
628
+ const effectiveTargetId = target.payload.targetId ?? "";
629
+ let releaseOperationRetention;
630
+ try {
631
+ releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
632
+ } catch (error) {
633
+ return errorToFailure(error);
634
+ }
635
+ const cancelScope = createCancelScope(
636
+ ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
637
+ );
554
638
  const installContext = {};
555
639
  const commonParams = {
556
- autoInstallApp: params.autoInstallApp
640
+ autoInstallApp: params.autoInstallApp,
641
+ operationId: target.payload.operationId,
642
+ knownConnections: params.knownConnections,
643
+ extra: params.extra,
644
+ allowDeviceSelection: params.allowDeviceSelection,
645
+ supportedTransports: params.supportedTransports
557
646
  };
558
647
  const chainFingerprints = /* @__PURE__ */ new Map();
559
- const result = await (0, import_hwk_adapter_core2.runAllNetworkGetAddress)({
560
- connectId,
561
- deviceId: _deviceId,
562
- params,
563
- normalizeItem: normalizeLedgerAllNetworkItem,
564
- buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
565
- callItem: async ({ method, chain, item }) => {
566
- const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
567
- return callAllNetworkMethod(
568
- callChain,
569
- connectId,
570
- itemDeviceId,
571
- method,
648
+ try {
649
+ const result = await (0, import_hwk_adapter_core2.runAllNetworkGetAddress)({
650
+ connectId: effectiveTargetId,
651
+ deviceId: _deviceId,
652
+ params,
653
+ normalizeItem: normalizeLedgerAllNetworkItem,
654
+ buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
655
+ callItem: async ({ method, chain, item }) => {
656
+ if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
657
+ const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
658
+ return callAllNetworkMethod(
659
+ callChain,
660
+ effectiveTargetId,
661
+ itemDeviceId,
662
+ method,
663
+ item,
664
+ commonParams,
665
+ installContext
666
+ );
667
+ },
668
+ attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
669
+ getChainFingerprint,
670
+ effectiveTargetId,
572
671
  item,
573
- commonParams,
574
- installContext
575
- );
576
- },
577
- attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
578
- getChainFingerprint,
579
- connectId,
580
- item,
581
- chain,
582
- payload,
583
- chainFingerprints
584
- ),
585
- shouldAbortBundle: isTopLevelAllNetworkFailure,
586
- buildTopLevelFailure: (response) => {
587
- const code = response.payload?.code ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch;
588
- return (0, import_hwk_adapter_core2.failure)(
589
- code,
590
- response.payload?.error ?? "All-network get-address aborted",
591
- response.payload?.params
592
- );
593
- }
594
- });
595
- debugLog("[LedgerAdapter][RES]", {
596
- method: "allNetworkGetAddress",
597
- success: result.success,
598
- payload: result
599
- });
600
- return result;
672
+ chain,
673
+ payload,
674
+ chainFingerprints,
675
+ installContext,
676
+ cancelScope.signal
677
+ ),
678
+ shouldAbortBundle: isTopLevelAllNetworkFailure,
679
+ buildTopLevelFailure: (response) => {
680
+ const code = response.payload?.code ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch;
681
+ return (0, import_hwk_adapter_core2.failure)(
682
+ code,
683
+ response.payload?.error ?? "All-network get-address aborted",
684
+ response.payload?.params
685
+ );
686
+ }
687
+ });
688
+ debugLog("[LedgerAdapter][RES]", {
689
+ method: "allNetworkGetAddress",
690
+ success: result.success,
691
+ payload: result
692
+ });
693
+ return result;
694
+ } finally {
695
+ cancelScope.release();
696
+ releaseOperationRetention?.();
697
+ }
601
698
  };
602
699
  }
700
+ function buildCancelledFailure(signal) {
701
+ const reason = signal.reason;
702
+ const code = typeof reason?.code === "number" ? reason.code : import_hwk_adapter_core2.HardwareErrorCode.UserAborted;
703
+ const message = typeof reason?.message === "string" ? reason.message : "";
704
+ return (0, import_hwk_adapter_core2.failure)(code, message || "All-network get-address cancelled");
705
+ }
603
706
  function isTopLevelAllNetworkFailure(response) {
604
707
  if (response.success) {
605
708
  return false;
606
709
  }
607
710
  const code = response.payload?.code;
608
- return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch || code === import_hwk_adapter_core2.HardwareErrorCode.UserAborted || code === import_hwk_adapter_core2.HardwareErrorCode.UserRejected;
711
+ return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch || (0, import_hwk_adapter_core2.isUserRefusal)(code) || (0, import_hwk_adapter_core2.isConnectionLost)(code);
609
712
  }
610
713
  function getItemDeviceId(item) {
611
714
  const { deviceId } = item;
@@ -635,8 +738,18 @@ function normalizeLedgerAllNetworkItem(method, item) {
635
738
  const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
636
739
  return coin ? { ...item, coin } : item;
637
740
  }
638
- async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
639
- const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(getChainFingerprint, connectId, chain);
741
+ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
742
+ const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
743
+ if (!knownFingerprint && cancelSignal.aborted) {
744
+ const cancelled = buildCancelledFailure(cancelSignal);
745
+ return { ...item, success: false, payload: cancelled.payload };
746
+ }
747
+ const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
748
+ getChainFingerprint,
749
+ context.connection?.connectId ?? connectId,
750
+ chain,
751
+ context
752
+ );
640
753
  if (!fingerprint) {
641
754
  return buildFingerprintBootstrapFailure(item, chain);
642
755
  }
@@ -657,8 +770,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
657
770
  }
658
771
  };
659
772
  }
660
- async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
661
- const response = await getChainFingerprint(connectId, chain);
773
+ async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
774
+ const response = await getChainFingerprint(connectId, chain, context);
662
775
  return response.success ? response.payload : "";
663
776
  }
664
777
  function buildFingerprintBootstrapFailure(item, chain) {
@@ -751,6 +864,16 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
751
864
  function formatDeviceMismatchError(expected, actual) {
752
865
  return `Wrong device: expected ${expected}, got ${actual}`;
753
866
  }
867
+ var LOG_REDACTED_RESULT_KEYS = ["ufvk", "orchardFvk"];
868
+ function redactResultForLog(result) {
869
+ if (!result || typeof result !== "object") return result;
870
+ const keys = LOG_REDACTED_RESULT_KEYS.filter((key) => key in result);
871
+ if (!keys.length) return result;
872
+ return { ...result, ...Object.fromEntries(keys.map((key) => [key, "[redacted]"])) };
873
+ }
874
+ function isLostConnectionError(err) {
875
+ return isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err);
876
+ }
754
877
  var BTC_HIGH_INDEX_THRESHOLD = 100;
755
878
  function btcAccountIndexFromPath(path) {
756
879
  const segments = path.replace(/^m\//, "").split("/");
@@ -762,9 +885,31 @@ function btcAccountIndexFromPath(path) {
762
885
  var _LedgerAdapter = class _LedgerAdapter {
763
886
  constructor(connector, options) {
764
887
  this.vendor = "ledger";
888
+ // Cancel frees the DMK action and its intent-queue slot, but cannot retract a
889
+ // confirmation screen the device is already showing.
890
+ this.cancelCapability = "stops-waiting";
765
891
  this.emitter = new import_hwk_adapter_core3.TypedEventEmitter();
892
+ this._operations = new import_hwk_adapter_core3.OperationRegistry({
893
+ vendor: "ledger",
894
+ onEnded: (operation, reason) => {
895
+ const binding = this._pendingOperationBindings.get(operation.operationId);
896
+ if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
897
+ this._finishBleBinding("cancelled");
898
+ }
899
+ this._pendingOperationBindings.delete(operation.operationId);
900
+ this.emitter.emit(import_hwk_adapter_core3.SDK.OPERATION_ENDED, {
901
+ type: import_hwk_adapter_core3.SDK.OPERATION_ENDED,
902
+ payload: { operationId: operation.operationId, reason }
903
+ });
904
+ if (reason === "timeout") {
905
+ void this._releaseOperationConnection(operation);
906
+ }
907
+ }
908
+ });
766
909
  this._discoveredDevices = /* @__PURE__ */ new Map();
767
910
  this._sessions = /* @__PURE__ */ new Map();
911
+ this._pendingOperationBindings = /* @__PURE__ */ new Map();
912
+ this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
768
913
  this._uiRegistry = new import_hwk_adapter_core3.UiRequestRegistry();
769
914
  // BTC App rejects account index >= 100 unless display=true. Cached per
770
915
  // adapter instance: first 100+ path asks the user once via UI request,
@@ -778,10 +923,31 @@ var _LedgerAdapter = class _LedgerAdapter {
778
923
  this._deviceAuthenticityQueueTail = Promise.resolve();
779
924
  // Shared across concurrent callers — only `cancel()` aborts.
780
925
  this._doConnectAbortController = null;
926
+ this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
927
+ this._connectorIdleWaiters = /* @__PURE__ */ new Set();
928
+ this._resetPromise = null;
929
+ this._stateGeneration = 0;
930
+ this._connectorTeardownTail = Promise.resolve();
931
+ this._pendingConnectorTeardowns = 0;
932
+ this._activeOperationJobs = /* @__PURE__ */ new Set();
933
+ this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
781
934
  this._installProgressLastEmittedValue = -Infinity;
782
935
  this.allNetworkGetAddress = createAllNetworkGetAddress({
783
936
  callChain: this.callChain.bind(this),
784
- getChainFingerprint: (connectId, chain) => this.getChainFingerprint(connectId, "", chain)
937
+ getChainFingerprint: async (connectId, chain, context) => {
938
+ try {
939
+ const fingerprint = await this._computeChainFingerprint(
940
+ chain,
941
+ (method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
942
+ );
943
+ return (0, import_hwk_adapter_core3.success)(fingerprint);
944
+ } catch (error) {
945
+ return this.errorToFailure(error);
946
+ }
947
+ },
948
+ retainOperation: (operationId) => this._operations.retain(operationId),
949
+ errorToFailure: (error) => this.errorToFailure(error),
950
+ createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
785
951
  });
786
952
  // ---------------------------------------------------------------------------
787
953
  // Private helpers
@@ -791,7 +957,7 @@ var _LedgerAdapter = class _LedgerAdapter {
791
957
  *
792
958
  * - If a session already exists for the given connectId, reuse it.
793
959
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
794
- * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
960
+ * - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
795
961
  */
796
962
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
797
963
  this._connectingPromise = null;
@@ -808,6 +974,12 @@ var _LedgerAdapter = class _LedgerAdapter {
808
974
  });
809
975
  };
810
976
  this.deviceDisconnectHandler = (data) => {
977
+ const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
978
+ if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
979
+ this._pendingOperationDisconnects.add(activeOperation.operationId);
980
+ } else {
981
+ this._operations.endByConnectionKey(data.connectId, "disconnect");
982
+ }
811
983
  this._discoveredDevices.delete(data.connectId);
812
984
  this._sessions.delete(data.connectId);
813
985
  this.emitter.emit(import_hwk_adapter_core3.DEVICE.DISCONNECT, {
@@ -816,10 +988,8 @@ var _LedgerAdapter = class _LedgerAdapter {
816
988
  });
817
989
  };
818
990
  // Forward connector `ui-event` to the public hw.emitter so consumers only
819
- // need to subscribe in one place. For the AppInstallProgress variant we
820
- // re-key sessionId connectId via the live _sessions map; if no mapping
821
- // exists (race during teardown) we drop. All other variants pass through
822
- // unchanged.
991
+ // need to subscribe in one place. The AppInstallProgress variant re-keys
992
+ // sessionId to connectId via `_sessions`, and drops if no mapping exists.
823
993
  this.uiEventForwarder = (event) => {
824
994
  if (event.type === import_hwk_adapter_core3.EConnectorInteraction.AppInstallProgress) {
825
995
  let connectId;
@@ -864,11 +1034,28 @@ var _LedgerAdapter = class _LedgerAdapter {
864
1034
  this._jobQueue = new import_hwk_adapter_core3.DeviceJobQueue();
865
1035
  this.registerEventListeners();
866
1036
  }
1037
+ _finishBleBinding(status) {
1038
+ const selectionRequestId = this._bindingSelectionRequestId;
1039
+ this._bindingSelectionRequestId = void 0;
1040
+ if (selectionRequestId)
1041
+ this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS, {
1042
+ type: import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS,
1043
+ payload: { selectionRequestId, status }
1044
+ });
1045
+ }
1046
+ _isBleConnection() {
1047
+ return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
1048
+ }
867
1049
  // Transport
868
1050
  get activeTransport() {
869
- return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
1051
+ return this._isBleConnection() ? "ble" : "hid";
870
1052
  }
871
1053
  getAvailableTransports() {
1054
+ if (this.connector.availableTransports) {
1055
+ return this.connector.availableTransports.map(
1056
+ (transport) => transport === "ble" ? "ble" : "hid"
1057
+ );
1058
+ }
872
1059
  return this.activeTransport ? [this.activeTransport] : [];
873
1060
  }
874
1061
  // Connector is bound at construction; switching requires a new adapter.
@@ -885,21 +1072,39 @@ var _LedgerAdapter = class _LedgerAdapter {
885
1072
  * The next operation will re-discover and re-connect automatically.
886
1073
  */
887
1074
  resetState() {
1075
+ void this._resetStateAndDisconnectSessions();
1076
+ }
1077
+ _resetStateAndDisconnectSessions() {
1078
+ if (this._resetPromise) return this._resetPromise;
1079
+ const sessionIds = new Set(this._sessions.values());
1080
+ this._stateGeneration += 1;
1081
+ this._finishBleBinding("cancelled");
1082
+ this._operations.endAll("runtime-reset");
1083
+ this._doConnectAbortController?.abort();
888
1084
  this._discoveredDevices.clear();
889
1085
  this._sessions.clear();
1086
+ this._verifiedBleReconnectTargets.clear();
890
1087
  this._connectingPromise = null;
1088
+ this._doConnectAbortController = null;
891
1089
  this._uiRegistry.reset();
892
1090
  this._jobQueue.clear();
893
1091
  this._btcHighIndexConfirmedThisSession = false;
1092
+ const resetPromise = this._runConnectorTeardown(async () => {
1093
+ for (const sessionId of sessionIds) {
1094
+ await this.connector.disconnect(sessionId).catch(() => void 0);
1095
+ }
1096
+ });
1097
+ this._resetPromise = resetPromise;
1098
+ return resetPromise.finally(() => {
1099
+ if (this._resetPromise === resetPromise) {
1100
+ this._resetPromise = null;
1101
+ }
1102
+ });
894
1103
  }
895
1104
  async dispose() {
896
- this._uiRegistry.reset();
897
- this._jobQueue.clear();
1105
+ await this._resetStateAndDisconnectSessions();
898
1106
  this.unregisterEventListeners();
899
1107
  this.connector.reset();
900
- this._discoveredDevices.clear();
901
- this._sessions.clear();
902
- this._btcHighIndexConfirmedThisSession = false;
903
1108
  this.emitter.removeAllListeners();
904
1109
  }
905
1110
  uiResponse(response) {
@@ -909,17 +1114,32 @@ var _LedgerAdapter = class _LedgerAdapter {
909
1114
  // Device management
910
1115
  // ---------------------------------------------------------------------------
911
1116
  async searchDevices(options) {
1117
+ return this._searchDevices(options);
1118
+ }
1119
+ async _searchDevices(options, signal) {
912
1120
  debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
913
1121
  try {
914
1122
  if (options?.resetSession) {
915
- this._doConnectAbortController?.abort();
916
- this._sessions.clear();
917
- this._connectingPromise = null;
918
- this._doConnectAbortController = null;
919
- this._btcHighIndexConfirmedThisSession = false;
1123
+ await this._resetStateAndDisconnectSessions();
1124
+ } else {
1125
+ await this._connectorTeardownTail;
1126
+ }
1127
+ await this._ensureDevicePermission(void 0, void 0, signal);
1128
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1129
+ const stateGeneration = this._stateGeneration;
1130
+ const devices = await this.connector.searchDevices(
1131
+ options?.transportType ? {
1132
+ transportType: options.transportType,
1133
+ waitForAll: options.waitForAllTransports
1134
+ } : void 0
1135
+ );
1136
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1137
+ if (stateGeneration !== this._stateGeneration) {
1138
+ throw (0, import_hwk_adapter_core3.createHwkError)({
1139
+ code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
1140
+ message: "Ledger discovery was reset"
1141
+ });
920
1142
  }
921
- await this._ensureDevicePermission();
922
- const devices = await this.connector.searchDevices();
923
1143
  this._discoveredDevices.clear();
924
1144
  for (const d of devices) {
925
1145
  if (d.connectId) {
@@ -927,7 +1147,7 @@ var _LedgerAdapter = class _LedgerAdapter {
927
1147
  }
928
1148
  }
929
1149
  if (this._discoveredDevices.size === 0) {
930
- await this._ensureDevicePermission();
1150
+ await this._ensureDevicePermission(void 0, void 0, signal);
931
1151
  }
932
1152
  const result = Array.from(this._discoveredDevices.values());
933
1153
  debugLog("[LedgerAdapter][RES]", {
@@ -946,41 +1166,288 @@ var _LedgerAdapter = class _LedgerAdapter {
946
1166
  throw err;
947
1167
  }
948
1168
  }
1169
+ async searchDeviceTargets(options) {
1170
+ const devices = await this.searchDevices(options);
1171
+ return devices.map((device) => ({
1172
+ searchTargetId: device.connectId,
1173
+ searchTargetReusePolicy: (0, import_hwk_adapter_core3.resolveSearchTargetReusePolicy)(device),
1174
+ vendor: "ledger",
1175
+ connectionType: device.connectionType,
1176
+ kind: "physical",
1177
+ label: device.label,
1178
+ model: device.model,
1179
+ modelName: device.modelName,
1180
+ serialNumber: device.serialNumber
1181
+ }));
1182
+ }
1183
+ async listConnectionTargets(options) {
1184
+ const targets = await this.searchDeviceTargets(options);
1185
+ return targets.map(({ searchTargetId, ...target }) => ({
1186
+ ...target,
1187
+ targetId: searchTargetId
1188
+ }));
1189
+ }
949
1190
  // USB single-session invariant: evict all sessions, best-effort (see connectDevice).
950
- async _evictAllSessions() {
1191
+ async _evictAllSessions(preserveOperationId) {
1192
+ this._operations.endAll("explicit", preserveOperationId);
951
1193
  if (this._sessions.size === 0) return;
952
1194
  const stale = [...this._sessions.values()];
953
1195
  this._sessions.clear();
954
- for (const sid of stale) {
955
- try {
956
- await this.connector.disconnect(sid);
957
- } catch {
1196
+ await this._runConnectorTeardown(async () => {
1197
+ for (const sid of stale) {
1198
+ try {
1199
+ await this.connector.disconnect(sid);
1200
+ } catch {
1201
+ }
958
1202
  }
959
- }
1203
+ });
960
1204
  }
961
1205
  static _createDeviceBusyError(method) {
962
1206
  return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
963
1207
  code: import_hwk_adapter_core3.HardwareErrorCode.DeviceBusy
964
1208
  });
965
1209
  }
966
- async connectDevice(connectId) {
1210
+ async connectDevice(searchTargetId) {
1211
+ try {
1212
+ return await this._jobQueue.enqueue(
1213
+ searchTargetId || "__ledger_connect__",
1214
+ async (signal) => {
1215
+ const connected = await this._connectTarget(searchTargetId, void 0, signal);
1216
+ if (!connected.success) return connected;
1217
+ return this._createOperation(searchTargetId, connected.payload);
1218
+ },
1219
+ {
1220
+ label: "connectDevice",
1221
+ rejectIfBusy: true,
1222
+ busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
1223
+ }
1224
+ );
1225
+ } catch (error) {
1226
+ return this.errorToFailure(error);
1227
+ }
1228
+ }
1229
+ async bindBleDevice(params) {
1230
+ if (params.identity.vendor !== "ledger" || !params.identity.value) {
1231
+ return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "Ledger wallet identity is required");
1232
+ }
1233
+ const { chain, value: expectedFingerprint } = params.identity;
1234
+ try {
1235
+ return await this._jobQueue.enqueue(
1236
+ expectedFingerprint,
1237
+ async (signal) => {
1238
+ if (!this.getAvailableTransports().includes("ble")) {
1239
+ throw (0, import_hwk_adapter_core3.createHwkError)({
1240
+ code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
1241
+ message: "Ledger Bluetooth transport is not available"
1242
+ });
1243
+ }
1244
+ this._activeConnectionType = "ble";
1245
+ await this._ensureDevicePermission(void 0, void 0, signal);
1246
+ const attempt = {
1247
+ extra: params.extra,
1248
+ bindingReason: "manual-rebind"
1249
+ };
1250
+ try {
1251
+ for (; ; ) {
1252
+ const connectId = await this._connectFirstOrSelect(
1253
+ [],
1254
+ void 0,
1255
+ true,
1256
+ void 0,
1257
+ attempt,
1258
+ signal
1259
+ );
1260
+ const sessionId = this._sessions.get(connectId);
1261
+ if (!sessionId) {
1262
+ throw (0, import_hwk_adapter_core3.createHwkError)({
1263
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
1264
+ message: "Selected Ledger connection ended"
1265
+ });
1266
+ }
1267
+ let saved = false;
1268
+ try {
1269
+ const installContext = {
1270
+ connection: { connectId, sessionId }
1271
+ };
1272
+ const fingerprint = await this._computeChainFingerprint(
1273
+ chain,
1274
+ (method, callParams) => this._runConnectorCall(
1275
+ connectId,
1276
+ method,
1277
+ callParams,
1278
+ signal,
1279
+ void 0,
1280
+ void 0,
1281
+ { autoInstallApp: true },
1282
+ installContext
1283
+ )
1284
+ );
1285
+ if (fingerprint !== expectedFingerprint) {
1286
+ attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
1287
+ attempt.rejectedConnectIds.add(connectId);
1288
+ attempt.rejectedConnectId = connectId;
1289
+ } else {
1290
+ const persisted = await this._publishVerifiedBleBinding(
1291
+ connectId,
1292
+ chain,
1293
+ fingerprint,
1294
+ attempt,
1295
+ void 0,
1296
+ signal
1297
+ );
1298
+ if (!persisted) {
1299
+ throw (0, import_hwk_adapter_core3.createHwkError)({
1300
+ code: import_hwk_adapter_core3.HardwareErrorCode.UnknownError,
1301
+ message: "Bluetooth binding could not be saved",
1302
+ origin: "host"
1303
+ });
1304
+ }
1305
+ saved = true;
1306
+ return (0, import_hwk_adapter_core3.success)(connectId);
1307
+ }
1308
+ } finally {
1309
+ if (!saved && this._sessions.get(connectId) === sessionId) {
1310
+ this._sessions.delete(connectId);
1311
+ const teardown = this._runConnectorTeardown(
1312
+ () => this.connector.disconnect(sessionId)
1313
+ ).catch(() => void 0);
1314
+ if (!signal.aborted) await teardown;
1315
+ }
1316
+ }
1317
+ }
1318
+ } catch (error) {
1319
+ this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
1320
+ throw error;
1321
+ }
1322
+ },
1323
+ {
1324
+ label: "bindBleDevice",
1325
+ rejectIfBusy: true,
1326
+ busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
1327
+ }
1328
+ );
1329
+ } catch (error) {
1330
+ return this.errorToFailure(error);
1331
+ }
1332
+ }
1333
+ async acquireOperation(connectId, context) {
1334
+ try {
1335
+ return await this._jobQueue.enqueue(
1336
+ connectId || "__ledger_acquire__",
1337
+ async (signal) => {
1338
+ const transport = this._isBleConnection() ? "ble" : "usb";
1339
+ const hint = context.knownConnections?.find(
1340
+ (connection) => connection.transport === transport
1341
+ );
1342
+ const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
1343
+ await this._ensureDevicePermission(target, void 0, signal);
1344
+ const attempt = {
1345
+ ...context,
1346
+ extra: context.extra ? { ...context.extra } : void 0
1347
+ };
1348
+ try {
1349
+ const resolvedConnectId = await this.ensureConnected(
1350
+ target,
1351
+ signal,
1352
+ true,
1353
+ void 0,
1354
+ attempt
1355
+ );
1356
+ _LedgerAdapter._throwIfAborted(signal);
1357
+ const result = this._createOperation(connectId, resolvedConnectId);
1358
+ if (result.success && attempt.selectedConnection) {
1359
+ this._pendingOperationBindings.set(result.payload, attempt);
1360
+ }
1361
+ return result;
1362
+ } catch (error) {
1363
+ this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
1364
+ throw error;
1365
+ }
1366
+ },
1367
+ {
1368
+ label: "acquireOperation",
1369
+ rejectIfBusy: true,
1370
+ busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
1371
+ }
1372
+ );
1373
+ } catch (error) {
1374
+ return this.errorToFailure(error);
1375
+ }
1376
+ }
1377
+ _createOperation(searchTargetId, resolvedConnectId) {
1378
+ this._operations.endByConnectionKey(resolvedConnectId, "explicit");
1379
+ const sessionId = this._sessions.get(resolvedConnectId);
1380
+ if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
1381
+ const connectionType = this._isBleConnection() ? "ble" : "usb";
1382
+ const device = this._discoveredDevices.get(resolvedConnectId) ?? {
1383
+ vendor: "ledger",
1384
+ model: "unknown",
1385
+ firmwareVersion: "",
1386
+ deviceId: "",
1387
+ connectId: resolvedConnectId,
1388
+ connectionType
1389
+ };
1390
+ const operation = this._operations.create({
1391
+ searchTargetId,
1392
+ connectId: resolvedConnectId,
1393
+ device,
1394
+ connectionType,
1395
+ connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
1396
+ });
1397
+ return (0, import_hwk_adapter_core3.success)(operation.operationId);
1398
+ }
1399
+ async _connectTarget(connectId, preserveOperationId, signal) {
967
1400
  debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
968
1401
  try {
969
- if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
1402
+ this._assertConnectorReady("connectDevice");
1403
+ const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
1404
+ if (discoveredType === "usb" || discoveredType === "ble") {
1405
+ this._activeConnectionType = discoveredType;
1406
+ }
1407
+ if (this._isBleConnection() && !connectId) {
970
1408
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
971
1409
  code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
972
1410
  });
973
1411
  }
974
- if (!isLedgerBleConnectionType(this.connector.connectionType)) {
975
- await this._evictAllSessions();
1412
+ await this._ensureDevicePermission(connectId, void 0, signal);
1413
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1414
+ const isMultiTransport = (this.connector.availableTransports?.length ?? 0) > 1;
1415
+ if (!isMultiTransport && this._isBleConnection()) {
1416
+ const previousSessionId = this._sessions.get(connectId);
1417
+ this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
1418
+ if (previousSessionId) {
1419
+ this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
1420
+ this._sessions.delete(connectId);
1421
+ await this.connector.disconnect(previousSessionId).catch(() => void 0);
1422
+ }
1423
+ } else {
1424
+ await this._evictAllSessions(preserveOperationId);
1425
+ }
1426
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1427
+ const stateGeneration = this._stateGeneration;
1428
+ const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
1429
+ let session;
1430
+ this._connectingConnectId = connectId;
1431
+ try {
1432
+ session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
1433
+ transportType: this._activeConnectionType ?? "usb"
1434
+ }) : await this.connector.connect(connectId);
1435
+ if (signal?.aborted || stateGeneration !== this._stateGeneration) {
1436
+ await this.connector.disconnect(session.sessionId).catch(() => void 0);
1437
+ throw Object.assign(new Error("Ledger connection aborted"), {
1438
+ code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted
1439
+ });
1440
+ }
1441
+ } finally {
1442
+ if (this._connectingConnectId === connectId) this._connectingConnectId = void 0;
1443
+ releaseOperation();
976
1444
  }
977
- await this._ensureDevicePermission(connectId);
978
- const session = await this.connector.connect(connectId);
979
- this._sessions.set(connectId, session.sessionId);
1445
+ const resolvedConnectId = session.deviceInfo?.connectId || connectId;
1446
+ this._sessions.set(resolvedConnectId, session.sessionId);
980
1447
  if (session.deviceInfo) {
981
- this._discoveredDevices.set(connectId, session.deviceInfo);
1448
+ this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
982
1449
  }
983
- const result = (0, import_hwk_adapter_core3.success)(connectId);
1450
+ const result = (0, import_hwk_adapter_core3.success)(resolvedConnectId);
984
1451
  debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
985
1452
  return result;
986
1453
  } catch (err) {
@@ -993,30 +1460,60 @@ var _LedgerAdapter = class _LedgerAdapter {
993
1460
  return failureResult;
994
1461
  }
995
1462
  }
996
- async disconnectDevice(connectId) {
1463
+ async releaseOperation(operationId) {
1464
+ const operation = this._operations.find(operationId);
1465
+ if (!operation) {
1466
+ this._operations.resolve(operationId);
1467
+ return;
1468
+ }
1469
+ const endedOperation = this._operations.end(operationId, "explicit");
1470
+ if (!endedOperation) return;
1471
+ const { connectId } = operation;
997
1472
  debugLog("[LedgerAdapter][REQ]", {
998
- method: "disconnectDevice",
1473
+ method: "releaseOperation",
999
1474
  connectId,
1000
1475
  params: { connectId }
1001
1476
  });
1002
1477
  try {
1003
- const sessionId = this._sessions.get(connectId);
1004
- if (sessionId) {
1005
- await this.connector.disconnect(sessionId);
1006
- this._sessions.delete(connectId);
1007
- }
1008
- debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
1478
+ await this._releaseOperationConnection(endedOperation);
1479
+ debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
1009
1480
  } catch (err) {
1010
1481
  const e = err;
1011
1482
  debugLog("[LedgerAdapter][RES]", {
1012
- method: "disconnectDevice",
1483
+ method: "releaseOperation",
1013
1484
  success: false,
1014
1485
  error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
1015
1486
  });
1016
1487
  throw err;
1017
1488
  }
1018
1489
  }
1019
- async getDeviceInfo(connectId, deviceId) {
1490
+ async _releaseOperationConnection(operation) {
1491
+ const sessionIds = /* @__PURE__ */ new Set();
1492
+ for (const [connectId, sessionId] of this._sessions) {
1493
+ if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
1494
+ this._sessions.delete(connectId);
1495
+ sessionIds.add(sessionId);
1496
+ }
1497
+ }
1498
+ for (const sessionId of sessionIds) {
1499
+ await this._runConnectorTeardown(
1500
+ () => this.connector.disconnect(sessionId).catch(() => void 0)
1501
+ );
1502
+ }
1503
+ }
1504
+ async _releaseLostOperationConnection(operationId) {
1505
+ const endedOperation = this._operations.end(operationId, "disconnect");
1506
+ if (!endedOperation) return;
1507
+ this._discoveredDevices.delete(endedOperation.connectId);
1508
+ await this._releaseOperationConnection(endedOperation);
1509
+ }
1510
+ async getDeviceInfo(connectIdOrOperationId, deviceId) {
1511
+ let connectId;
1512
+ try {
1513
+ connectId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
1514
+ } catch (error) {
1515
+ return this.errorToFailure(error);
1516
+ }
1020
1517
  debugLog("[LedgerAdapter][REQ]", {
1021
1518
  method: "getDeviceInfo",
1022
1519
  connectId,
@@ -1051,12 +1548,9 @@ var _LedgerAdapter = class _LedgerAdapter {
1051
1548
  success: false,
1052
1549
  error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
1053
1550
  });
1054
- throw err;
1551
+ return this.errorToFailure(err);
1055
1552
  }
1056
1553
  }
1057
- getSupportedChains() {
1058
- return ["evm", "btc", "sol", "tron"];
1059
- }
1060
1554
  // ---------------------------------------------------------------------------
1061
1555
  // Chain call helper
1062
1556
  // ---------------------------------------------------------------------------
@@ -1091,13 +1585,23 @@ var _LedgerAdapter = class _LedgerAdapter {
1091
1585
  if (params && typeof params === "object") {
1092
1586
  const {
1093
1587
  autoInstallApp,
1588
+ operationId,
1094
1589
  passphraseState: _passphraseState,
1095
1590
  useEmptyPassphrase: _useEmptyPassphrase,
1591
+ knownConnections,
1592
+ extra,
1593
+ allowDeviceSelection,
1594
+ supportedTransports,
1096
1595
  ...rest
1097
1596
  } = params;
1098
1597
  return {
1099
1598
  commonParams: {
1100
- autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
1599
+ autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
1600
+ operationId: typeof operationId === "string" ? operationId : void 0,
1601
+ knownConnections,
1602
+ extra,
1603
+ allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0,
1604
+ supportedTransports
1101
1605
  },
1102
1606
  rest
1103
1607
  };
@@ -1273,9 +1777,28 @@ var _LedgerAdapter = class _LedgerAdapter {
1273
1777
  );
1274
1778
  }
1275
1779
  // ---------------------------------------------------------------------------
1276
- // App management OS-level Ledger app install / list. Bypasses fingerprint
1277
- // and chain-handler dispatch; installApp progress is forwarded to the adapter
1278
- // emitter via 'ui-event' AppInstallProgress events.
1780
+ // Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
1781
+ // ---------------------------------------------------------------------------
1782
+ zcashGetFullViewingKey(connectId, deviceId, params) {
1783
+ return this.callChainWithMergedParams(
1784
+ connectId,
1785
+ deviceId,
1786
+ "zcash",
1787
+ "zcashGetFullViewingKey",
1788
+ params
1789
+ );
1790
+ }
1791
+ zcashGetShieldedAddress(connectId, deviceId, params) {
1792
+ return this.callChainWithMergedParams(
1793
+ connectId,
1794
+ deviceId,
1795
+ "zcash",
1796
+ "zcashGetShieldedAddress",
1797
+ params
1798
+ );
1799
+ }
1800
+ // ---------------------------------------------------------------------------
1801
+ // App management: OS-level app install/list, no fingerprint or chain dispatch.
1279
1802
  // ---------------------------------------------------------------------------
1280
1803
  async installApp(connectId, appName) {
1281
1804
  try {
@@ -1361,7 +1884,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1361
1884
  );
1362
1885
  }
1363
1886
  await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
1364
- this.resetState();
1887
+ await this._resetStateAndDisconnectSessions();
1365
1888
  }
1366
1889
  const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
1367
1890
  if (!result.isGenuine) {
@@ -1387,10 +1910,10 @@ var _LedgerAdapter = class _LedgerAdapter {
1387
1910
  if (relayUrl) {
1388
1911
  try {
1389
1912
  await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
1390
- this.resetState();
1913
+ await this._resetStateAndDisconnectSessions();
1391
1914
  } catch {
1392
1915
  this.connector.reset();
1393
- this.resetState();
1916
+ await this._resetStateAndDisconnectSessions();
1394
1917
  }
1395
1918
  }
1396
1919
  }
@@ -1402,6 +1925,20 @@ var _LedgerAdapter = class _LedgerAdapter {
1402
1925
  this.emitter.off(event, listener);
1403
1926
  }
1404
1927
  cancel(connectId) {
1928
+ const namedOperationIsLive = (id) => {
1929
+ try {
1930
+ this._operations.resolve(id);
1931
+ return true;
1932
+ } catch {
1933
+ return false;
1934
+ }
1935
+ };
1936
+ if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) && !namedOperationIsLive(connectId)) {
1937
+ debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
1938
+ connectId
1939
+ });
1940
+ return;
1941
+ }
1405
1942
  const userAbortReason = Object.assign(new Error("User aborted operation"), {
1406
1943
  code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
1407
1944
  _tag: ERROR_TAG.UserAborted
@@ -1412,14 +1949,50 @@ var _LedgerAdapter = class _LedgerAdapter {
1412
1949
  this._lastCancelReason = void 0;
1413
1950
  }
1414
1951
  }, 2e3);
1415
- this._uiRegistry.cancel();
1416
- if (connectId) {
1417
- this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
1952
+ const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
1953
+ let operationId;
1954
+ if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId)) {
1955
+ operationId = connectId;
1956
+ } else if (!connectId && (0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) {
1957
+ operationId = activeJobId;
1958
+ }
1959
+ let resolvedConnectId = connectId;
1960
+ if (operationId) {
1961
+ try {
1962
+ resolvedConnectId = this._operations.resolve(operationId).connectId;
1963
+ } catch {
1964
+ resolvedConnectId = void 0;
1965
+ }
1966
+ }
1967
+ const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
1968
+ const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
1969
+ if (!connectId) {
1970
+ this._uiRegistry.cancel();
1971
+ } else if (pendingOperationId) {
1972
+ this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
1973
+ }
1974
+ this._finishBleBinding("cancelled");
1975
+ if (!connectId) this._pendingOperationBindings.clear();
1976
+ else if (pendingOperationId) {
1977
+ this._pendingOperationBindings.delete(pendingOperationId);
1978
+ }
1979
+ const queueKeys = /* @__PURE__ */ new Set();
1980
+ if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
1981
+ if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
1982
+ if (queueKeys.size) {
1983
+ let cancelledAnyJob = false;
1984
+ for (const key of queueKeys) {
1985
+ cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
1986
+ }
1987
+ debugLog("[LedgerAdapter] cancel routed to queue keys", {
1988
+ queueKeys: [...queueKeys],
1989
+ cancelledAnyJob
1990
+ });
1418
1991
  } else {
1419
1992
  this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
1420
1993
  }
1421
- if (connectId) {
1422
- const sessionId = this._sessions.get(connectId) ?? connectId;
1994
+ if (resolvedConnectId) {
1995
+ const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
1423
1996
  void this.connector.cancel(sessionId);
1424
1997
  } else {
1425
1998
  for (const sid of this._sessions.values()) void this.connector.cancel(sid);
@@ -1427,45 +2000,105 @@ var _LedgerAdapter = class _LedgerAdapter {
1427
2000
  if (this._connectingPromise) {
1428
2001
  this._doConnectAbortController?.abort(userAbortReason);
1429
2002
  }
2003
+ const connectingConnectId = this._connectingConnectId;
2004
+ if (connectingConnectId) void this.connector.cancel(connectingConnectId);
1430
2005
  }
1431
2006
  // ---------------------------------------------------------------------------
1432
2007
  // Chain fingerprint
1433
2008
  // ---------------------------------------------------------------------------
2009
+ /** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
1434
2010
  async getChainFingerprint(connectId, deviceId, chain) {
1435
2011
  try {
1436
2012
  const fingerprint = await this._computeChainFingerprint(
1437
2013
  chain,
1438
2014
  (method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
1439
2015
  );
2016
+ if (deviceId) {
2017
+ if (fingerprint !== deviceId) {
2018
+ return (0, import_hwk_adapter_core3.failure)(
2019
+ import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
2020
+ formatDeviceMismatchError(deviceId, fingerprint)
2021
+ );
2022
+ }
2023
+ if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId)) {
2024
+ const operation = this._operations.resolve(connectId);
2025
+ await this._publishVerifiedBleBinding(
2026
+ operation.connectId,
2027
+ chain,
2028
+ fingerprint,
2029
+ void 0,
2030
+ connectId
2031
+ );
2032
+ }
2033
+ }
1440
2034
  return (0, import_hwk_adapter_core3.success)(fingerprint);
1441
2035
  } catch (err) {
1442
2036
  debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
1443
2037
  return this.errorToFailure(err);
1444
2038
  }
1445
2039
  }
1446
- /**
1447
- * Verify fingerprint using an existing sessionId directly.
1448
- * Safe to call inside connectorCall without causing queue deadlock.
1449
- */
1450
- async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
1451
- if (!deviceId) return { success: true };
2040
+ /** Discovery may select a BLE target before a later call verifies its wallet. */
2041
+ async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, operationId, signal) {
2042
+ const binding = operationId ? this._pendingOperationBindings.get(operationId) : attempt;
2043
+ if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) {
2044
+ return false;
2045
+ }
1452
2046
  try {
1453
- const fingerprint = await this._computeChainFingerprint(
1454
- chain,
1455
- (method, params) => this._callConnector(sessionId, method, params)
2047
+ const outcome = await (0, import_hwk_adapter_core3.requestSaveDeviceBinding)(
2048
+ this.emitter,
2049
+ this._uiRegistry,
2050
+ {
2051
+ selectionRequestId: binding.selectedConnection.requestId,
2052
+ connection: { transport: "ble", connectId },
2053
+ identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
2054
+ extra: binding.extra,
2055
+ operationId
2056
+ },
2057
+ signal
1456
2058
  );
1457
- if (fingerprint === deviceId) {
1458
- return { success: true };
2059
+ if (operationId) this._operations.resolve(operationId);
2060
+ if (!outcome.saved) {
2061
+ debugLog("[LedgerAdapter] BLE binding not persisted by host", {
2062
+ connectId,
2063
+ reason: outcome.reason
2064
+ });
1459
2065
  }
1460
- return { success: false, expected: deviceId, actual: fingerprint };
1461
- } catch (err) {
1462
- const mapped = mapLedgerError(err);
1463
- if (mapped.code === import_hwk_adapter_core3.HardwareErrorCode.WrongApp || mapped.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceLocked) {
1464
- return { success: true };
2066
+ return outcome.saved;
2067
+ } catch (error) {
2068
+ if (operationId) {
2069
+ this._pendingOperationBindings.delete(operationId);
2070
+ this._operations.end(operationId, "explicit");
2071
+ }
2072
+ const sessionId = this._sessions.get(connectId);
2073
+ this._sessions.delete(connectId);
2074
+ if (sessionId) {
2075
+ const teardown = this._runConnectorTeardown(
2076
+ () => this.connector.disconnect(sessionId)
2077
+ ).catch(() => void 0);
2078
+ if (!signal?.aborted) await teardown;
2079
+ }
2080
+ throw error;
2081
+ } finally {
2082
+ if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
2083
+ this._bindingSelectionRequestId = void 0;
2084
+ }
2085
+ if (operationId) {
2086
+ this._pendingOperationBindings.delete(operationId);
1465
2087
  }
1466
- throw err;
1467
2088
  }
1468
2089
  }
2090
+ /** Verify on the acquired session without re-entering the job queue. */
2091
+ async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
2092
+ if (!deviceId) return { success: true };
2093
+ const fingerprint = await this._computeChainFingerprint(
2094
+ chain,
2095
+ (method, params) => this._callConnector(sessionId, method, params)
2096
+ );
2097
+ if (fingerprint === deviceId) {
2098
+ return { success: true };
2099
+ }
2100
+ return { success: false, expected: deviceId, actual: fingerprint };
2101
+ }
1469
2102
  /**
1470
2103
  * Compute the chain fingerprint via a caller-supplied call strategy.
1471
2104
  *
@@ -1493,11 +2126,23 @@ var _LedgerAdapter = class _LedgerAdapter {
1493
2126
  address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
1494
2127
  } else if (chain === "tron") {
1495
2128
  address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
2129
+ } else if (chain === "zcash") {
2130
+ address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
1496
2131
  } else {
1497
2132
  throw new Error(`Unsupported chain for fingerprint: ${chain}`);
1498
2133
  }
1499
2134
  return (0, import_hwk_adapter_core3.deriveDeviceFingerprint)(address);
1500
2135
  }
2136
+ /**
2137
+ * Operation owning the running job, so a mid-call UI request can name it.
2138
+ * Undefined at cold start when no operation owns the job.
2139
+ */
2140
+ _activeOperationId() {
2141
+ const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
2142
+ if (!activeJobId) return void 0;
2143
+ if ((0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) return activeJobId;
2144
+ return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
2145
+ }
1501
2146
  // Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
1502
2147
  // The user must press Confirm after unlocking, which triggers a search retry.
1503
2148
  // If `signal` is provided, an abort cancels the pending UI request so the
@@ -1507,15 +2152,18 @@ var _LedgerAdapter = class _LedgerAdapter {
1507
2152
  if (signal?.aborted) {
1508
2153
  _LedgerAdapter._throwIfAborted(signal);
1509
2154
  }
2155
+ const operationId = this._activeOperationId();
1510
2156
  const waitPromise = this._uiRegistry.wait(
1511
- import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT
2157
+ import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
2158
+ { operationId }
1512
2159
  );
1513
2160
  this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
1514
2161
  type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
1515
2162
  payload: {
1516
2163
  vendor: "ledger",
1517
2164
  reason: "device-not-found",
1518
- message: "Please connect and unlock your Ledger device"
2165
+ message: "Please connect and unlock your Ledger device",
2166
+ operationId
1519
2167
  }
1520
2168
  });
1521
2169
  let payload;
@@ -1576,15 +2224,18 @@ var _LedgerAdapter = class _LedgerAdapter {
1576
2224
  return { ...params, showOnDevice: true };
1577
2225
  }
1578
2226
  async _waitForBtcHighIndexConfirm(path, accountIndex) {
2227
+ const operationId = this._activeOperationId();
1579
2228
  const waitPromise = this._uiRegistry.wait(
1580
- import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
2229
+ import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
2230
+ { operationId }
1581
2231
  );
1582
2232
  this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
1583
2233
  type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
1584
2234
  payload: {
1585
2235
  vendor: "ledger",
1586
2236
  path,
1587
- accountIndex
2237
+ accountIndex,
2238
+ operationId
1588
2239
  }
1589
2240
  });
1590
2241
  try {
@@ -1601,12 +2252,14 @@ var _LedgerAdapter = class _LedgerAdapter {
1601
2252
  // Ask the user whether to install a missing app (autoInstallApp flow).
1602
2253
  // Same register-then-emit ordering as the BTC high-index gate.
1603
2254
  async _waitForInstallAppConfirm(appName) {
2255
+ const operationId = this._activeOperationId();
1604
2256
  const waitPromise = this._uiRegistry.wait(
1605
- import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP
2257
+ import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
2258
+ { operationId }
1606
2259
  );
1607
2260
  this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP, {
1608
2261
  type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
1609
- payload: { vendor: "ledger", appName }
2262
+ payload: { vendor: "ledger", appName, operationId }
1610
2263
  });
1611
2264
  try {
1612
2265
  const payload = await waitPromise;
@@ -1622,16 +2275,17 @@ var _LedgerAdapter = class _LedgerAdapter {
1622
2275
  // Layer 1 entry. Caller signal only races the outer awaiter; the shared
1623
2276
  // `_doConnect` runs under its own internal controller so caller A's cancel
1624
2277
  // doesn't kill caller B's await.
1625
- async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
2278
+ async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
1626
2279
  if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
1627
- if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
2280
+ this._assertConnectorReady("connectDevice");
2281
+ if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
1628
2282
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1629
2283
  code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
1630
2284
  });
1631
2285
  }
1632
2286
  if (connectId && this._sessions.has(connectId)) return connectId;
1633
2287
  if (!connectId && this._sessions.size > 0) {
1634
- if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
2288
+ if (!this._isBleConnection() && this._sessions.size > 1) {
1635
2289
  throw Object.assign(
1636
2290
  new Error(
1637
2291
  "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
@@ -1641,15 +2295,24 @@ var _LedgerAdapter = class _LedgerAdapter {
1641
2295
  }
1642
2296
  return this._sessions.keys().next().value;
1643
2297
  }
1644
- if (!this._connectingPromise) {
1645
- this._doConnectAbortController = new AbortController();
1646
- const innerSignal = this._doConnectAbortController.signal;
2298
+ if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
2299
+ const controller = new AbortController();
2300
+ this._doConnectAbortController = controller;
2301
+ const innerSignal = controller.signal;
1647
2302
  this._connectingPromise = (async () => {
1648
2303
  try {
1649
- return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback);
2304
+ return await this._doConnect(
2305
+ innerSignal,
2306
+ connectId,
2307
+ allowUsbEphemeralFallback,
2308
+ preserveOperationId,
2309
+ context
2310
+ );
1650
2311
  } finally {
1651
- this._connectingPromise = null;
1652
- this._doConnectAbortController = null;
2312
+ if (this._doConnectAbortController === controller) {
2313
+ this._connectingPromise = null;
2314
+ this._doConnectAbortController = null;
2315
+ }
1653
2316
  }
1654
2317
  })();
1655
2318
  }
@@ -1658,22 +2321,57 @@ var _LedgerAdapter = class _LedgerAdapter {
1658
2321
  // Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
1659
2322
  // Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
1660
2323
  // throw DeviceNotFound so the user is kicked out of the loop.
1661
- async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
1662
- if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
1663
- try {
1664
- return await this._connectDeviceOrThrow(targetConnectId);
1665
- } catch (err) {
1666
- if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
1667
- throw err;
1668
- }
1669
- this._discoveredDevices.delete(targetConnectId);
1670
- if (isDeviceDisconnectedError(err)) {
1671
- try {
1672
- this.connector.reset?.();
1673
- } catch {
1674
- }
1675
- }
2324
+ async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
2325
+ _LedgerAdapter._throwIfAborted(internalSignal);
2326
+ if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
2327
+ this._activeConnectionType = "usb";
2328
+ const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
2329
+ _LedgerAdapter._throwIfAborted(internalSignal);
2330
+ if (usbDevices.length > 0) {
2331
+ const knownUsb = context?.knownConnections?.find(
2332
+ (connection) => connection.transport === "usb"
2333
+ );
2334
+ const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
2335
+ return this._connectFirstOrSelect(
2336
+ usbDevices,
2337
+ usbTarget,
2338
+ allowUsbEphemeralFallback,
2339
+ preserveOperationId,
2340
+ context,
2341
+ internalSignal
2342
+ );
2343
+ }
2344
+ if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
2345
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2346
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
2347
+ message: "This Ledger model has no Bluetooth transport; connect it over USB"
2348
+ });
2349
+ }
2350
+ this._activeConnectionType = "ble";
2351
+ const knownBle = context?.knownConnections?.find(
2352
+ (connection) => connection.transport === "ble"
2353
+ );
2354
+ if (knownBle?.transport === "ble") {
2355
+ return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
1676
2356
  }
2357
+ if (targetConnectId && context?.knownConnections === void 0) {
2358
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2359
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
2360
+ message: "Ledger connection metadata is required before starting Bluetooth binding"
2361
+ });
2362
+ }
2363
+ const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
2364
+ return this._connectFirstOrSelect(
2365
+ bleDevices,
2366
+ void 0,
2367
+ allowUsbEphemeralFallback,
2368
+ preserveOperationId,
2369
+ context,
2370
+ internalSignal
2371
+ );
2372
+ }
2373
+ if (this._isBleConnection() && targetConnectId) {
2374
+ return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
1677
2375
  }
1678
2376
  let confirms = 0;
1679
2377
  while (!internalSignal.aborted) {
@@ -1681,22 +2379,28 @@ var _LedgerAdapter = class _LedgerAdapter {
1681
2379
  type: import_hwk_adapter_core3.EConnectorInteraction.Searching,
1682
2380
  payload: { sessionId: "" }
1683
2381
  });
1684
- let devices = await this.searchDevices();
2382
+ let devices = await this._searchDevices(void 0, internalSignal);
2383
+ _LedgerAdapter._throwIfAborted(internalSignal);
1685
2384
  if (devices.length === 0) {
1686
2385
  for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
1687
2386
  await new Promise((resolve) => {
1688
2387
  setTimeout(resolve, import_hwk_adapter_core3.DEVICE_CONNECT_RETRY_DELAY_MS);
1689
2388
  });
1690
- devices = await this.searchDevices();
2389
+ _LedgerAdapter._throwIfAborted(internalSignal);
2390
+ devices = await this._searchDevices(void 0, internalSignal);
2391
+ _LedgerAdapter._throwIfAborted(internalSignal);
1691
2392
  if (devices.length > 0) break;
1692
2393
  }
1693
2394
  }
1694
- if (devices.length > 0) {
2395
+ if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
1695
2396
  try {
1696
2397
  return await this._connectFirstOrSelect(
1697
2398
  devices,
1698
2399
  targetConnectId,
1699
- allowUsbEphemeralFallback
2400
+ allowUsbEphemeralFallback,
2401
+ preserveOperationId,
2402
+ context,
2403
+ internalSignal
1700
2404
  );
1701
2405
  } catch (err) {
1702
2406
  if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
@@ -1725,45 +2429,133 @@ var _LedgerAdapter = class _LedgerAdapter {
1725
2429
  _LedgerAdapter._throwIfAborted(internalSignal);
1726
2430
  throw new Error("_doConnect aborted");
1727
2431
  }
1728
- async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
2432
+ async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
2433
+ _LedgerAdapter._throwIfAborted(signal);
1729
2434
  if (targetConnectId) {
1730
2435
  const target = devices.find(
1731
2436
  (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
1732
2437
  );
1733
2438
  if (target) {
1734
- return this._connectDeviceOrThrow(target.connectId);
2439
+ return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
1735
2440
  }
1736
- if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1 && allowUsbEphemeralFallback) {
2441
+ if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
1737
2442
  debugLog(
1738
2443
  `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
1739
2444
  );
1740
- return this._connectDeviceOrThrow(devices[0].connectId);
2445
+ return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
1741
2446
  }
1742
- const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1743
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
1744
- });
1745
- if (isLedgerBleConnectionType(this.connector.connectionType)) {
1746
- err._tag = ERROR_TAG.DeviceNotAdvertising;
2447
+ if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
2448
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
2449
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
2450
+ });
2451
+ if (this._isBleConnection()) {
2452
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
2453
+ }
2454
+ throw err;
1747
2455
  }
1748
- throw err;
1749
2456
  }
1750
- if (isLedgerBleConnectionType(this.connector.connectionType)) {
2457
+ const requiresBleSelection = this._isBleConnection();
2458
+ if (requiresBleSelection && !allowUsbEphemeralFallback) {
1751
2459
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1752
2460
  code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
1753
2461
  });
1754
2462
  }
1755
- if (devices.length > 1) {
1756
- throw createMultipleUsbLedgerDevicesError();
2463
+ if (requiresBleSelection && context?.allowDeviceSelection !== false) {
2464
+ const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
2465
+ if (context) context.bindingSessionId = bindingSessionId;
2466
+ const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
2467
+ const knownUsb = context?.knownConnections?.find(
2468
+ (connection) => connection.transport === "usb"
2469
+ );
2470
+ const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
2471
+ const { device, requestId } = await (0, import_hwk_adapter_core3.requestBleDeviceSelection)({
2472
+ emitter: this.emitter,
2473
+ registry: this._uiRegistry,
2474
+ signal,
2475
+ allowUsbFallback,
2476
+ scan: async () => {
2477
+ if (allowUsbFallback) {
2478
+ const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
2479
+ const candidate = usbDevices.find(
2480
+ (device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
2481
+ ) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
2482
+ if (candidate) return [candidate];
2483
+ }
2484
+ return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
2485
+ },
2486
+ request: {
2487
+ devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
2488
+ bindingSessionId,
2489
+ rejectedConnectId: context?.rejectedConnectId,
2490
+ context: {
2491
+ kind: "bind-connection",
2492
+ transport: "ble",
2493
+ reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
2494
+ },
2495
+ extra: context?.extra,
2496
+ operationId: preserveOperationId
2497
+ }
2498
+ });
2499
+ if (device.connectionType === "usb") {
2500
+ this._activeConnectionType = "usb";
2501
+ if (context) context.selectedConnection = void 0;
2502
+ if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
2503
+ this._bindingSelectionRequestId = requestId;
2504
+ this._finishBleBinding("cancelled");
2505
+ return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
2506
+ }
2507
+ if (context) context.selectedConnection = { connectId: device.connectId, requestId };
2508
+ this._bindingSelectionRequestId = requestId;
2509
+ return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
2510
+ }
2511
+ if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
2512
+ if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE)) {
2513
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2514
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
2515
+ message: "Select a Ledger device before continuing"
2516
+ });
2517
+ }
2518
+ const requestId = this._uiRegistry.createRequestId();
2519
+ const operationId = this._activeOperationId();
2520
+ const waitPromise = this._uiRegistry.wait(
2521
+ import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
2522
+ { requestId, operationId }
2523
+ );
2524
+ this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE, {
2525
+ type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
2526
+ payload: {
2527
+ devices,
2528
+ requestId,
2529
+ operationId,
2530
+ context: requiresBleSelection ? {
2531
+ kind: "bind-connection",
2532
+ transport: "ble",
2533
+ reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
2534
+ } : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
2535
+ extra: context?.extra
2536
+ }
2537
+ });
2538
+ const { sdkConnectId } = await this._abortable(signal, waitPromise);
2539
+ _LedgerAdapter._throwIfAborted(signal);
2540
+ const selected = devices.find((device) => device.connectId === sdkConnectId);
2541
+ if (!selected) {
2542
+ throw Object.assign(new Error("Selected Ledger is no longer available"), {
2543
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
2544
+ });
2545
+ }
2546
+ if (context && requiresBleSelection)
2547
+ context.selectedConnection = { connectId: selected.connectId, requestId };
2548
+ return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
1757
2549
  }
1758
2550
  if (devices.length !== 1) {
1759
2551
  throw Object.assign(new Error("Ledger device not found."), {
1760
2552
  code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
1761
2553
  });
1762
2554
  }
1763
- return this._connectDeviceOrThrow(devices[0].connectId);
2555
+ return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
1764
2556
  }
1765
- async _connectDeviceOrThrow(chosenConnectId) {
1766
- const result = await this.connectDevice(chosenConnectId);
2557
+ async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
2558
+ const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
1767
2559
  if (!result.success) {
1768
2560
  const payload = result.payload;
1769
2561
  const rethrow = Object.assign(new Error(payload.error), {
@@ -1774,7 +2566,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1774
2566
  }
1775
2567
  throw rethrow;
1776
2568
  }
1777
- return chosenConnectId;
2569
+ return result.payload;
1778
2570
  }
1779
2571
  /**
1780
2572
  * Call the connector with automatic session resolution and disconnect retry.
@@ -1804,33 +2596,133 @@ var _LedgerAdapter = class _LedgerAdapter {
1804
2596
  * lives in one place.
1805
2597
  */
1806
2598
  async _callConnector(sessionId, method, params, signal) {
1807
- const promise = this.connector.call(sessionId, method, params);
2599
+ this._assertConnectorReady(method);
2600
+ if (signal?.aborted) throw this._abortReason(signal);
2601
+ const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
2602
+ let promise;
2603
+ try {
2604
+ promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
2605
+ } catch (error) {
2606
+ releaseOperation();
2607
+ throw error;
2608
+ }
1808
2609
  const result = signal ? await this._abortable(signal, promise) : await promise;
1809
2610
  return this._unwrapConnectorResult(result);
1810
2611
  }
2612
+ _assertConnectorReady(method) {
2613
+ if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
2614
+ throw _LedgerAdapter._createDeviceBusyError(method);
2615
+ }
2616
+ }
2617
+ _runConnectorTeardown(task) {
2618
+ const previous = this._connectorTeardownTail;
2619
+ let releaseTail = () => void 0;
2620
+ this._connectorTeardownTail = new Promise((resolve) => {
2621
+ releaseTail = resolve;
2622
+ });
2623
+ this._pendingConnectorTeardowns += 1;
2624
+ return (async () => {
2625
+ try {
2626
+ await previous;
2627
+ await this._waitForConnectorOperationsToDrain();
2628
+ await task();
2629
+ } finally {
2630
+ this._pendingConnectorTeardowns -= 1;
2631
+ releaseTail();
2632
+ }
2633
+ })();
2634
+ }
2635
+ _waitForConnectorOperationsToDrain() {
2636
+ if (this._unsettledConnectorOperations.size === 0) {
2637
+ return Promise.resolve();
2638
+ }
2639
+ return new Promise((resolve) => {
2640
+ this._connectorIdleWaiters.add(resolve);
2641
+ });
2642
+ }
2643
+ _retainConnectorOperation(key) {
2644
+ this._unsettledConnectorOperations.set(
2645
+ key,
2646
+ (this._unsettledConnectorOperations.get(key) ?? 0) + 1
2647
+ );
2648
+ let released = false;
2649
+ return () => {
2650
+ if (released) return;
2651
+ released = true;
2652
+ const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
2653
+ if (remaining > 0) {
2654
+ this._unsettledConnectorOperations.set(key, remaining);
2655
+ } else {
2656
+ this._unsettledConnectorOperations.delete(key);
2657
+ }
2658
+ if (this._unsettledConnectorOperations.size === 0) {
2659
+ for (const resolve of this._connectorIdleWaiters) resolve();
2660
+ this._connectorIdleWaiters.clear();
2661
+ }
2662
+ };
2663
+ }
1811
2664
  async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
1812
- debugLog("[LedgerAdapter][REQ]", { method, connectId: connectId || "(empty)", params });
1813
- const queueKey = connectId || "__ledger_default__";
2665
+ const positionalOperationId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) ? connectId : void 0;
2666
+ if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
2667
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2668
+ code: import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
2669
+ message: "Conflicting Ledger operation ids",
2670
+ params: {
2671
+ positionalOperationId,
2672
+ commonOperationId: commonParams.operationId
2673
+ }
2674
+ });
2675
+ }
2676
+ const operationId = commonParams?.operationId ?? positionalOperationId;
2677
+ const operation = operationId ? this._operations.resolve(operationId) : void 0;
2678
+ const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
2679
+ const effectiveConnectId = operation?.connectId ?? connectId;
2680
+ debugLog("[LedgerAdapter][REQ]", {
2681
+ method,
2682
+ connectId: effectiveConnectId || "(empty)",
2683
+ params
2684
+ });
2685
+ const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
1814
2686
  try {
1815
2687
  const result = await this._jobQueue.enqueue(
1816
2688
  queueKey,
1817
- async (signal) => this._runConnectorCall(
1818
- connectId,
1819
- method,
1820
- params,
1821
- signal,
1822
- fingerprint,
1823
- permissionDeviceId,
1824
- commonParams,
1825
- installContext
1826
- ),
2689
+ async (signal) => {
2690
+ if (operationId) this._activeOperationJobs.add(operationId);
2691
+ try {
2692
+ return await this._runConnectorCall(
2693
+ effectiveConnectId,
2694
+ method,
2695
+ params,
2696
+ signal,
2697
+ fingerprint,
2698
+ permissionDeviceId,
2699
+ commonParams,
2700
+ installContext ?? {},
2701
+ operationId
2702
+ );
2703
+ } catch (error) {
2704
+ this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
2705
+ throw error;
2706
+ } finally {
2707
+ if (operationId) {
2708
+ this._activeOperationJobs.delete(operationId);
2709
+ if (this._pendingOperationDisconnects.delete(operationId)) {
2710
+ await this._releaseLostOperationConnection(operationId);
2711
+ }
2712
+ }
2713
+ }
2714
+ },
1827
2715
  {
1828
2716
  label: method,
1829
2717
  rejectIfBusy: true,
1830
2718
  busyError: _LedgerAdapter._createDeviceBusyError(method)
1831
2719
  }
1832
2720
  );
1833
- debugLog("[LedgerAdapter][RES]", { method, success: true, payload: result });
2721
+ debugLog("[LedgerAdapter][RES]", {
2722
+ method,
2723
+ success: true,
2724
+ payload: redactResultForLog(result)
2725
+ });
1834
2726
  return result;
1835
2727
  } catch (err) {
1836
2728
  const e = err;
@@ -1844,15 +2736,22 @@ var _LedgerAdapter = class _LedgerAdapter {
1844
2736
  }
1845
2737
  });
1846
2738
  throw err;
2739
+ } finally {
2740
+ releaseOperationRetention?.();
1847
2741
  }
1848
2742
  }
2743
+ /** Hermes/RN polyfills don't always populate signal.reason; fall back. */
2744
+ _abortReason(signal) {
2745
+ return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
2746
+ }
1849
2747
  /**
1850
2748
  * Race a promise against an abort signal. On abort, rejects with
1851
2749
  * signal.reason → instance _lastCancelReason → generic Error('Aborted').
1852
2750
  */
1853
2751
  _abortable(signal, promise) {
1854
- const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
2752
+ const getAbortReason = () => this._abortReason(signal);
1855
2753
  if (signal.aborted) {
2754
+ void promise.catch(() => void 0);
1856
2755
  return Promise.reject(getAbortReason());
1857
2756
  }
1858
2757
  return new Promise((resolve, reject) => {
@@ -1879,13 +2778,15 @@ var _LedgerAdapter = class _LedgerAdapter {
1879
2778
  }
1880
2779
  }
1881
2780
  /** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
1882
- async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
2781
+ async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
1883
2782
  _LedgerAdapter._throwIfAborted(signal);
1884
- await this._ensureDevicePermission(
1885
- connectId,
1886
- permissionDeviceId ?? fingerprint?.deviceId,
1887
- signal
1888
- );
2783
+ if (!operationId) {
2784
+ await this._ensureDevicePermission(
2785
+ connectId,
2786
+ permissionDeviceId ?? fingerprint?.deviceId,
2787
+ signal
2788
+ );
2789
+ }
1889
2790
  _LedgerAdapter._throwIfAborted(signal);
1890
2791
  let effectiveParams = params;
1891
2792
  if (method === "btcGetPublicKey") {
@@ -1899,36 +2800,135 @@ var _LedgerAdapter = class _LedgerAdapter {
1899
2800
  effectiveParams = gatedParams;
1900
2801
  }
1901
2802
  const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
1902
- const resolvedConnectId = await this.ensureConnected(
1903
- connectId,
2803
+ let businessCallStarted = false;
2804
+ const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
2805
+ const knownTransport = this._isBleConnection() ? "ble" : "usb";
2806
+ const hintedConnectId = commonParams?.knownConnections?.find(
2807
+ (connection) => connection.transport === knownTransport
2808
+ );
2809
+ const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
2810
+ const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
2811
+ const connectionAttempt = { ...commonParams };
2812
+ const bundleConnection = installContext?.connection;
2813
+ if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
2814
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2815
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
2816
+ message: "Ledger all-network connection ended"
2817
+ });
2818
+ }
2819
+ let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
2820
+ preferredConnectId,
1904
2821
  signal,
1905
- allowUsbEphemeralFallback
2822
+ allowUsbEphemeralFallback,
2823
+ void 0,
2824
+ connectionAttempt
1906
2825
  );
1907
- const sessionId = this._sessions.get(resolvedConnectId);
2826
+ let sessionId = this._sessions.get(resolvedConnectId);
2827
+ if (sessionId && installContext && !installContext.connection) {
2828
+ installContext.connection = { connectId: resolvedConnectId, sessionId };
2829
+ }
1908
2830
  if (!sessionId) {
2831
+ if (operationId) {
2832
+ this._operations.end(operationId, "disconnect");
2833
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2834
+ code: import_hwk_adapter_core3.HardwareErrorCode.OperationEnded,
2835
+ message: "Ledger operation connection is no longer active",
2836
+ params: { operationId, reason: "disconnect" }
2837
+ });
2838
+ }
1909
2839
  throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
1910
2840
  _tag: ERROR_TAG.DeviceSessionNotFound
1911
2841
  });
1912
2842
  }
1913
2843
  try {
1914
2844
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1915
- const fp = await this._abortable(
1916
- signal,
1917
- this._verifyDeviceFingerprintWithSession(
1918
- sessionId,
1919
- fingerprint.deviceId,
1920
- fingerprint.chain
1921
- )
2845
+ for (; ; ) {
2846
+ const fp = await this._abortable(
2847
+ signal,
2848
+ this._verifyDeviceFingerprintWithSession(
2849
+ sessionId,
2850
+ fingerprint.deviceId,
2851
+ fingerprint.chain
2852
+ )
2853
+ );
2854
+ if (fp.success) break;
2855
+ const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
2856
+ if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
2857
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2858
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
2859
+ });
2860
+ }
2861
+ binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
2862
+ binding.rejectedConnectIds.add(resolvedConnectId);
2863
+ binding.rejectedConnectId = resolvedConnectId;
2864
+ this._sessions.delete(resolvedConnectId);
2865
+ await this.connector.disconnect(sessionId);
2866
+ if (operationId) this._pendingOperationDisconnects.delete(operationId);
2867
+ _LedgerAdapter._throwIfAborted(signal);
2868
+ resolvedConnectId = await this._connectFirstOrSelect(
2869
+ [],
2870
+ void 0,
2871
+ true,
2872
+ operationId,
2873
+ binding,
2874
+ signal
2875
+ );
2876
+ const selectedSession = this._sessions.get(resolvedConnectId);
2877
+ const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
2878
+ if (!selectedSession || !selectedDevice)
2879
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2880
+ code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
2881
+ message: "Selected Ledger connection ended"
2882
+ });
2883
+ sessionId = selectedSession;
2884
+ if (operationId)
2885
+ this._operations.rebind(operationId, {
2886
+ connectId: resolvedConnectId,
2887
+ device: selectedDevice,
2888
+ // Same source as `_createOperation`: the selected transport, not
2889
+ // the device snapshot a session connect overwrote.
2890
+ connectionType: this._isBleConnection() ? "ble" : "usb",
2891
+ connectionKeys: [sessionId]
2892
+ });
2893
+ if (installContext)
2894
+ installContext.connection = { connectId: resolvedConnectId, sessionId };
2895
+ }
2896
+ await this._publishVerifiedBleBinding(
2897
+ resolvedConnectId,
2898
+ fingerprint.chain,
2899
+ fingerprint.deviceId,
2900
+ connectionAttempt,
2901
+ operationId,
2902
+ signal
1922
2903
  );
1923
- if (!fp.success) {
1924
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1925
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
2904
+ if (!operationId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
2905
+ this._verifiedBleReconnectTargets.set(connectId, {
2906
+ connectId: resolvedConnectId,
2907
+ chain: fingerprint.chain,
2908
+ fingerprint: fingerprint.deviceId
1926
2909
  });
1927
2910
  }
1928
2911
  }
2912
+ businessCallStarted = true;
1929
2913
  return await this._callConnector(sessionId, method, effectiveParams, signal);
1930
2914
  } catch (err) {
1931
2915
  if (signal.aborted) throw err;
2916
+ if (isLostConnectionError(err)) {
2917
+ this._discoveredDevices.delete(resolvedConnectId);
2918
+ if (operationId) await this._releaseLostOperationConnection(operationId);
2919
+ else {
2920
+ this._sessions.delete(resolvedConnectId);
2921
+ await this.connector.disconnect(sessionId).catch(() => void 0);
2922
+ }
2923
+ const ambiguous = businessCallStarted && !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method);
2924
+ const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
2925
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2926
+ code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(err).code,
2927
+ message: "Ledger operation connection was lost; start a new operation",
2928
+ params: ambiguous ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, { operationId }) : interactionParams,
2929
+ recovery: ambiguous ? { scope: "unknown" } : void 0
2930
+ });
2931
+ }
1932
2932
  const errObj = err;
1933
2933
  debugLog("[LedgerAdapter] connectorCall error:", method, {
1934
2934
  message: errObj?.message,
@@ -1940,6 +2940,60 @@ var _LedgerAdapter = class _LedgerAdapter {
1940
2940
  isNotAdvertising: isDeviceNotAdvertisingError(err),
1941
2941
  isStuckApp: isStuckAppStateError(err)
1942
2942
  });
2943
+ const assertSessionCurrent = (message) => {
2944
+ if (this._sessions.get(resolvedConnectId) === sessionId) return;
2945
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2946
+ code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
2947
+ message
2948
+ });
2949
+ };
2950
+ if ((isDeviceLockedError(err) || errObj?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceLocked) && lockedRetryBudget > 0) {
2951
+ await this._waitForDeviceConnect(signal);
2952
+ assertSessionCurrent("Ledger connection ended while waiting for unlock");
2953
+ return this._runConnectorCall(
2954
+ resolvedConnectId,
2955
+ method,
2956
+ effectiveParams,
2957
+ signal,
2958
+ fingerprint,
2959
+ permissionDeviceId,
2960
+ commonParams,
2961
+ installContext,
2962
+ operationId,
2963
+ lockedRetryBudget - 1
2964
+ );
2965
+ }
2966
+ if (businessCallStarted && isStuckAppStateError(err)) {
2967
+ await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2968
+ assertSessionCurrent("Ledger connection ended during the app transition");
2969
+ try {
2970
+ return await this._callConnector(sessionId, method, effectiveParams, signal);
2971
+ } catch (retryErr) {
2972
+ if (isStuckAppStateError(retryErr)) throw err;
2973
+ if (isLostConnectionError(retryErr)) {
2974
+ if (operationId) await this._releaseLostOperationConnection(operationId);
2975
+ else {
2976
+ this._sessions.delete(resolvedConnectId);
2977
+ this._discoveredDevices.delete(resolvedConnectId);
2978
+ await this.connector.disconnect(sessionId).catch(() => void 0);
2979
+ }
2980
+ const ambiguous = !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method);
2981
+ throw (0, import_hwk_adapter_core3.createHwkError)({
2982
+ code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(retryErr).code,
2983
+ message: `Ledger ${method} may have completed before the connection was lost`,
2984
+ params: ambiguous ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, { operationId, reason: "disconnect" }) : { operationId, reason: "disconnect" },
2985
+ recovery: ambiguous ? { scope: "unknown" } : void 0
2986
+ });
2987
+ }
2988
+ throw retryErr;
2989
+ }
2990
+ }
2991
+ if (!operationId && errObj?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch) {
2992
+ this._sessions.delete(resolvedConnectId);
2993
+ this._discoveredDevices.delete(resolvedConnectId);
2994
+ await this.connector.disconnect(sessionId).catch(() => void 0);
2995
+ throw err;
2996
+ }
1943
2997
  const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
1944
2998
  const isAppMissing = isAppNotInstalledError(err) || err?.code === import_hwk_adapter_core3.HardwareErrorCode.AppNotInstalled;
1945
2999
  if (autoInstallApp && isAppMissing) {
@@ -1995,157 +3049,14 @@ var _LedgerAdapter = class _LedgerAdapter {
1995
3049
  fingerprint,
1996
3050
  permissionDeviceId,
1997
3051
  commonParams,
1998
- installContext
3052
+ installContext,
3053
+ operationId
1999
3054
  );
2000
3055
  }
2001
3056
  }
2002
- if (isStuckAppStateError(err)) {
2003
- try {
2004
- this._sessions.delete(resolvedConnectId);
2005
- this._discoveredDevices.delete(resolvedConnectId);
2006
- this.connector.reset?.();
2007
- } catch {
2008
- }
2009
- debugLog(
2010
- "[LedgerAdapter] stuck-app retry: method=",
2011
- method,
2012
- "delayMs=",
2013
- _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
2014
- "_tag=",
2015
- err?._tag
2016
- );
2017
- try {
2018
- const retryResult = await this._retryAfterStuckApp(
2019
- resolvedConnectId,
2020
- method,
2021
- effectiveParams,
2022
- signal,
2023
- err,
2024
- fingerprint
2025
- );
2026
- debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
2027
- return retryResult;
2028
- } catch (retryErr) {
2029
- if (signal.aborted) throw retryErr;
2030
- if (isStuckAppStateError(retryErr)) {
2031
- debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
2032
- throw err;
2033
- }
2034
- debugLog(
2035
- "[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
2036
- method,
2037
- "retryErrTag=",
2038
- retryErr?._tag
2039
- );
2040
- throw retryErr;
2041
- }
2042
- }
2043
- if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
2044
- let lastErr = err;
2045
- for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
2046
- if (signal.aborted) throw lastErr;
2047
- try {
2048
- this._sessions.delete(resolvedConnectId);
2049
- this._discoveredDevices.delete(resolvedConnectId);
2050
- if (isDeviceDisconnectedError(lastErr)) {
2051
- try {
2052
- this.connector.reset?.();
2053
- } catch {
2054
- }
2055
- }
2056
- const reConnectId = await this.ensureConnected(
2057
- resolvedConnectId,
2058
- signal,
2059
- allowUsbEphemeralFallback
2060
- );
2061
- const reSessionId = this._sessions.get(reConnectId);
2062
- if (!reSessionId) throw lastErr;
2063
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2064
- const fp = await this._abortable(
2065
- signal,
2066
- this._verifyDeviceFingerprintWithSession(
2067
- reSessionId,
2068
- fingerprint.deviceId,
2069
- fingerprint.chain
2070
- )
2071
- );
2072
- if (!fp.success) {
2073
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2074
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
2075
- });
2076
- }
2077
- }
2078
- return await this._callConnector(reSessionId, method, effectiveParams, signal);
2079
- } catch (retryErr) {
2080
- if (signal.aborted) throw retryErr;
2081
- lastErr = retryErr;
2082
- const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
2083
- if (!canRetry) {
2084
- throw retryErr;
2085
- }
2086
- }
2087
- }
2088
- throw lastErr;
2089
- }
2090
- if (isTimeoutError(err)) {
2091
- debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
2092
- this._discoveredDevices.delete(resolvedConnectId);
2093
- return this._retryWithFreshConnection(
2094
- resolvedConnectId,
2095
- method,
2096
- effectiveParams,
2097
- signal,
2098
- err,
2099
- fingerprint
2100
- );
2101
- }
2102
- if (isConnectionLevelError(err)) {
2103
- debugLog("[LedgerAdapter] connection-level fail-closed reset");
2104
- this._sessions.delete(resolvedConnectId);
2105
- this._discoveredDevices.delete(resolvedConnectId);
2106
- this.connector.reset();
2107
- const codeNum = err?.code;
2108
- throw Object.assign(err, {
2109
- code: codeNum ?? import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected
2110
- });
2111
- }
2112
3057
  throw err;
2113
3058
  }
2114
3059
  }
2115
- /**
2116
- * Stuck-app recovery: pause for the device's UI transition, then retry once.
2117
- *
2118
- * Caller has already cleared the session + reset connector. We wait so Stax
2119
- * finishes its post-CloseApp animation, then go through ensureConnected +
2120
- * fingerprint check + call exactly once. Caller decides what to do on a
2121
- * second stuck-app hit.
2122
- */
2123
- async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
2124
- await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2125
- const retryConnectId = await this.ensureConnected(
2126
- resolvedConnectId,
2127
- signal,
2128
- !!fingerprint?.deviceId && !fingerprint.skipFingerprint
2129
- );
2130
- const retrySessionId = this._sessions.get(retryConnectId);
2131
- if (!retrySessionId) throw originalErr;
2132
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2133
- const fp = await this._abortable(
2134
- signal,
2135
- this._verifyDeviceFingerprintWithSession(
2136
- retrySessionId,
2137
- fingerprint.deviceId,
2138
- fingerprint.chain
2139
- )
2140
- );
2141
- if (!fp.success) {
2142
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2143
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
2144
- });
2145
- }
2146
- }
2147
- return this._callConnector(retrySessionId, method, params, signal);
2148
- }
2149
3060
  _sleepAbortable(ms, signal) {
2150
3061
  return new Promise((resolve, reject) => {
2151
3062
  if (signal.aborted) {
@@ -2160,115 +3071,26 @@ var _LedgerAdapter = class _LedgerAdapter {
2160
3071
  clearTimeout(timer);
2161
3072
  reject(signal.reason ?? new Error("Aborted"));
2162
3073
  };
2163
- signal.addEventListener("abort", onAbort, { once: true });
2164
- });
2165
- }
2166
- /**
2167
- * Clear stale session, reconnect, and retry the call.
2168
- *
2169
- * Timeout recovery starts with a full connector reset. After an APDU
2170
- * timeout, DMK/transport state may still emit malformed responses; retrying
2171
- * on the same DMK can poison the next chain switch.
2172
- */
2173
- async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
2174
- this.connector.reset();
2175
- this._sessions.clear();
2176
- this._discoveredDevices.clear();
2177
- this._connectingPromise = null;
2178
- const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
2179
- const retryConnectId = await this.ensureConnected(
2180
- targetConnectId,
2181
- signal,
2182
- allowUsbEphemeralFallback
2183
- );
2184
- const retrySessionId = this._sessions.get(retryConnectId);
2185
- if (!retrySessionId) {
2186
- throw originalErr;
2187
- }
2188
- try {
2189
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2190
- const fp = await this._abortable(
2191
- signal,
2192
- this._verifyDeviceFingerprintWithSession(
2193
- retrySessionId,
2194
- fingerprint.deviceId,
2195
- fingerprint.chain
2196
- )
2197
- );
2198
- if (!fp.success) {
2199
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2200
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
2201
- });
2202
- }
2203
- }
2204
- return await this._callConnector(retrySessionId, method, params, signal);
2205
- } catch (retryErr) {
2206
- if (signal.aborted) throw retryErr;
2207
- this.connector.reset();
2208
- this._sessions.clear();
2209
- this._discoveredDevices.clear();
2210
- this._connectingPromise = null;
2211
- if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
2212
- throw retryErr;
2213
- }
2214
- debugLog(
2215
- "[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
2216
- );
2217
- this.connector.reset();
2218
- this._sessions.clear();
2219
- this._discoveredDevices.clear();
2220
- this._connectingPromise = null;
2221
- const finalConnectId = await this.ensureConnected(
2222
- targetConnectId,
2223
- signal,
2224
- allowUsbEphemeralFallback
2225
- );
2226
- const finalSessionId = this._sessions.get(finalConnectId);
2227
- if (!finalSessionId) {
2228
- throw originalErr;
2229
- }
2230
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2231
- const fp = await this._abortable(
2232
- signal,
2233
- this._verifyDeviceFingerprintWithSession(
2234
- finalSessionId,
2235
- fingerprint.deviceId,
2236
- fingerprint.chain
2237
- )
2238
- );
2239
- if (!fp.success) {
2240
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2241
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
2242
- });
2243
- }
2244
- }
2245
- return this._callConnector(finalSessionId, method, params, signal);
2246
- }
3074
+ signal.addEventListener("abort", onAbort, { once: true });
3075
+ });
2247
3076
  }
2248
3077
  /**
2249
- * Ensure OS-level device permission (Bluetooth / USB) before proceeding.
2250
- *
2251
- * Emits `REQUEST_DEVICE_PERMISSION` and awaits the consumer's
2252
- * `RECEIVE_DEVICE_PERMISSION` reply (60s budget covers "probe → system
2253
- * prompt → user tap" plus a generous margin). If the consumer never wires
2254
- * a handler or never replies, the wait times out and the operation fails
2255
- * fast so scanners/callers don't hang silently.
2256
- *
2257
- * - No connectId (searchDevices): environment-level permission
2258
- * - With connectId (business methods): device-level permission
3078
+ * Ensure OS-level Bluetooth/USB permission; times out after 60s if the host never
3079
+ * replies. Without connectId the request is environment-level, else device-level.
2259
3080
  */
2260
3081
  async _ensureDevicePermission(connectId, deviceId, signal) {
2261
3082
  if (signal?.aborted) {
2262
3083
  _LedgerAdapter._throwIfAborted(signal);
2263
3084
  }
2264
3085
  const transportType = this.activeTransport ?? "hid";
3086
+ const operationId = this._activeOperationId();
2265
3087
  const waitPromise = this._uiRegistry.wait(
2266
3088
  import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
2267
- { timeoutMs: 6e4 }
3089
+ { timeoutMs: 6e4, operationId }
2268
3090
  );
2269
3091
  this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
2270
3092
  type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
2271
- payload: { transportType, connectId, deviceId }
3093
+ payload: { transportType, connectId, deviceId, operationId }
2272
3094
  });
2273
3095
  let response;
2274
3096
  const onAbort = () => {
@@ -2314,7 +3136,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2314
3136
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
2315
3137
  const e = err;
2316
3138
  const params = e.code === import_hwk_adapter_core3.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
2317
- return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
3139
+ return ledgerFailure(
3140
+ e.code,
3141
+ e.message ?? "Unknown error",
3142
+ e.appName,
3143
+ tag,
3144
+ params,
3145
+ void 0,
3146
+ (0, import_hwk_adapter_core3.isHwkRecoveryHint)(e.recovery) ? e.recovery : void 0
3147
+ );
2318
3148
  }
2319
3149
  const mapped = mapLedgerError(err);
2320
3150
  return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
@@ -2341,7 +3171,7 @@ var _LedgerAdapter = class _LedgerAdapter {
2341
3171
  deviceId: device.deviceId,
2342
3172
  connectId: device.connectId,
2343
3173
  label: device.name,
2344
- connectionType: this.connector.connectionType,
3174
+ connectionType: device.connectionType ?? this.connector.connectionType,
2345
3175
  rssi: device.rssi,
2346
3176
  isConnectable: device.isConnectable,
2347
3177
  serialNumber: device.serialNumber,
@@ -2366,7 +3196,7 @@ _LedgerAdapter.APP_INSTALL_PROGRESS_MIN_DELTA = 0.05;
2366
3196
  var LedgerAdapter = _LedgerAdapter;
2367
3197
 
2368
3198
  // src/connector/LedgerConnectorBase.ts
2369
- var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
3199
+ var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
2370
3200
 
2371
3201
  // src/device/LedgerDeviceManager.ts
2372
3202
  var LedgerDeviceManager = class {
@@ -2902,7 +3732,7 @@ var SignerManager = class _SignerManager {
2902
3732
  return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
2903
3733
  }
2904
3734
  static _createContextModule() {
2905
- const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
3735
+ const contextModule = new import_context_module.ContextModuleBuilder({}).setChain(import_context_module.ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
2906
3736
  return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
2907
3737
  }
2908
3738
  static wrapBlindSigningReportNonBlocking(contextModule) {
@@ -2940,6 +3770,27 @@ function collapseSignerInteraction(interaction) {
2940
3770
  return import_hwk_adapter_core5.EConnectorInteraction.ConfirmOnDevice;
2941
3771
  }
2942
3772
  }
3773
+ function wireSignerToSession(ctx, sessionId, chain, signer) {
3774
+ signer.onInteraction = (interaction) => {
3775
+ debugLog(`[LedgerConnector] ${chain}.onInteraction:`, interaction);
3776
+ ctx.emit("ui-event", {
3777
+ type: collapseSignerInteraction(interaction),
3778
+ payload: { sessionId }
3779
+ });
3780
+ };
3781
+ signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
3782
+ return signer;
3783
+ }
3784
+ async function runSignerCall(ctx, sessionId, call) {
3785
+ try {
3786
+ return await call();
3787
+ } catch (err) {
3788
+ ctx.invalidateSession(sessionId);
3789
+ throw ctx.wrapError(err);
3790
+ } finally {
3791
+ ctx.clearCanceller(sessionId);
3792
+ }
3793
+ }
2943
3794
 
2944
3795
  // src/connector/chains/evm.ts
2945
3796
  async function evmGetAddress(ctx, sessionId, params) {
@@ -3373,6 +4224,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
3373
4224
 
3374
4225
  // src/connector/chains/sol.ts
3375
4226
  var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
4227
+ var import_bs58 = __toESM(require("bs58"));
3376
4228
 
3377
4229
  // src/signer/SignerSol.ts
3378
4230
  var SignerSol = class {
@@ -3456,8 +4308,21 @@ async function solSignMessage(ctx, sessionId, params) {
3456
4308
  const path = normalizePath(params.path);
3457
4309
  const messageBytes = (0, import_hwk_adapter_core9.hexToBytes)(params.message);
3458
4310
  try {
4311
+ if (params.messageVersion === 1) {
4312
+ const preparedMessage = (0, import_hwk_adapter_core9.prepareSolanaOffchainMessageV1)({
4313
+ message: messageBytes,
4314
+ requiredSigners: params.requiredSigners
4315
+ });
4316
+ const { SignMessageVersion } = await ctx.importLedgerKit(
4317
+ "@ledgerhq/device-signer-kit-solana"
4318
+ );
4319
+ const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
4320
+ version: SignMessageVersion.Raw
4321
+ });
4322
+ return { signature: decodeBase58Signature(result2.signature) };
4323
+ }
3459
4324
  const result = await solSigner.signMessage(path, messageBytes);
3460
- return { signature: result.signature };
4325
+ return { signature: decodeBase58EnvelopeSignature(result.signature) };
3461
4326
  } catch (err) {
3462
4327
  ctx.invalidateSession(sessionId);
3463
4328
  throw ctx.wrapError(err);
@@ -3465,11 +4330,27 @@ async function solSignMessage(ctx, sessionId, params) {
3465
4330
  ctx.clearCanceller(sessionId);
3466
4331
  }
3467
4332
  }
4333
+ function decodeBase58Signature(signature) {
4334
+ const bytes = import_bs58.default.decode(signature);
4335
+ if (bytes.length !== 64) {
4336
+ throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
4337
+ }
4338
+ return (0, import_hwk_adapter_core9.bytesToHex)(bytes);
4339
+ }
4340
+ function decodeBase58EnvelopeSignature(envelope) {
4341
+ const bytes = import_bs58.default.decode(envelope);
4342
+ if (bytes.length < 65 || bytes[0] !== 1) {
4343
+ throw new Error("Ledger Solana signature envelope is invalid");
4344
+ }
4345
+ return (0, import_hwk_adapter_core9.bytesToHex)(bytes.subarray(1, 65));
4346
+ }
3468
4347
  async function _createSolSigner(ctx, sessionId) {
3469
4348
  const dmk = await ctx.getOrCreateDmk();
3470
- const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit("@ledgerhq/context-module");
4349
+ const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
4350
+ "@ledgerhq/context-module"
4351
+ );
3471
4352
  const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
3472
- const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
4353
+ const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
3473
4354
  const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
3474
4355
  const signer = new SignerSol(sdkSigner);
3475
4356
  signer.onInteraction = (interaction) => {
@@ -3485,360 +4366,151 @@ async function _createSolSigner(ctx, sessionId) {
3485
4366
  return signer;
3486
4367
  }
3487
4368
 
3488
- // src/connector/chains/tron.ts
3489
- var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
3490
- var import_hw_app_trx = __toESM(require("@ledgerhq/hw-app-trx"));
3491
-
3492
- // src/connector/chains/legacyChainCall.ts
3493
- var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
3494
-
3495
- // src/app/AppManager.ts
3496
- var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
4369
+ // src/connector/chains/zcash.ts
3497
4370
  var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
3498
- var APP_NAME_MAP = {
3499
- ETH: "Ethereum",
3500
- BTC: "Bitcoin",
3501
- SOL: "Solana",
3502
- TRX: "Tron",
3503
- XRP: "XRP",
3504
- ADA: "Cardano",
3505
- DOT: "Polkadot",
3506
- ATOM: "Cosmos"
3507
- };
3508
- var DASHBOARD_APP_NAME = "BOLOS";
3509
- var AppManager = class {
3510
- constructor(dmk, options) {
3511
- this._dmk = dmk;
3512
- this._waitMs = options?.waitMs ?? 1e3;
3513
- this._maxRetries = options?.maxRetries ?? 10;
3514
- }
3515
- /**
3516
- * Return the Ledger app name for a given chain ticker,
3517
- * or undefined if the chain is not supported.
3518
- */
3519
- static getAppName(chain) {
3520
- return APP_NAME_MAP[chain];
3521
- }
3522
- /**
3523
- * Ensure the target app is open on the device identified by `sessionId`.
3524
- *
3525
- * Flow:
3526
- * 1. Check the currently running app.
3527
- * 2. If it is already the target, return immediately.
3528
- * 3. If a different app is running (not dashboard), close it first.
3529
- * 4. Open the target app.
3530
- * 5. Poll until the device confirms the target app is running.
3531
- */
3532
- /**
3533
- * @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
3534
- * device is about to display "Open <app>" on screen and wait for the
3535
- * user's button press. UI consumers should show their "open app" prompt
3536
- * in response. NOT called when the target app is already open (no user
3537
- * interaction needed in that case).
3538
- *
3539
- * Important: OpenAppCommand is blocking. It does not resolve until the user
3540
- * has physically confirmed on the device, so anything that runs AFTER
3541
- * `await this._openApp(...)` lands AFTER the prompt is already gone.
3542
- * Hence the callback must fire BEFORE that await.
3543
- */
3544
- async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
3545
- const currentApp = await this._getCurrentApp(sessionId);
3546
- if (currentApp === targetAppName) {
3547
- return;
3548
- }
3549
- if (!this._isDashboard(currentApp)) {
3550
- await this._closeCurrentApp(sessionId);
3551
- await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
3552
- }
3553
- onConfirmOnDevice?.();
3554
- await this._openApp(sessionId, targetAppName);
3555
- await this._waitForApp(sessionId, targetAppName);
4371
+
4372
+ // src/signer/SignerZcash.ts
4373
+ var SignerZcash = class {
4374
+ // eslint-disable-next-line no-useless-constructor, no-empty-function
4375
+ constructor(_sdk) {
4376
+ this._sdk = _sdk;
3556
4377
  }
3557
- // ---------------------------------------------------------------------------
3558
- // Private helpers
3559
- // ---------------------------------------------------------------------------
3560
- async _getCurrentApp(sessionId) {
3561
- const result = await this._dmk.sendCommand({
3562
- sessionId,
3563
- command: new import_device_management_kit2.GetAppAndVersionCommand()
3564
- });
3565
- if ((0, import_device_management_kit2.isSuccessCommandResult)(result)) {
3566
- debugLog("[AppManager] currentApp:", result.data.name);
3567
- return result.data.name;
3568
- }
3569
- const errResult = result;
3570
- const dmkErr = errResult.error ?? {};
3571
- const original = dmkErr.originalError;
3572
- debugLog(
3573
- "[AppManager] _getCurrentApp failed sessionId=",
3574
- sessionId,
3575
- "tag=",
3576
- dmkErr._tag,
3577
- "errorCode=",
3578
- dmkErr.errorCode,
3579
- "message=",
3580
- dmkErr.message,
3581
- "originalErrorMessage=",
3582
- original?.message ?? String(original ?? "")
3583
- );
3584
- throw Object.assign(
3585
- new Error(
3586
- dmkErr.message ?? "Failed to get current app from device"
3587
- ),
3588
- {
3589
- _tag: dmkErr._tag,
3590
- errorCode: dmkErr.errorCode,
3591
- originalError: original
3592
- }
4378
+ /** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
4379
+ async getFullViewingKey(derivationPath, options) {
4380
+ const action = this._sdk.getFullViewingKey(derivationPath, options);
4381
+ return deviceActionToPromise(
4382
+ action,
4383
+ this.onInteraction,
4384
+ void 0,
4385
+ this.onRegisterCanceller
3593
4386
  );
3594
4387
  }
3595
- async _openApp(sessionId, appName) {
3596
- const result = await this._dmk.sendCommand({
3597
- sessionId,
3598
- command: new import_device_management_kit2.OpenAppCommand({ appName })
3599
- });
3600
- if (!(0, import_device_management_kit2.isSuccessCommandResult)(result)) {
3601
- const dmkErr = result.error;
3602
- const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
3603
- const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
3604
- debugLog(
3605
- "[AppManager] openApp failed:",
3606
- appName,
3607
- "errorCode:",
3608
- errorCode,
3609
- "tag:",
3610
- dmkErr._tag
3611
- );
3612
- let code;
3613
- if (errorCode === "6807" || /unknown application/i.test(message)) {
3614
- code = import_hwk_adapter_core10.HardwareErrorCode.AppNotInstalled;
3615
- } else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
3616
- code = import_hwk_adapter_core10.HardwareErrorCode.UserRejected;
3617
- }
3618
- throw Object.assign(new Error(`Failed to open "${appName}"`), {
3619
- _tag: ERROR_TAG.OpenAppCommand,
3620
- code,
3621
- errorCode,
3622
- statusCode: errorCode,
3623
- appName,
3624
- originalError: dmkErr
3625
- });
3626
- }
3627
- }
3628
- async _closeCurrentApp(sessionId) {
3629
- debugLog("[AppManager] closeCurrentApp");
3630
- await this._dmk.sendCommand({
3631
- sessionId,
3632
- command: new import_device_management_kit2.CloseAppCommand()
3633
- });
3634
- }
3635
- /**
3636
- * Poll the device until the expected app is reported as running,
3637
- * or throw after `_maxRetries` attempts.
3638
- */
3639
- async _waitForApp(sessionId, expectedAppName) {
3640
- let lastSeen = "";
3641
- for (let i = 0; i < this._maxRetries; i++) {
3642
- await this._wait();
3643
- const current = await this._getCurrentApp(sessionId);
3644
- lastSeen = current;
3645
- if (current === expectedAppName) {
3646
- return;
3647
- }
3648
- }
3649
- debugLog(
3650
- "[AppManager] waitForApp exhausted: expected=",
3651
- expectedAppName,
3652
- "lastSeen=",
3653
- lastSeen
3654
- );
3655
- throw new Error(
3656
- `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
4388
+ /** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
4389
+ async getShieldedAddress(derivationPath, options) {
4390
+ const action = this._sdk.getShieldedAddress(derivationPath, options);
4391
+ return deviceActionToPromise(
4392
+ action,
4393
+ this.onInteraction,
4394
+ void 0,
4395
+ this.onRegisterCanceller
3657
4396
  );
3658
4397
  }
3659
- _isDashboard(appName) {
3660
- return appName === DASHBOARD_APP_NAME;
3661
- }
3662
- _wait() {
3663
- return new Promise((resolve) => setTimeout(resolve, this._waitMs));
3664
- }
3665
4398
  };
3666
4399
 
3667
- // src/connector/chains/legacyChainCall.ts
3668
- function isLegacyWrongAppError(err, _appName) {
3669
- return isWrongAppError(err);
4400
+ // src/connector/chains/zcash.ts
4401
+ async function zcashGetFullViewingKey(ctx, sessionId, params) {
4402
+ const signer = await _createZcashSigner(ctx, sessionId);
4403
+ const path = normalizePath(params.path);
4404
+ const mode = params.mode ?? "ufvk";
4405
+ return runSignerCall(ctx, sessionId, async () => {
4406
+ const result = await signer.getFullViewingKey(path, { mode });
4407
+ if (result.mode === "ufvk") {
4408
+ return { path: params.path, mode, ufvk: result.fullViewingKey };
4409
+ }
4410
+ return { path: params.path, mode, orchardFvk: (0, import_hwk_adapter_core10.bytesToHex)(result.fullViewingKey) };
4411
+ });
3670
4412
  }
3671
- async function withLegacyChainCall(ctx, sessionId, options, action) {
3672
- const { appName, needsConfirmation } = options;
3673
- let openAppPromptShown = false;
3674
- const onAppOpenPrompt = () => {
3675
- openAppPromptShown = true;
3676
- ctx.emit("ui-event", {
3677
- type: import_hwk_adapter_core11.EConnectorInteraction.ConfirmOpenApp,
3678
- payload: { sessionId }
4413
+ async function zcashGetShieldedAddress(ctx, sessionId, params) {
4414
+ const signer = await _createZcashSigner(ctx, sessionId);
4415
+ const path = normalizePath(params.path);
4416
+ return runSignerCall(ctx, sessionId, async () => {
4417
+ const result = await signer.getShieldedAddress(path, {
4418
+ checkOnDevice: params.showOnDevice ?? false
3679
4419
  });
3680
- };
3681
- const closeOpenAppUiIfShown = () => {
3682
- if (openAppPromptShown) {
3683
- ctx.emit("ui-event", {
3684
- type: import_hwk_adapter_core11.EConnectorInteraction.InteractionComplete,
3685
- payload: { sessionId }
3686
- });
3687
- openAppPromptShown = false;
3688
- }
3689
- };
3690
- try {
3691
- await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
3692
- } catch (err) {
3693
- debugLog(
3694
- "[LegacyChainCall] pre-flight ensureAppOpen failed:",
3695
- appName,
3696
- err?.message
3697
- );
3698
- closeOpenAppUiIfShown();
3699
- throw ctx.wrapError(err, { defaultAppName: appName });
3700
- }
3701
- const runOnce = async () => {
3702
- let confirmEmitted = false;
3703
- if (needsConfirmation) {
3704
- ctx.emit("ui-event", {
3705
- type: import_hwk_adapter_core11.EConnectorInteraction.ConfirmOnDevice,
3706
- payload: { sessionId }
3707
- });
3708
- confirmEmitted = true;
3709
- }
3710
- try {
3711
- return await action(sessionId);
3712
- } finally {
3713
- if (confirmEmitted || openAppPromptShown) {
3714
- ctx.emit("ui-event", {
3715
- type: import_hwk_adapter_core11.EConnectorInteraction.InteractionComplete,
3716
- payload: { sessionId }
3717
- });
3718
- openAppPromptShown = false;
3719
- }
3720
- }
3721
- };
3722
- try {
3723
- return await runOnce();
3724
- } catch (err) {
3725
- if (!isLegacyWrongAppError(err, appName)) {
3726
- debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
3727
- ctx.invalidateSession(sessionId);
3728
- throw ctx.wrapError(err, { defaultAppName: appName });
3729
- }
3730
- debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
3731
- try {
3732
- await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
3733
- } catch (switchErr) {
3734
- debugLog(
3735
- "[LegacyChainCall] retry ensureAppOpen failed:",
3736
- appName,
3737
- switchErr?.message
3738
- );
3739
- closeOpenAppUiIfShown();
3740
- throw ctx.wrapError(switchErr, { defaultAppName: appName });
3741
- }
3742
- ctx.clearAllSigners();
3743
- const result = await runOnce();
3744
- debugLog("[LegacyChainCall] retry succeeded:", appName);
3745
- return result;
3746
- }
4420
+ return { address: result.address, path: params.path };
4421
+ });
3747
4422
  }
3748
- async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
4423
+ async function _createZcashSigner(ctx, sessionId) {
3749
4424
  const dmk = await ctx.getOrCreateDmk();
3750
- const appManager = new AppManager(dmk);
3751
- await appManager.ensureAppOpen(sessionId, appName, onPrompt);
4425
+ const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
4426
+ const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
4427
+ return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
3752
4428
  }
3753
4429
 
3754
- // src/transport/DmkTransport.ts
3755
- var import_hw_transport = __toESM(require("@ledgerhq/hw-transport"));
3756
- var DmkTransport = class extends import_hw_transport.default {
3757
- constructor(dmk, sessionId) {
3758
- super();
3759
- this._dmk = dmk;
3760
- this._sessionId = sessionId;
4430
+ // src/connector/chains/tron.ts
4431
+ var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
4432
+
4433
+ // src/signer/SignerTron.ts
4434
+ var SignerTron = class {
4435
+ // eslint-disable-next-line no-useless-constructor, no-empty-function
4436
+ constructor(_sdk) {
4437
+ this._sdk = _sdk;
3761
4438
  }
3762
- async exchange(apdu) {
3763
- const response = await this._dmk.sendApdu({
3764
- sessionId: this._sessionId,
3765
- apdu: new Uint8Array(apdu)
3766
- });
3767
- const { data, statusCode } = response;
3768
- const result = Buffer.alloc(data.length + 2);
3769
- if (data.length > 0) {
3770
- result.set(data, 0);
3771
- }
3772
- result.set(statusCode, data.length);
3773
- return result;
4439
+ /** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
4440
+ async getAddress(derivationPath, options) {
4441
+ const action = this._sdk.getAddress(derivationPath, options);
4442
+ const result = await deviceActionToPromise(
4443
+ action,
4444
+ this.onInteraction,
4445
+ void 0,
4446
+ this.onRegisterCanceller
4447
+ );
4448
+ return { address: result.address, publicKey: result.publicKey };
3774
4449
  }
3775
- async close() {
4450
+ /** Sign a protobuf-encoded raw transaction. */
4451
+ async signTransaction(derivationPath, transaction, options) {
4452
+ const action = this._sdk.signTransaction(derivationPath, transaction, options);
4453
+ return deviceActionToPromise(
4454
+ action,
4455
+ this.onInteraction,
4456
+ void 0,
4457
+ this.onRegisterCanceller
4458
+ );
4459
+ }
4460
+ /** Sign a personal message (TIP-191). */
4461
+ async signPersonalMessage(derivationPath, message, options) {
4462
+ const action = this._sdk.signPersonalMessage(derivationPath, message, options);
4463
+ return deviceActionToPromise(
4464
+ action,
4465
+ this.onInteraction,
4466
+ void 0,
4467
+ this.onRegisterCanceller
4468
+ );
3776
4469
  }
3777
4470
  };
3778
4471
 
3779
4472
  // src/connector/chains/tron.ts
3780
4473
  async function tronGetAddress(ctx, sessionId, params) {
4474
+ const tronSigner = await _createTronSigner(ctx, sessionId);
3781
4475
  const path = normalizePath(params.path);
3782
- const showOnDevice = params.showOnDevice ?? false;
3783
- return withLegacyChainCall(
3784
- ctx,
3785
- sessionId,
3786
- {
3787
- appName: "Tron",
3788
- // Only show "confirm on device" UI when the device is actually going
3789
- // to display the address for the user to verify.
3790
- needsConfirmation: showOnDevice
3791
- },
3792
- async (sid) => {
3793
- const trx = await _createTrx(ctx, sid);
3794
- const result = await trx.getAddress(path, showOnDevice);
3795
- return { address: result.address, publicKey: result.publicKey, path: params.path };
3796
- }
3797
- );
4476
+ return runSignerCall(ctx, sessionId, async () => {
4477
+ const result = await tronSigner.getAddress(path, {
4478
+ checkOnDevice: params.showOnDevice ?? false
4479
+ });
4480
+ return { address: result.address, publicKey: result.publicKey, path: params.path };
4481
+ });
3798
4482
  }
3799
4483
  async function tronSignTransaction(ctx, sessionId, params) {
3800
4484
  if (!params.rawTxHex) {
3801
4485
  throw Object.assign(
3802
4486
  new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
3803
- { code: import_hwk_adapter_core12.HardwareErrorCode.InvalidParams }
4487
+ { code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
3804
4488
  );
3805
4489
  }
4490
+ const tronSigner = await _createTronSigner(ctx, sessionId);
3806
4491
  const path = normalizePath(params.path);
3807
- return withLegacyChainCall(
3808
- ctx,
3809
- sessionId,
3810
- { appName: "Tron", needsConfirmation: true },
3811
- async (sid) => {
3812
- const trx = await _createTrx(ctx, sid);
3813
- const signature = await trx.signTransaction(
3814
- path,
3815
- params.rawTxHex,
3816
- params.tokenSignatures ?? []
3817
- );
3818
- return { signature };
3819
- }
3820
- );
4492
+ return runSignerCall(ctx, sessionId, async () => {
4493
+ const signature = await tronSigner.signTransaction(path, (0, import_hwk_adapter_core11.hexToBytes)(params.rawTxHex));
4494
+ return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
4495
+ });
3821
4496
  }
3822
4497
  async function tronSignMessage(ctx, sessionId, params) {
4498
+ const tronSigner = await _createTronSigner(ctx, sessionId);
3823
4499
  const path = normalizePath(params.path);
3824
- return withLegacyChainCall(
3825
- ctx,
3826
- sessionId,
3827
- { appName: "Tron", needsConfirmation: true },
3828
- async (sid) => {
3829
- const trx = await _createTrx(ctx, sid);
3830
- const signature = await trx.signPersonalMessage(path, params.messageHex);
3831
- return { signature };
3832
- }
3833
- );
4500
+ return runSignerCall(ctx, sessionId, async () => {
4501
+ const signature = await tronSigner.signPersonalMessage(path, (0, import_hwk_adapter_core11.hexToBytes)(params.messageHex));
4502
+ return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
4503
+ });
3834
4504
  }
3835
- async function _createTrx(ctx, sessionId) {
4505
+ async function _createTronSigner(ctx, sessionId) {
3836
4506
  const dmk = await ctx.getOrCreateDmk();
3837
- return new import_hw_app_trx.default(new DmkTransport(dmk, sessionId));
4507
+ const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
4508
+ const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
4509
+ return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
3838
4510
  }
3839
4511
 
3840
4512
  // src/device-apps/customActions.ts
3841
- var import_device_management_kit3 = require("@ledgerhq/device-management-kit");
4513
+ var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
3842
4514
  var import_rxjs = require("rxjs");
3843
4515
  var GetOsVersionDeviceAction = class {
3844
4516
  // eslint-disable-next-line no-useless-constructor, no-empty-function
@@ -3852,7 +4524,7 @@ var GetOsVersionDeviceAction = class {
3852
4524
  (async () => {
3853
4525
  try {
3854
4526
  subject.next({
3855
- status: import_device_management_kit3.DeviceActionStatus.Pending,
4527
+ status: import_device_management_kit2.DeviceActionStatus.Pending,
3856
4528
  intermediateValue: { requiredUserInteraction: "none" }
3857
4529
  });
3858
4530
  const result = await internalApi.sendCommand(new this._deps.GetOsVersionCommand());
@@ -3861,11 +4533,11 @@ var GetOsVersionDeviceAction = class {
3861
4533
  const errObj = result?.error;
3862
4534
  throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
3863
4535
  }
3864
- subject.next({ status: import_device_management_kit3.DeviceActionStatus.Completed, output: result.data });
4536
+ subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: result.data });
3865
4537
  subject.complete();
3866
4538
  } catch (err) {
3867
4539
  if (cancelled) return;
3868
- subject.next({ status: import_device_management_kit3.DeviceActionStatus.Error, error: err });
4540
+ subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
3869
4541
  subject.complete();
3870
4542
  }
3871
4543
  })();
@@ -3873,7 +4545,7 @@ var GetOsVersionDeviceAction = class {
3873
4545
  observable: subject.asObservable(),
3874
4546
  cancel: () => {
3875
4547
  cancelled = true;
3876
- subject.next({ status: import_device_management_kit3.DeviceActionStatus.Stopped });
4548
+ subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
3877
4549
  subject.complete();
3878
4550
  }
3879
4551
  };
@@ -3891,7 +4563,7 @@ var ListAvailableAppsDeviceAction = class {
3891
4563
  (async () => {
3892
4564
  try {
3893
4565
  subject.next({
3894
- status: import_device_management_kit3.DeviceActionStatus.Pending,
4566
+ status: import_device_management_kit2.DeviceActionStatus.Pending,
3895
4567
  intermediateValue: { requiredUserInteraction: "none" }
3896
4568
  });
3897
4569
  const osVersionResult = await internalApi.sendCommand(new this._GetOsVersionCommand());
@@ -3908,11 +4580,11 @@ var ListAvailableAppsDeviceAction = class {
3908
4580
  throw new Error(httpErr?.message ?? "Manager API getAppList failed");
3909
4581
  }
3910
4582
  const apps = either.extract();
3911
- subject.next({ status: import_device_management_kit3.DeviceActionStatus.Completed, output: apps });
4583
+ subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: apps });
3912
4584
  subject.complete();
3913
4585
  } catch (err) {
3914
4586
  if (cancelled) return;
3915
- subject.next({ status: import_device_management_kit3.DeviceActionStatus.Error, error: err });
4587
+ subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
3916
4588
  subject.complete();
3917
4589
  }
3918
4590
  })();
@@ -3920,7 +4592,7 @@ var ListAvailableAppsDeviceAction = class {
3920
4592
  observable: subject.asObservable(),
3921
4593
  cancel: () => {
3922
4594
  cancelled = true;
3923
- subject.next({ status: import_device_management_kit3.DeviceActionStatus.Stopped });
4595
+ subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
3924
4596
  subject.complete();
3925
4597
  }
3926
4598
  };
@@ -4013,7 +4685,7 @@ var DeviceApps = class {
4013
4685
  seTargetId: v.seTargetId,
4014
4686
  mcuTargetId: v.mcuTargetId,
4015
4687
  seVersion: v.seVersion,
4016
- seFlagsHex: bytesToHex2(v.seFlags),
4688
+ seFlagsHex: bytesToHex4(v.seFlags),
4017
4689
  mcuSephVersion: v.mcuSephVersion,
4018
4690
  mcuBootloaderVersion: v.mcuBootloaderVersion,
4019
4691
  hwVersion: v.hwVersion
@@ -4087,7 +4759,7 @@ var DeviceApps = class {
4087
4759
  }
4088
4760
  }
4089
4761
  };
4090
- function bytesToHex2(bytes) {
4762
+ function bytesToHex4(bytes) {
4091
4763
  if (!bytes) return "";
4092
4764
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
4093
4765
  }
@@ -4130,10 +4802,11 @@ var METHOD_PREFIX_TO_APP_NAME = {
4130
4802
  evm: "Ethereum",
4131
4803
  btc: "Bitcoin",
4132
4804
  sol: "Solana",
4133
- tron: "Tron"
4805
+ tron: "Tron",
4806
+ zcash: "Zcash"
4134
4807
  };
4135
4808
  var HARDWARE_ERROR_CODE_VALUES = new Set(
4136
- Object.values(import_hwk_adapter_core13.HardwareErrorCode).filter((value) => typeof value === "number")
4809
+ Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
4137
4810
  );
4138
4811
  var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
4139
4812
  var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
@@ -4170,6 +4843,10 @@ async function defaultLedgerKitImporter(pkg) {
4170
4843
  return import("@ledgerhq/device-signer-kit-bitcoin");
4171
4844
  case "@ledgerhq/device-signer-kit-solana":
4172
4845
  return import("@ledgerhq/device-signer-kit-solana");
4846
+ case "@ledgerhq/device-signer-kit-tron":
4847
+ return import("@ledgerhq/device-signer-kit-tron");
4848
+ case "@ledgerhq/device-signer-kit-zcash":
4849
+ return import("@ledgerhq/device-signer-kit-zcash");
4173
4850
  case "@ledgerhq/context-module":
4174
4851
  return import("@ledgerhq/context-module");
4175
4852
  default:
@@ -4229,6 +4906,7 @@ var LedgerConnectorBase = class {
4229
4906
  this._ctx = {
4230
4907
  emit: (event, data) => this._emit(event, data),
4231
4908
  invalidateSession: (sid) => this._invalidateSession(sid),
4909
+ teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
4232
4910
  wrapError: (err, opts) => this._wrapError(err, opts),
4233
4911
  getOrCreateDmk: () => this._getOrCreateDmk(),
4234
4912
  getDeviceManager: () => this._getDeviceManager(),
@@ -4280,7 +4958,7 @@ var LedgerConnectorBase = class {
4280
4958
  async searchDevices() {
4281
4959
  const dm = await this._getDeviceManager();
4282
4960
  const descriptors = await this._discoverDescriptors(dm);
4283
- const resolvedDescriptors = descriptors.filter((d) => !(0, import_hwk_adapter_core13.isKnownNonTargetHardwareVendor)(d, "ledger")).map((d) => ({
4961
+ const resolvedDescriptors = descriptors.filter((d) => !(0, import_hwk_adapter_core12.isKnownNonTargetHardwareVendor)(d, "ledger")).map((d) => ({
4284
4962
  descriptor: d,
4285
4963
  connectId: this._resolveConnectId(d)
4286
4964
  }));
@@ -4328,7 +5006,7 @@ var LedgerConnectorBase = class {
4328
5006
  `Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
4329
5007
  );
4330
5008
  err._tag = ERROR_TAG.BlePairingTimeout;
4331
- err.code = import_hwk_adapter_core13.HardwareErrorCode.BlePairingTimeout;
5009
+ err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
4332
5010
  reject(err);
4333
5011
  }, HANG_CEILING_MS);
4334
5012
  });
@@ -4361,7 +5039,7 @@ var LedgerConnectorBase = class {
4361
5039
  "Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
4362
5040
  );
4363
5041
  err._tag = ERROR_TAG.DeviceNotAdvertising;
4364
- err.code = import_hwk_adapter_core13.HardwareErrorCode.DeviceNotFound;
5042
+ err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
4365
5043
  throw err;
4366
5044
  };
4367
5045
  const doConnect = async (path) => {
@@ -4418,14 +5096,14 @@ var LedgerConnectorBase = class {
4418
5096
  this._resetSignersAndSessions();
4419
5097
  if (isLedgerBleConnectionType(this.connectionType)) {
4420
5098
  const tag = err?._tag;
4421
- if (isKnownConnectionTag(tag)) {
5099
+ if (isKnownConnectionTag(tag) && !isConnectionOpeningTag(tag)) {
4422
5100
  throw err;
4423
5101
  }
4424
5102
  const wrapped = new Error(
4425
5103
  "Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
4426
5104
  );
4427
5105
  wrapped._tag = ERROR_TAG.BleGattBondingFailed;
4428
- wrapped.code = import_hwk_adapter_core13.HardwareErrorCode.BlePairingTimeout;
5106
+ wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
4429
5107
  wrapped.originalError = err;
4430
5108
  throw wrapped;
4431
5109
  }
@@ -4503,7 +5181,7 @@ var LedgerConnectorBase = class {
4503
5181
  this._unwatchSessionState(sessionId);
4504
5182
  this._signerManager?.invalidate(sessionId);
4505
5183
  this._cancellers.get(sessionId)?.({
4506
- code: import_hwk_adapter_core13.HardwareErrorCode.DeviceDisconnected,
5184
+ code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
4507
5185
  tag: "DeviceDisconnected",
4508
5186
  message: "Device disconnected"
4509
5187
  });
@@ -4527,9 +5205,9 @@ var LedgerConnectorBase = class {
4527
5205
  if (isAppStuckByApdu(err)) {
4528
5206
  return {
4529
5207
  success: false,
4530
- error: (0, import_hwk_adapter_core13.serializeConnectorError)(
5208
+ error: (0, import_hwk_adapter_core12.serializeConnectorError)(
4531
5209
  Object.assign(new Error("Ledger app is unresponsive"), {
4532
- code: import_hwk_adapter_core13.HardwareErrorCode.DeviceAppStuck,
5210
+ code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
4533
5211
  _tag: ERROR_TAG.DeviceAppStuck,
4534
5212
  originalError: err
4535
5213
  })
@@ -4539,16 +5217,16 @@ var LedgerConnectorBase = class {
4539
5217
  if (isTransportStuck(err)) {
4540
5218
  return {
4541
5219
  success: false,
4542
- error: (0, import_hwk_adapter_core13.serializeConnectorError)(
5220
+ error: (0, import_hwk_adapter_core12.serializeConnectorError)(
4543
5221
  Object.assign(new Error("Device communication interrupted, please retry"), {
4544
- code: import_hwk_adapter_core13.HardwareErrorCode.TransportError,
5222
+ code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
4545
5223
  _tag: ERROR_TAG.DeviceTransportStuck,
4546
5224
  originalError: err
4547
5225
  })
4548
5226
  )
4549
5227
  };
4550
5228
  }
4551
- return { success: false, error: (0, import_hwk_adapter_core13.serializeConnectorError)(err) };
5229
+ return { success: false, error: (0, import_hwk_adapter_core12.serializeConnectorError)(err) };
4552
5230
  }
4553
5231
  }
4554
5232
  async _dispatch(sessionId, method, params) {
@@ -4587,6 +5265,11 @@ var LedgerConnectorBase = class {
4587
5265
  return solSignTransaction(ctx, sessionId, params);
4588
5266
  case "solSignMessage":
4589
5267
  return solSignMessage(ctx, sessionId, params);
5268
+ // ZCASH
5269
+ case "zcashGetFullViewingKey":
5270
+ return zcashGetFullViewingKey(ctx, sessionId, params);
5271
+ case "zcashGetShieldedAddress":
5272
+ return zcashGetShieldedAddress(ctx, sessionId, params);
4590
5273
  // TRON
4591
5274
  case "tronGetAddress":
4592
5275
  return tronGetAddress(ctx, sessionId, params);
@@ -4611,7 +5294,7 @@ var LedgerConnectorBase = class {
4611
5294
  try {
4612
5295
  return await apps.install(p.appName, ({ progress }) => {
4613
5296
  ctx.emit("ui-event", {
4614
- type: import_hwk_adapter_core13.EConnectorInteraction.AppInstallProgress,
5297
+ type: import_hwk_adapter_core12.EConnectorInteraction.AppInstallProgress,
4615
5298
  payload: {
4616
5299
  sessionId,
4617
5300
  appName: p.appName,
@@ -4620,7 +5303,7 @@ var LedgerConnectorBase = class {
4620
5303
  });
4621
5304
  });
4622
5305
  } catch (err) {
4623
- ctx.invalidateSession(sessionId);
5306
+ ctx.teardownSecureChannelSession(sessionId);
4624
5307
  throw ctx.wrapError(err);
4625
5308
  } finally {
4626
5309
  ctx.clearCanceller(sessionId);
@@ -4710,7 +5393,7 @@ var LedgerConnectorBase = class {
4710
5393
  );
4711
5394
  return { isGenuine: output.isGenuine, deviceId };
4712
5395
  } catch (err) {
4713
- ctx.invalidateSession(sessionId);
5396
+ ctx.teardownSecureChannelSession(sessionId);
4714
5397
  throw ctx.wrapError(err);
4715
5398
  } finally {
4716
5399
  ctx.clearCanceller(sessionId);
@@ -4829,9 +5512,26 @@ var LedgerConnectorBase = class {
4829
5512
  }
4830
5513
  return this._deviceAppsManager;
4831
5514
  }
5515
+ // DeviceAppsManager is a per-call factory with no cached session state, so
5516
+ // there is nothing to invalidate for it here.
4832
5517
  _invalidateSession(sessionId) {
4833
5518
  this._signerManager?.invalidate(sessionId);
4834
5519
  }
5520
+ /**
5521
+ * Fire the canceller so DMK closes the secure-channel WebSocket. The DMK device
5522
+ * session is kept: unlock/retry recovery reuses it.
5523
+ */
5524
+ _teardownSecureChannelSession(sessionId) {
5525
+ const cancel = this._cancellers.get(sessionId);
5526
+ this._cancellers.delete(sessionId);
5527
+ if (cancel) {
5528
+ try {
5529
+ cancel();
5530
+ } catch {
5531
+ }
5532
+ }
5533
+ this._invalidateSession(sessionId);
5534
+ }
4835
5535
  /**
4836
5536
  * Replace an old session with a new one after app switch.
4837
5537
  * Emits device-connect so the adapter updates its _sessions Map.
@@ -4916,7 +5616,7 @@ var LedgerConnectorBase = class {
4916
5616
  * at every catch site. Falls through unchanged for unknown methods.
4917
5617
  */
4918
5618
  _ctxForMethod(method) {
4919
- const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
5619
+ const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
4920
5620
  const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
4921
5621
  if (!defaultAppName) return this._ctx;
4922
5622
  return {
@@ -4950,6 +5650,204 @@ var LedgerConnectorBase = class {
4950
5650
  return error;
4951
5651
  }
4952
5652
  };
5653
+
5654
+ // src/transport/DmkTransport.ts
5655
+ var import_hw_transport = __toESM(require("@ledgerhq/hw-transport"));
5656
+ var DmkTransport = class extends import_hw_transport.default {
5657
+ constructor(dmk, sessionId) {
5658
+ super();
5659
+ this._dmk = dmk;
5660
+ this._sessionId = sessionId;
5661
+ }
5662
+ async exchange(apdu) {
5663
+ const response = await this._dmk.sendApdu({
5664
+ sessionId: this._sessionId,
5665
+ apdu: new Uint8Array(apdu)
5666
+ });
5667
+ const { data, statusCode } = response;
5668
+ const result = Buffer.alloc(data.length + 2);
5669
+ if (data.length > 0) {
5670
+ result.set(data, 0);
5671
+ }
5672
+ result.set(statusCode, data.length);
5673
+ return result;
5674
+ }
5675
+ async close() {
5676
+ }
5677
+ };
5678
+
5679
+ // src/app/AppManager.ts
5680
+ var import_device_management_kit3 = require("@ledgerhq/device-management-kit");
5681
+ var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
5682
+ var APP_NAME_MAP = {
5683
+ ETH: "Ethereum",
5684
+ BTC: "Bitcoin",
5685
+ SOL: "Solana",
5686
+ TRX: "Tron",
5687
+ ZEC: "Zcash",
5688
+ XRP: "XRP",
5689
+ ADA: "Cardano",
5690
+ DOT: "Polkadot",
5691
+ ATOM: "Cosmos"
5692
+ };
5693
+ var DASHBOARD_APP_NAME = "BOLOS";
5694
+ var AppManager = class {
5695
+ constructor(dmk, options) {
5696
+ this._dmk = dmk;
5697
+ this._waitMs = options?.waitMs ?? 1e3;
5698
+ this._maxRetries = options?.maxRetries ?? 10;
5699
+ }
5700
+ /**
5701
+ * Return the Ledger app name for a given chain ticker,
5702
+ * or undefined if the chain is not supported.
5703
+ */
5704
+ static getAppName(chain) {
5705
+ return APP_NAME_MAP[chain];
5706
+ }
5707
+ /**
5708
+ * Ensure the target app is open on the device identified by `sessionId`.
5709
+ *
5710
+ * Flow:
5711
+ * 1. Check the currently running app.
5712
+ * 2. If it is already the target, return immediately.
5713
+ * 3. If a different app is running (not dashboard), close it first.
5714
+ * 4. Open the target app.
5715
+ * 5. Poll until the device confirms the target app is running.
5716
+ */
5717
+ /**
5718
+ * @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
5719
+ * device is about to display "Open <app>" on screen and wait for the
5720
+ * user's button press. UI consumers should show their "open app" prompt
5721
+ * in response. NOT called when the target app is already open (no user
5722
+ * interaction needed in that case).
5723
+ *
5724
+ * Important: OpenAppCommand is blocking. It does not resolve until the user
5725
+ * has physically confirmed on the device, so anything that runs AFTER
5726
+ * `await this._openApp(...)` lands AFTER the prompt is already gone.
5727
+ * Hence the callback must fire BEFORE that await.
5728
+ */
5729
+ async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
5730
+ const currentApp = await this._getCurrentApp(sessionId);
5731
+ if (currentApp === targetAppName) {
5732
+ return;
5733
+ }
5734
+ if (!this._isDashboard(currentApp)) {
5735
+ await this._closeCurrentApp(sessionId);
5736
+ await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
5737
+ }
5738
+ onConfirmOnDevice?.();
5739
+ await this._openApp(sessionId, targetAppName);
5740
+ await this._waitForApp(sessionId, targetAppName);
5741
+ }
5742
+ // ---------------------------------------------------------------------------
5743
+ // Private helpers
5744
+ // ---------------------------------------------------------------------------
5745
+ async _getCurrentApp(sessionId) {
5746
+ const result = await this._dmk.sendCommand({
5747
+ sessionId,
5748
+ command: new import_device_management_kit3.GetAppAndVersionCommand()
5749
+ });
5750
+ if ((0, import_device_management_kit3.isSuccessCommandResult)(result)) {
5751
+ debugLog("[AppManager] currentApp:", result.data.name);
5752
+ return result.data.name;
5753
+ }
5754
+ const errResult = result;
5755
+ const dmkErr = errResult.error ?? {};
5756
+ const original = dmkErr.originalError;
5757
+ debugLog(
5758
+ "[AppManager] _getCurrentApp failed sessionId=",
5759
+ sessionId,
5760
+ "tag=",
5761
+ dmkErr._tag,
5762
+ "errorCode=",
5763
+ dmkErr.errorCode,
5764
+ "message=",
5765
+ dmkErr.message,
5766
+ "originalErrorMessage=",
5767
+ original?.message ?? String(original ?? "")
5768
+ );
5769
+ throw Object.assign(
5770
+ new Error(
5771
+ dmkErr.message ?? "Failed to get current app from device"
5772
+ ),
5773
+ {
5774
+ _tag: dmkErr._tag,
5775
+ errorCode: dmkErr.errorCode,
5776
+ originalError: original
5777
+ }
5778
+ );
5779
+ }
5780
+ async _openApp(sessionId, appName) {
5781
+ const result = await this._dmk.sendCommand({
5782
+ sessionId,
5783
+ command: new import_device_management_kit3.OpenAppCommand({ appName })
5784
+ });
5785
+ if (!(0, import_device_management_kit3.isSuccessCommandResult)(result)) {
5786
+ const dmkErr = result.error;
5787
+ const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
5788
+ const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
5789
+ debugLog(
5790
+ "[AppManager] openApp failed:",
5791
+ appName,
5792
+ "errorCode:",
5793
+ errorCode,
5794
+ "tag:",
5795
+ dmkErr._tag
5796
+ );
5797
+ let code;
5798
+ if (errorCode === "6807" || /unknown application/i.test(message)) {
5799
+ code = import_hwk_adapter_core13.HardwareErrorCode.AppNotInstalled;
5800
+ } else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
5801
+ code = import_hwk_adapter_core13.HardwareErrorCode.UserRejected;
5802
+ }
5803
+ throw Object.assign(new Error(`Failed to open "${appName}"`), {
5804
+ _tag: ERROR_TAG.OpenAppCommand,
5805
+ code,
5806
+ errorCode,
5807
+ statusCode: errorCode,
5808
+ appName,
5809
+ originalError: dmkErr
5810
+ });
5811
+ }
5812
+ }
5813
+ async _closeCurrentApp(sessionId) {
5814
+ debugLog("[AppManager] closeCurrentApp");
5815
+ await this._dmk.sendCommand({
5816
+ sessionId,
5817
+ command: new import_device_management_kit3.CloseAppCommand()
5818
+ });
5819
+ }
5820
+ /**
5821
+ * Poll the device until the expected app is reported as running,
5822
+ * or throw after `_maxRetries` attempts.
5823
+ */
5824
+ async _waitForApp(sessionId, expectedAppName) {
5825
+ let lastSeen = "";
5826
+ for (let i = 0; i < this._maxRetries; i++) {
5827
+ await this._wait();
5828
+ const current = await this._getCurrentApp(sessionId);
5829
+ lastSeen = current;
5830
+ if (current === expectedAppName) {
5831
+ return;
5832
+ }
5833
+ }
5834
+ debugLog(
5835
+ "[AppManager] waitForApp exhausted: expected=",
5836
+ expectedAppName,
5837
+ "lastSeen=",
5838
+ lastSeen
5839
+ );
5840
+ throw new Error(
5841
+ `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
5842
+ );
5843
+ }
5844
+ _isDashboard(appName) {
5845
+ return appName === DASHBOARD_APP_NAME;
5846
+ }
5847
+ _wait() {
5848
+ return new Promise((resolve) => setTimeout(resolve, this._waitMs));
5849
+ }
5850
+ };
4953
5851
  // Annotate the CommonJS export names for ESM import in node:
4954
5852
  0 && (module.exports = {
4955
5853
  AppManager,
@@ -4961,6 +5859,7 @@ var LedgerConnectorBase = class {
4961
5859
  SignerEth,
4962
5860
  SignerManager,
4963
5861
  SignerSol,
5862
+ SignerZcash,
4964
5863
  debugLog,
4965
5864
  deviceActionToPromise,
4966
5865
  isDeviceLockedError,