@onekeyfe/hwk-ledger-adapter 1.2.2-alpha.8 → 1.2.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,30 +6,41 @@ import {
6
6
  DeviceJobQueue,
7
7
  EConnectorInteraction,
8
8
  HardwareErrorCode as HardwareErrorCode3,
9
+ InteractionRegistry,
10
+ SDK,
9
11
  TypedEventEmitter,
10
12
  UI_REQUEST,
11
13
  UI_REQUEST_PREEMPTED_TAG,
12
14
  UiRequestRegistry,
15
+ canReplayHardwareMethodAfterTransportFailure,
13
16
  createHwkError,
14
17
  deriveDeviceFingerprint,
15
18
  failure as failure2,
19
+ isHardwareInteractionId,
20
+ isHwkRecoveryHint,
21
+ operationMayHaveCompletedParams,
16
22
  rehydrateConnectorError,
23
+ requestBleDeviceSelection,
24
+ requestSaveDeviceBinding,
25
+ resolveSearchTargetReusePolicy,
17
26
  success
18
27
  } from "@onekeyfe/hwk-adapter-core";
19
28
 
20
29
  // src/errors.ts
21
- import { HardwareErrorCode, enrichErrorMessage } from "@onekeyfe/hwk-adapter-core";
22
- var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
23
- function createMultipleUsbLedgerDevicesError() {
24
- return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
25
- code: HardwareErrorCode.DeviceOneDeviceOnly
26
- });
27
- }
28
- function ledgerFailure(code, error, appName, tag, params) {
30
+ import {
31
+ HardwareErrorCode,
32
+ defaultOriginForCode,
33
+ defaultRecoveryForCode,
34
+ enrichErrorMessage
35
+ } from "@onekeyfe/hwk-adapter-core";
36
+ function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
29
37
  const payload = { error, code };
30
38
  if (appName !== void 0) payload.appName = appName;
31
39
  if (tag !== void 0) payload._tag = tag;
32
40
  if (params !== void 0) payload.params = params;
41
+ const resolvedOrigin = origin ?? defaultOriginForCode(code);
42
+ if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
43
+ payload.recovery = recovery ?? defaultRecoveryForCode(code);
33
44
  return { success: false, payload };
34
45
  }
35
46
  var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
@@ -213,7 +224,6 @@ function isBlePairingFailureError(err) {
213
224
  return false;
214
225
  }
215
226
  var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
216
- ERROR_TAG.DeviceLocked,
217
227
  ERROR_TAG.DeviceNotAdvertising,
218
228
  ERROR_TAG.BlePairingTimeout,
219
229
  ERROR_TAG.BleGattBondingFailed,
@@ -448,11 +458,21 @@ function mapLedgerError(err, opts) {
448
458
  }
449
459
  const errAppName = err && typeof err === "object" ? err.appName : void 0;
450
460
  const appName = errAppName ?? opts?.defaultAppName;
451
- return { code, message: enrichErrorMessage(code, originalMessage), appName };
461
+ return {
462
+ code,
463
+ message: enrichErrorMessage(code, originalMessage),
464
+ origin: defaultOriginForCode(code),
465
+ appName
466
+ };
452
467
  }
453
468
 
454
469
  // src/adapter/methods/allNetworkGetAddress.ts
455
- import { HardwareErrorCode as HardwareErrorCode2, failure, runAllNetworkGetAddress } from "@onekeyfe/hwk-adapter-core";
470
+ import {
471
+ HardwareErrorCode as HardwareErrorCode2,
472
+ failure,
473
+ resolveHardwareOperationTarget,
474
+ runAllNetworkGetAddress
475
+ } from "@onekeyfe/hwk-adapter-core";
456
476
 
457
477
  // src/utils/sdkEventBus.ts
458
478
  var listeners = /* @__PURE__ */ new Set();
@@ -509,57 +529,81 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
509
529
  var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
510
530
  function createAllNetworkGetAddress({
511
531
  callChain,
512
- getChainFingerprint
532
+ getChainFingerprint,
533
+ retainInteraction,
534
+ errorToFailure
513
535
  }) {
514
536
  return async function allNetworkGetAddress(connectId, _deviceId, params) {
515
- debugLog("[LedgerAdapter][REQ]", { method: "allNetworkGetAddress", connectId, params });
537
+ debugLog("[LedgerAdapter][REQ]", {
538
+ method: "allNetworkGetAddress",
539
+ connectId,
540
+ itemCount: params.bundle.length
541
+ });
542
+ const target = resolveHardwareOperationTarget(connectId, params.interactionId, "ledger");
543
+ if (!target.success) return target;
544
+ const effectiveTargetId = target.payload.targetId ?? "";
545
+ let releaseInteractionRetention;
546
+ try {
547
+ releaseInteractionRetention = target.payload.interactionId ? retainInteraction(target.payload.interactionId) : void 0;
548
+ } catch (error) {
549
+ return errorToFailure(error);
550
+ }
516
551
  const installContext = {};
517
552
  const commonParams = {
518
- autoInstallApp: params.autoInstallApp
553
+ autoInstallApp: params.autoInstallApp,
554
+ interactionId: target.payload.interactionId,
555
+ knownConnections: params.knownConnections,
556
+ extra: params.extra,
557
+ allowDeviceSelection: params.allowDeviceSelection
519
558
  };
520
559
  const chainFingerprints = /* @__PURE__ */ new Map();
521
- const result = await runAllNetworkGetAddress({
522
- connectId,
523
- deviceId: _deviceId,
524
- params,
525
- normalizeItem: normalizeLedgerAllNetworkItem,
526
- buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
527
- callItem: async ({ method, chain, item }) => {
528
- const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
529
- return callAllNetworkMethod(
530
- callChain,
531
- connectId,
532
- itemDeviceId,
533
- method,
560
+ try {
561
+ const result = await runAllNetworkGetAddress({
562
+ connectId: effectiveTargetId,
563
+ deviceId: _deviceId,
564
+ params,
565
+ normalizeItem: normalizeLedgerAllNetworkItem,
566
+ buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
567
+ callItem: async ({ method, chain, item }) => {
568
+ const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
569
+ return callAllNetworkMethod(
570
+ callChain,
571
+ effectiveTargetId,
572
+ itemDeviceId,
573
+ method,
574
+ item,
575
+ commonParams,
576
+ installContext
577
+ );
578
+ },
579
+ attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
580
+ getChainFingerprint,
581
+ effectiveTargetId,
534
582
  item,
535
- commonParams,
583
+ chain,
584
+ payload,
585
+ chainFingerprints,
536
586
  installContext
537
- );
538
- },
539
- attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
540
- getChainFingerprint,
541
- connectId,
542
- item,
543
- chain,
544
- payload,
545
- chainFingerprints
546
- ),
547
- shouldAbortBundle: isTopLevelAllNetworkFailure,
548
- buildTopLevelFailure: (response) => {
549
- const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
550
- return failure(
551
- code,
552
- response.payload?.error ?? "All-network get-address aborted",
553
- response.payload?.params
554
- );
555
- }
556
- });
557
- debugLog("[LedgerAdapter][RES]", {
558
- method: "allNetworkGetAddress",
559
- success: result.success,
560
- payload: result
561
- });
562
- return result;
587
+ ),
588
+ shouldAbortBundle: isTopLevelAllNetworkFailure,
589
+ buildTopLevelFailure: (response) => {
590
+ const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
591
+ return failure(
592
+ code,
593
+ response.payload?.error ?? "All-network get-address aborted",
594
+ response.payload?.params
595
+ );
596
+ }
597
+ });
598
+ debugLog("[LedgerAdapter][RES]", {
599
+ method: "allNetworkGetAddress",
600
+ success: result.success,
601
+ payload: result
602
+ });
603
+ return result;
604
+ } finally {
605
+ releaseInteractionRetention?.();
606
+ }
563
607
  };
564
608
  }
565
609
  function isTopLevelAllNetworkFailure(response) {
@@ -567,7 +611,7 @@ function isTopLevelAllNetworkFailure(response) {
567
611
  return false;
568
612
  }
569
613
  const code = response.payload?.code;
570
- return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
614
+ return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.DeviceDisconnected || code === HardwareErrorCode2.OperationTimeout || code === HardwareErrorCode2.TransportError || code === HardwareErrorCode2.InteractionEnded || code === HardwareErrorCode2.InteractionNotFound || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
571
615
  }
572
616
  function getItemDeviceId(item) {
573
617
  const { deviceId } = item;
@@ -597,8 +641,13 @@ function normalizeLedgerAllNetworkItem(method, item) {
597
641
  const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
598
642
  return coin ? { ...item, coin } : item;
599
643
  }
600
- async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
601
- const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(getChainFingerprint, connectId, chain);
644
+ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context) {
645
+ const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(
646
+ getChainFingerprint,
647
+ context.connection?.connectId ?? connectId,
648
+ chain,
649
+ context
650
+ );
602
651
  if (!fingerprint) {
603
652
  return buildFingerprintBootstrapFailure(item, chain);
604
653
  }
@@ -619,8 +668,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
619
668
  }
620
669
  };
621
670
  }
622
- async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
623
- const response = await getChainFingerprint(connectId, chain);
671
+ async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
672
+ const response = await getChainFingerprint(connectId, chain, context);
624
673
  return response.success ? response.payload : "";
625
674
  }
626
675
  function buildFingerprintBootstrapFailure(item, chain) {
@@ -725,8 +774,27 @@ var _LedgerAdapter = class _LedgerAdapter {
725
774
  constructor(connector, options) {
726
775
  this.vendor = "ledger";
727
776
  this.emitter = new TypedEventEmitter();
777
+ this._interactions = new InteractionRegistry({
778
+ vendor: "ledger",
779
+ onEnded: (interaction, reason) => {
780
+ const binding = this._pendingInteractionBindings.get(interaction.interactionId);
781
+ if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
782
+ this._finishBleBinding("cancelled");
783
+ }
784
+ this._pendingInteractionBindings.delete(interaction.interactionId);
785
+ this.emitter.emit(SDK.INTERACTION_ENDED, {
786
+ type: SDK.INTERACTION_ENDED,
787
+ payload: { interactionId: interaction.interactionId, reason }
788
+ });
789
+ if (reason === "timeout") {
790
+ void this._releaseInteractionConnection(interaction);
791
+ }
792
+ }
793
+ });
728
794
  this._discoveredDevices = /* @__PURE__ */ new Map();
729
795
  this._sessions = /* @__PURE__ */ new Map();
796
+ this._pendingInteractionBindings = /* @__PURE__ */ new Map();
797
+ this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
730
798
  this._uiRegistry = new UiRequestRegistry();
731
799
  // BTC App rejects account index >= 100 unless display=true. Cached per
732
800
  // adapter instance: first 100+ path asks the user once via UI request,
@@ -740,10 +808,30 @@ var _LedgerAdapter = class _LedgerAdapter {
740
808
  this._deviceAuthenticityQueueTail = Promise.resolve();
741
809
  // Shared across concurrent callers — only `cancel()` aborts.
742
810
  this._doConnectAbortController = null;
811
+ this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
812
+ this._connectorIdleWaiters = /* @__PURE__ */ new Set();
813
+ this._resetPromise = null;
814
+ this._stateGeneration = 0;
815
+ this._connectorTeardownTail = Promise.resolve();
816
+ this._pendingConnectorTeardowns = 0;
817
+ this._activeInteractionJobs = /* @__PURE__ */ new Set();
818
+ this._pendingInteractionDisconnects = /* @__PURE__ */ new Set();
743
819
  this._installProgressLastEmittedValue = -Infinity;
744
820
  this.allNetworkGetAddress = createAllNetworkGetAddress({
745
821
  callChain: this.callChain.bind(this),
746
- getChainFingerprint: (connectId, chain) => this.getChainFingerprint(connectId, "", chain)
822
+ getChainFingerprint: async (connectId, chain, context) => {
823
+ try {
824
+ const fingerprint = await this._computeChainFingerprint(
825
+ chain,
826
+ (method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
827
+ );
828
+ return success(fingerprint);
829
+ } catch (error) {
830
+ return this.errorToFailure(error);
831
+ }
832
+ },
833
+ retainInteraction: (interactionId) => this._interactions.retain(interactionId),
834
+ errorToFailure: (error) => this.errorToFailure(error)
747
835
  });
748
836
  // ---------------------------------------------------------------------------
749
837
  // Private helpers
@@ -753,7 +841,7 @@ var _LedgerAdapter = class _LedgerAdapter {
753
841
  *
754
842
  * - If a session already exists for the given connectId, reuse it.
755
843
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
756
- * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
844
+ * - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
757
845
  */
758
846
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
759
847
  this._connectingPromise = null;
@@ -770,6 +858,12 @@ var _LedgerAdapter = class _LedgerAdapter {
770
858
  });
771
859
  };
772
860
  this.deviceDisconnectHandler = (data) => {
861
+ const activeInteraction = this._interactions.findActiveByConnectionKey(data.connectId);
862
+ if (activeInteraction && this._activeInteractionJobs.has(activeInteraction.interactionId)) {
863
+ this._pendingInteractionDisconnects.add(activeInteraction.interactionId);
864
+ } else {
865
+ this._interactions.endByConnectionKey(data.connectId, "disconnect");
866
+ }
773
867
  this._discoveredDevices.delete(data.connectId);
774
868
  this._sessions.delete(data.connectId);
775
869
  this.emitter.emit(DEVICE.DISCONNECT, {
@@ -826,11 +920,28 @@ var _LedgerAdapter = class _LedgerAdapter {
826
920
  this._jobQueue = new DeviceJobQueue();
827
921
  this.registerEventListeners();
828
922
  }
923
+ _finishBleBinding(status) {
924
+ const selectionRequestId = this._bindingSelectionRequestId;
925
+ this._bindingSelectionRequestId = void 0;
926
+ if (selectionRequestId)
927
+ this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
928
+ type: UI_REQUEST.DEVICE_BINDING_STATUS,
929
+ payload: { selectionRequestId, status }
930
+ });
931
+ }
932
+ _isBleConnection() {
933
+ return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
934
+ }
829
935
  // Transport
830
936
  get activeTransport() {
831
- return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
937
+ return this._isBleConnection() ? "ble" : "hid";
832
938
  }
833
939
  getAvailableTransports() {
940
+ if (this.connector.availableTransports) {
941
+ return this.connector.availableTransports.map(
942
+ (transport) => transport === "ble" ? "ble" : "hid"
943
+ );
944
+ }
834
945
  return this.activeTransport ? [this.activeTransport] : [];
835
946
  }
836
947
  // Connector is bound at construction; switching requires a new adapter.
@@ -847,21 +958,39 @@ var _LedgerAdapter = class _LedgerAdapter {
847
958
  * The next operation will re-discover and re-connect automatically.
848
959
  */
849
960
  resetState() {
961
+ void this._resetStateAndDisconnectSessions();
962
+ }
963
+ _resetStateAndDisconnectSessions() {
964
+ if (this._resetPromise) return this._resetPromise;
965
+ const sessionIds = new Set(this._sessions.values());
966
+ this._stateGeneration += 1;
967
+ this._finishBleBinding("cancelled");
968
+ this._interactions.endAll("runtime-reset");
969
+ this._doConnectAbortController?.abort();
850
970
  this._discoveredDevices.clear();
851
971
  this._sessions.clear();
972
+ this._verifiedBleReconnectTargets.clear();
852
973
  this._connectingPromise = null;
974
+ this._doConnectAbortController = null;
853
975
  this._uiRegistry.reset();
854
976
  this._jobQueue.clear();
855
977
  this._btcHighIndexConfirmedThisSession = false;
978
+ const resetPromise = this._runConnectorTeardown(async () => {
979
+ for (const sessionId of sessionIds) {
980
+ await this.connector.disconnect(sessionId).catch(() => void 0);
981
+ }
982
+ });
983
+ this._resetPromise = resetPromise;
984
+ return resetPromise.finally(() => {
985
+ if (this._resetPromise === resetPromise) {
986
+ this._resetPromise = null;
987
+ }
988
+ });
856
989
  }
857
990
  async dispose() {
858
- this._uiRegistry.reset();
859
- this._jobQueue.clear();
991
+ await this._resetStateAndDisconnectSessions();
860
992
  this.unregisterEventListeners();
861
993
  this.connector.reset();
862
- this._discoveredDevices.clear();
863
- this._sessions.clear();
864
- this._btcHighIndexConfirmedThisSession = false;
865
994
  this.emitter.removeAllListeners();
866
995
  }
867
996
  uiResponse(response) {
@@ -871,17 +1000,32 @@ var _LedgerAdapter = class _LedgerAdapter {
871
1000
  // Device management
872
1001
  // ---------------------------------------------------------------------------
873
1002
  async searchDevices(options) {
1003
+ return this._searchDevices(options);
1004
+ }
1005
+ async _searchDevices(options, signal) {
874
1006
  debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
875
1007
  try {
876
1008
  if (options?.resetSession) {
877
- this._doConnectAbortController?.abort();
878
- this._sessions.clear();
879
- this._connectingPromise = null;
880
- this._doConnectAbortController = null;
881
- this._btcHighIndexConfirmedThisSession = false;
1009
+ await this._resetStateAndDisconnectSessions();
1010
+ } else {
1011
+ await this._connectorTeardownTail;
1012
+ }
1013
+ await this._ensureDevicePermission(void 0, void 0, signal);
1014
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1015
+ const stateGeneration = this._stateGeneration;
1016
+ const devices = await this.connector.searchDevices(
1017
+ options?.transportType ? {
1018
+ transportType: options.transportType,
1019
+ waitForAll: options.waitForAllTransports
1020
+ } : void 0
1021
+ );
1022
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1023
+ if (stateGeneration !== this._stateGeneration) {
1024
+ throw createHwkError({
1025
+ code: HardwareErrorCode3.UserAborted,
1026
+ message: "Ledger discovery was reset"
1027
+ });
882
1028
  }
883
- await this._ensureDevicePermission();
884
- const devices = await this.connector.searchDevices();
885
1029
  this._discoveredDevices.clear();
886
1030
  for (const d of devices) {
887
1031
  if (d.connectId) {
@@ -889,7 +1033,7 @@ var _LedgerAdapter = class _LedgerAdapter {
889
1033
  }
890
1034
  }
891
1035
  if (this._discoveredDevices.size === 0) {
892
- await this._ensureDevicePermission();
1036
+ await this._ensureDevicePermission(void 0, void 0, signal);
893
1037
  }
894
1038
  const result = Array.from(this._discoveredDevices.values());
895
1039
  debugLog("[LedgerAdapter][RES]", {
@@ -908,41 +1052,172 @@ var _LedgerAdapter = class _LedgerAdapter {
908
1052
  throw err;
909
1053
  }
910
1054
  }
1055
+ async searchDeviceTargets(options) {
1056
+ const devices = await this.searchDevices(options);
1057
+ return devices.map((device) => ({
1058
+ searchTargetId: device.connectId,
1059
+ searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
1060
+ vendor: "ledger",
1061
+ connectionType: device.connectionType,
1062
+ kind: "physical",
1063
+ label: device.label,
1064
+ model: device.model,
1065
+ modelName: device.modelName,
1066
+ serialNumber: device.serialNumber
1067
+ }));
1068
+ }
1069
+ async listConnectionTargets(options) {
1070
+ const targets = await this.searchDeviceTargets(options);
1071
+ return targets.map(({ searchTargetId, ...target }) => ({
1072
+ ...target,
1073
+ targetId: searchTargetId
1074
+ }));
1075
+ }
911
1076
  // USB single-session invariant: evict all sessions, best-effort (see connectDevice).
912
- async _evictAllSessions() {
1077
+ async _evictAllSessions(preserveInteractionId) {
1078
+ this._interactions.endAll("explicit", preserveInteractionId);
913
1079
  if (this._sessions.size === 0) return;
914
1080
  const stale = [...this._sessions.values()];
915
1081
  this._sessions.clear();
916
- for (const sid of stale) {
917
- try {
918
- await this.connector.disconnect(sid);
919
- } catch {
1082
+ await this._runConnectorTeardown(async () => {
1083
+ for (const sid of stale) {
1084
+ try {
1085
+ await this.connector.disconnect(sid);
1086
+ } catch {
1087
+ }
920
1088
  }
921
- }
1089
+ });
922
1090
  }
923
1091
  static _createDeviceBusyError(method) {
924
1092
  return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
925
1093
  code: HardwareErrorCode3.DeviceBusy
926
1094
  });
927
1095
  }
928
- async connectDevice(connectId) {
1096
+ async connectDevice(searchTargetId) {
1097
+ const connected = await this._connectTarget(searchTargetId);
1098
+ if (!connected.success) return connected;
1099
+ return this._createInteraction(searchTargetId, connected.payload);
1100
+ }
1101
+ async acquireInteraction(connectId, context) {
1102
+ try {
1103
+ return await this._jobQueue.enqueue(
1104
+ connectId || "__ledger_acquire__",
1105
+ async (signal) => {
1106
+ const transport = this._isBleConnection() ? "ble" : "usb";
1107
+ const hint = context.knownConnections?.find(
1108
+ (connection) => connection.transport === transport
1109
+ );
1110
+ const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
1111
+ await this._ensureDevicePermission(target, void 0, signal);
1112
+ const attempt = {
1113
+ ...context,
1114
+ extra: context.extra ? { ...context.extra } : void 0,
1115
+ previousConnectId: target
1116
+ };
1117
+ try {
1118
+ const resolvedConnectId = await this.ensureConnected(
1119
+ target,
1120
+ signal,
1121
+ true,
1122
+ void 0,
1123
+ attempt
1124
+ );
1125
+ _LedgerAdapter._throwIfAborted(signal);
1126
+ const result = this._createInteraction(connectId, resolvedConnectId);
1127
+ if (result.success && attempt.selectedConnection) {
1128
+ this._pendingInteractionBindings.set(result.payload, attempt);
1129
+ }
1130
+ return result;
1131
+ } catch (error) {
1132
+ this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
1133
+ throw error;
1134
+ }
1135
+ },
1136
+ {
1137
+ label: "acquireInteraction",
1138
+ rejectIfBusy: true,
1139
+ busyError: _LedgerAdapter._createDeviceBusyError("acquireInteraction")
1140
+ }
1141
+ );
1142
+ } catch (error) {
1143
+ return this.errorToFailure(error);
1144
+ }
1145
+ }
1146
+ _createInteraction(searchTargetId, resolvedConnectId) {
1147
+ this._interactions.endByConnectionKey(resolvedConnectId, "explicit");
1148
+ const sessionId = this._sessions.get(resolvedConnectId);
1149
+ if (sessionId) this._interactions.endByConnectionKey(sessionId, "explicit");
1150
+ const device = this._discoveredDevices.get(resolvedConnectId) ?? {
1151
+ vendor: "ledger",
1152
+ model: "unknown",
1153
+ firmwareVersion: "",
1154
+ deviceId: "",
1155
+ connectId: resolvedConnectId,
1156
+ connectionType: this.connector.connectionType
1157
+ };
1158
+ const interaction = this._interactions.create({
1159
+ searchTargetId,
1160
+ connectId: resolvedConnectId,
1161
+ device,
1162
+ connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
1163
+ });
1164
+ return success(interaction.interactionId);
1165
+ }
1166
+ async _connectTarget(connectId, preserveInteractionId, signal) {
929
1167
  debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
930
1168
  try {
931
- if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
1169
+ this._assertConnectorReady("connectDevice");
1170
+ const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
1171
+ if (discoveredType === "usb" || discoveredType === "ble") {
1172
+ this._activeConnectionType = discoveredType;
1173
+ }
1174
+ if (this._isBleConnection() && !connectId) {
932
1175
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
933
1176
  code: HardwareErrorCode3.DeviceNotFound
934
1177
  });
935
1178
  }
936
- if (!isLedgerBleConnectionType(this.connector.connectionType)) {
937
- await this._evictAllSessions();
1179
+ await this._ensureDevicePermission(connectId, void 0, signal);
1180
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1181
+ if (this.connector.availableTransports && this.connector.availableTransports.length > 1) {
1182
+ await this._evictAllSessions(preserveInteractionId);
1183
+ } else if (this._isBleConnection()) {
1184
+ const previousSessionId = this._sessions.get(connectId);
1185
+ this._interactions.endByConnectionKey(connectId, "explicit", preserveInteractionId);
1186
+ if (previousSessionId) {
1187
+ this._interactions.endByConnectionKey(
1188
+ previousSessionId,
1189
+ "explicit",
1190
+ preserveInteractionId
1191
+ );
1192
+ this._sessions.delete(connectId);
1193
+ await this.connector.disconnect(previousSessionId).catch(() => void 0);
1194
+ }
1195
+ } else {
1196
+ await this._evictAllSessions(preserveInteractionId);
1197
+ }
1198
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1199
+ const stateGeneration = this._stateGeneration;
1200
+ const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
1201
+ let session;
1202
+ try {
1203
+ session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
1204
+ transportType: this._activeConnectionType ?? "usb"
1205
+ }) : await this.connector.connect(connectId);
1206
+ if (signal?.aborted || stateGeneration !== this._stateGeneration) {
1207
+ await this.connector.disconnect(session.sessionId).catch(() => void 0);
1208
+ throw Object.assign(new Error("Ledger connection aborted"), {
1209
+ code: HardwareErrorCode3.UserAborted
1210
+ });
1211
+ }
1212
+ } finally {
1213
+ releaseOperation();
938
1214
  }
939
- await this._ensureDevicePermission(connectId);
940
- const session = await this.connector.connect(connectId);
941
- this._sessions.set(connectId, session.sessionId);
1215
+ const resolvedConnectId = session.deviceInfo?.connectId || connectId;
1216
+ this._sessions.set(resolvedConnectId, session.sessionId);
942
1217
  if (session.deviceInfo) {
943
- this._discoveredDevices.set(connectId, session.deviceInfo);
1218
+ this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
944
1219
  }
945
- const result = success(connectId);
1220
+ const result = success(resolvedConnectId);
946
1221
  debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
947
1222
  return result;
948
1223
  } catch (err) {
@@ -955,30 +1230,60 @@ var _LedgerAdapter = class _LedgerAdapter {
955
1230
  return failureResult;
956
1231
  }
957
1232
  }
958
- async disconnectDevice(connectId) {
1233
+ async releaseInteraction(interactionId) {
1234
+ const interaction = this._interactions.find(interactionId);
1235
+ if (!interaction) {
1236
+ this._interactions.resolve(interactionId);
1237
+ return;
1238
+ }
1239
+ const endedInteraction = this._interactions.end(interactionId, "explicit");
1240
+ if (!endedInteraction) return;
1241
+ const { connectId } = interaction;
959
1242
  debugLog("[LedgerAdapter][REQ]", {
960
- method: "disconnectDevice",
1243
+ method: "releaseInteraction",
961
1244
  connectId,
962
1245
  params: { connectId }
963
1246
  });
964
1247
  try {
965
- const sessionId = this._sessions.get(connectId);
966
- if (sessionId) {
967
- await this.connector.disconnect(sessionId);
968
- this._sessions.delete(connectId);
969
- }
970
- debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
1248
+ await this._releaseInteractionConnection(endedInteraction);
1249
+ debugLog("[LedgerAdapter][RES]", { method: "releaseInteraction", success: true });
971
1250
  } catch (err) {
972
1251
  const e = err;
973
1252
  debugLog("[LedgerAdapter][RES]", {
974
- method: "disconnectDevice",
1253
+ method: "releaseInteraction",
975
1254
  success: false,
976
1255
  error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
977
1256
  });
978
1257
  throw err;
979
1258
  }
980
1259
  }
981
- async getDeviceInfo(connectId, deviceId) {
1260
+ async _releaseInteractionConnection(interaction) {
1261
+ const sessionIds = /* @__PURE__ */ new Set();
1262
+ for (const [connectId, sessionId] of this._sessions) {
1263
+ if (connectId === interaction.connectId || interaction.connectionKeys.includes(connectId) || interaction.connectionKeys.includes(sessionId)) {
1264
+ this._sessions.delete(connectId);
1265
+ sessionIds.add(sessionId);
1266
+ }
1267
+ }
1268
+ for (const sessionId of sessionIds) {
1269
+ await this._runConnectorTeardown(
1270
+ () => this.connector.disconnect(sessionId).catch(() => void 0)
1271
+ );
1272
+ }
1273
+ }
1274
+ async _releaseLostInteractionConnection(interactionId) {
1275
+ const endedInteraction = this._interactions.end(interactionId, "disconnect");
1276
+ if (!endedInteraction) return;
1277
+ this._discoveredDevices.delete(endedInteraction.connectId);
1278
+ await this._releaseInteractionConnection(endedInteraction);
1279
+ }
1280
+ async getDeviceInfo(connectIdOrInteractionId, deviceId) {
1281
+ let connectId;
1282
+ try {
1283
+ connectId = isHardwareInteractionId(connectIdOrInteractionId) ? this._interactions.resolve(connectIdOrInteractionId).connectId : connectIdOrInteractionId;
1284
+ } catch (error) {
1285
+ return this.errorToFailure(error);
1286
+ }
982
1287
  debugLog("[LedgerAdapter][REQ]", {
983
1288
  method: "getDeviceInfo",
984
1289
  connectId,
@@ -1017,7 +1322,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1017
1322
  }
1018
1323
  }
1019
1324
  getSupportedChains() {
1020
- return ["evm", "btc", "sol", "tron"];
1325
+ return ["evm", "btc", "sol", "tron", "zcash"];
1021
1326
  }
1022
1327
  // ---------------------------------------------------------------------------
1023
1328
  // Chain call helper
@@ -1053,13 +1358,21 @@ var _LedgerAdapter = class _LedgerAdapter {
1053
1358
  if (params && typeof params === "object") {
1054
1359
  const {
1055
1360
  autoInstallApp,
1361
+ interactionId,
1056
1362
  passphraseState: _passphraseState,
1057
1363
  useEmptyPassphrase: _useEmptyPassphrase,
1364
+ knownConnections,
1365
+ extra,
1366
+ allowDeviceSelection,
1058
1367
  ...rest
1059
1368
  } = params;
1060
1369
  return {
1061
1370
  commonParams: {
1062
- autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
1371
+ autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
1372
+ interactionId: typeof interactionId === "string" ? interactionId : void 0,
1373
+ knownConnections,
1374
+ extra,
1375
+ allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0
1063
1376
  },
1064
1377
  rest
1065
1378
  };
@@ -1235,6 +1548,27 @@ var _LedgerAdapter = class _LedgerAdapter {
1235
1548
  );
1236
1549
  }
1237
1550
  // ---------------------------------------------------------------------------
1551
+ // Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
1552
+ // ---------------------------------------------------------------------------
1553
+ zcashGetFullViewingKey(connectId, deviceId, params) {
1554
+ return this.callChainWithMergedParams(
1555
+ connectId,
1556
+ deviceId,
1557
+ "zcash",
1558
+ "zcashGetFullViewingKey",
1559
+ params
1560
+ );
1561
+ }
1562
+ zcashGetShieldedAddress(connectId, deviceId, params) {
1563
+ return this.callChainWithMergedParams(
1564
+ connectId,
1565
+ deviceId,
1566
+ "zcash",
1567
+ "zcashGetShieldedAddress",
1568
+ params
1569
+ );
1570
+ }
1571
+ // ---------------------------------------------------------------------------
1238
1572
  // App management — OS-level Ledger app install / list. Bypasses fingerprint
1239
1573
  // and chain-handler dispatch; installApp progress is forwarded to the adapter
1240
1574
  // emitter via 'ui-event' AppInstallProgress events.
@@ -1323,7 +1657,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1323
1657
  );
1324
1658
  }
1325
1659
  await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
1326
- this.resetState();
1660
+ await this._resetStateAndDisconnectSessions();
1327
1661
  }
1328
1662
  const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
1329
1663
  if (!result.isGenuine) {
@@ -1349,10 +1683,10 @@ var _LedgerAdapter = class _LedgerAdapter {
1349
1683
  if (relayUrl) {
1350
1684
  try {
1351
1685
  await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
1352
- this.resetState();
1686
+ await this._resetStateAndDisconnectSessions();
1353
1687
  } catch {
1354
1688
  this.connector.reset();
1355
- this.resetState();
1689
+ await this._resetStateAndDisconnectSessions();
1356
1690
  }
1357
1691
  }
1358
1692
  }
@@ -1375,13 +1709,36 @@ var _LedgerAdapter = class _LedgerAdapter {
1375
1709
  }
1376
1710
  }, 2e3);
1377
1711
  this._uiRegistry.cancel();
1378
- if (connectId) {
1379
- this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
1712
+ this._finishBleBinding("cancelled");
1713
+ const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
1714
+ let interactionId;
1715
+ if (isHardwareInteractionId(connectId)) {
1716
+ interactionId = connectId;
1717
+ } else if (!connectId && isHardwareInteractionId(activeJobId)) {
1718
+ interactionId = activeJobId;
1719
+ }
1720
+ let resolvedConnectId = connectId;
1721
+ if (interactionId) {
1722
+ try {
1723
+ resolvedConnectId = this._interactions.resolve(interactionId).connectId;
1724
+ } catch {
1725
+ resolvedConnectId = void 0;
1726
+ }
1727
+ }
1728
+ const interactionForPhysicalId = !interactionId && connectId ? this._interactions.findActiveByConnectionKey(connectId) : void 0;
1729
+ const queueKey = interactionId ?? (activeJobId === connectId ? connectId : interactionForPhysicalId?.interactionId) ?? connectId;
1730
+ const pendingInteractionId = interactionId ?? interactionForPhysicalId?.interactionId;
1731
+ if (!connectId) this._pendingInteractionBindings.clear();
1732
+ else if (pendingInteractionId) {
1733
+ this._pendingInteractionBindings.delete(pendingInteractionId);
1734
+ }
1735
+ if (queueKey) {
1736
+ this._jobQueue.cancelActiveAndPending(queueKey, userAbortReason);
1380
1737
  } else {
1381
1738
  this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
1382
1739
  }
1383
- if (connectId) {
1384
- const sessionId = this._sessions.get(connectId) ?? connectId;
1740
+ if (resolvedConnectId) {
1741
+ const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
1385
1742
  void this.connector.cancel(sessionId);
1386
1743
  } else {
1387
1744
  for (const sid of this._sessions.values()) void this.connector.cancel(sid);
@@ -1393,40 +1750,83 @@ var _LedgerAdapter = class _LedgerAdapter {
1393
1750
  // ---------------------------------------------------------------------------
1394
1751
  // Chain fingerprint
1395
1752
  // ---------------------------------------------------------------------------
1753
+ /** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
1396
1754
  async getChainFingerprint(connectId, deviceId, chain) {
1397
1755
  try {
1398
1756
  const fingerprint = await this._computeChainFingerprint(
1399
1757
  chain,
1400
1758
  (method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
1401
1759
  );
1760
+ if (deviceId) {
1761
+ if (fingerprint !== deviceId) {
1762
+ return failure2(
1763
+ HardwareErrorCode3.DeviceMismatch,
1764
+ formatDeviceMismatchError(deviceId, fingerprint)
1765
+ );
1766
+ }
1767
+ if (isHardwareInteractionId(connectId)) {
1768
+ const interaction = this._interactions.resolve(connectId);
1769
+ await this._publishVerifiedBleBinding(
1770
+ interaction.connectId,
1771
+ chain,
1772
+ fingerprint,
1773
+ void 0,
1774
+ connectId
1775
+ );
1776
+ }
1777
+ }
1402
1778
  return success(fingerprint);
1403
1779
  } catch (err) {
1404
1780
  debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
1405
1781
  return this.errorToFailure(err);
1406
1782
  }
1407
1783
  }
1408
- /**
1409
- * Verify fingerprint using an existing sessionId directly.
1410
- * Safe to call inside connectorCall without causing queue deadlock.
1411
- */
1412
- async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
1413
- if (!deviceId) return { success: true };
1414
- try {
1415
- const fingerprint = await this._computeChainFingerprint(
1784
+ /** Discovery may select a BLE target before a later call verifies its wallet. */
1785
+ async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, interactionId, previousConnectId = "", signal) {
1786
+ const binding = interactionId ? this._pendingInteractionBindings.get(interactionId) : attempt;
1787
+ if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) return;
1788
+ const acknowledged = await requestSaveDeviceBinding(
1789
+ this.emitter,
1790
+ this._uiRegistry,
1791
+ {
1792
+ selectionRequestId: binding.selectedConnection.requestId,
1793
+ connection: { transport: "ble", connectId },
1794
+ identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
1795
+ extra: binding.extra
1796
+ },
1797
+ signal
1798
+ );
1799
+ if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
1800
+ this._bindingSelectionRequestId = void 0;
1801
+ }
1802
+ if (interactionId) {
1803
+ this._interactions.resolve(interactionId);
1804
+ this._pendingInteractionBindings.delete(interactionId);
1805
+ }
1806
+ if (acknowledged) return;
1807
+ this.emitter.emit(DEVICE.LEDGER_CONNECTION_VERIFIED, {
1808
+ type: DEVICE.LEDGER_CONNECTION_VERIFIED,
1809
+ payload: {
1810
+ previousConnectId: binding.previousConnectId ?? previousConnectId,
1811
+ connectId,
1416
1812
  chain,
1417
- (method, params) => this._callConnector(sessionId, method, params)
1418
- );
1419
- if (fingerprint === deviceId) {
1420
- return { success: true };
1813
+ fingerprint,
1814
+ extra: binding.extra,
1815
+ selectionRequestId: binding.selectedConnection.requestId
1421
1816
  }
1422
- return { success: false, expected: deviceId, actual: fingerprint };
1423
- } catch (err) {
1424
- const mapped = mapLedgerError(err);
1425
- if (mapped.code === HardwareErrorCode3.WrongApp || mapped.code === HardwareErrorCode3.DeviceLocked) {
1426
- return { success: true };
1427
- }
1428
- throw err;
1817
+ });
1818
+ }
1819
+ /** Verify on the acquired session without re-entering the job queue. */
1820
+ async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
1821
+ if (!deviceId) return { success: true };
1822
+ const fingerprint = await this._computeChainFingerprint(
1823
+ chain,
1824
+ (method, params) => this._callConnector(sessionId, method, params)
1825
+ );
1826
+ if (fingerprint === deviceId) {
1827
+ return { success: true };
1429
1828
  }
1829
+ return { success: false, expected: deviceId, actual: fingerprint };
1430
1830
  }
1431
1831
  /**
1432
1832
  * Compute the chain fingerprint via a caller-supplied call strategy.
@@ -1455,6 +1855,8 @@ var _LedgerAdapter = class _LedgerAdapter {
1455
1855
  address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
1456
1856
  } else if (chain === "tron") {
1457
1857
  address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
1858
+ } else if (chain === "zcash") {
1859
+ address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
1458
1860
  } else {
1459
1861
  throw new Error(`Unsupported chain for fingerprint: ${chain}`);
1460
1862
  }
@@ -1584,16 +1986,17 @@ var _LedgerAdapter = class _LedgerAdapter {
1584
1986
  // Layer 1 entry. Caller signal only races the outer awaiter; the shared
1585
1987
  // `_doConnect` runs under its own internal controller so caller A's cancel
1586
1988
  // doesn't kill caller B's await.
1587
- async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
1989
+ async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveInteractionId, context) {
1588
1990
  if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
1589
- if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
1991
+ this._assertConnectorReady("connectDevice");
1992
+ if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
1590
1993
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1591
1994
  code: HardwareErrorCode3.DeviceNotFound
1592
1995
  });
1593
1996
  }
1594
1997
  if (connectId && this._sessions.has(connectId)) return connectId;
1595
1998
  if (!connectId && this._sessions.size > 0) {
1596
- if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
1999
+ if (!this._isBleConnection() && this._sessions.size > 1) {
1597
2000
  throw Object.assign(
1598
2001
  new Error(
1599
2002
  "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
@@ -1603,15 +2006,24 @@ var _LedgerAdapter = class _LedgerAdapter {
1603
2006
  }
1604
2007
  return this._sessions.keys().next().value;
1605
2008
  }
1606
- if (!this._connectingPromise) {
1607
- this._doConnectAbortController = new AbortController();
1608
- const innerSignal = this._doConnectAbortController.signal;
2009
+ if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
2010
+ const controller = new AbortController();
2011
+ this._doConnectAbortController = controller;
2012
+ const innerSignal = controller.signal;
1609
2013
  this._connectingPromise = (async () => {
1610
2014
  try {
1611
- return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback);
2015
+ return await this._doConnect(
2016
+ innerSignal,
2017
+ connectId,
2018
+ allowUsbEphemeralFallback,
2019
+ preserveInteractionId,
2020
+ context
2021
+ );
1612
2022
  } finally {
1613
- this._connectingPromise = null;
1614
- this._doConnectAbortController = null;
2023
+ if (this._doConnectAbortController === controller) {
2024
+ this._connectingPromise = null;
2025
+ this._doConnectAbortController = null;
2026
+ }
1615
2027
  }
1616
2028
  })();
1617
2029
  }
@@ -1620,22 +2032,49 @@ var _LedgerAdapter = class _LedgerAdapter {
1620
2032
  // Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
1621
2033
  // Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
1622
2034
  // throw DeviceNotFound so the user is kicked out of the loop.
1623
- async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
1624
- if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
1625
- try {
1626
- return await this._connectDeviceOrThrow(targetConnectId);
1627
- } catch (err) {
1628
- if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
1629
- throw err;
1630
- }
1631
- this._discoveredDevices.delete(targetConnectId);
1632
- if (isDeviceDisconnectedError(err)) {
1633
- try {
1634
- this.connector.reset?.();
1635
- } catch {
1636
- }
1637
- }
2035
+ async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveInteractionId, context) {
2036
+ _LedgerAdapter._throwIfAborted(internalSignal);
2037
+ if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
2038
+ this._activeConnectionType = "usb";
2039
+ const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
2040
+ _LedgerAdapter._throwIfAborted(internalSignal);
2041
+ if (usbDevices.length > 0) {
2042
+ const knownUsb = context?.knownConnections?.find(
2043
+ (connection) => connection.transport === "usb"
2044
+ );
2045
+ const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
2046
+ return this._connectFirstOrSelect(
2047
+ usbDevices,
2048
+ usbTarget,
2049
+ allowUsbEphemeralFallback,
2050
+ preserveInteractionId,
2051
+ context,
2052
+ internalSignal
2053
+ );
2054
+ }
2055
+ this._activeConnectionType = "ble";
2056
+ const knownBle = context?.knownConnections?.find(
2057
+ (connection) => connection.transport === "ble"
2058
+ );
2059
+ if (knownBle?.transport === "ble") {
2060
+ return this._connectDeviceOrThrow(
2061
+ knownBle.connectId,
2062
+ preserveInteractionId,
2063
+ internalSignal
2064
+ );
1638
2065
  }
2066
+ const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
2067
+ return this._connectFirstOrSelect(
2068
+ bleDevices,
2069
+ void 0,
2070
+ allowUsbEphemeralFallback,
2071
+ preserveInteractionId,
2072
+ context,
2073
+ internalSignal
2074
+ );
2075
+ }
2076
+ if (this._isBleConnection() && targetConnectId) {
2077
+ return this._connectDeviceOrThrow(targetConnectId, preserveInteractionId, internalSignal);
1639
2078
  }
1640
2079
  let confirms = 0;
1641
2080
  while (!internalSignal.aborted) {
@@ -1643,22 +2082,28 @@ var _LedgerAdapter = class _LedgerAdapter {
1643
2082
  type: EConnectorInteraction.Searching,
1644
2083
  payload: { sessionId: "" }
1645
2084
  });
1646
- let devices = await this.searchDevices();
2085
+ let devices = await this._searchDevices(void 0, internalSignal);
2086
+ _LedgerAdapter._throwIfAborted(internalSignal);
1647
2087
  if (devices.length === 0) {
1648
2088
  for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
1649
2089
  await new Promise((resolve) => {
1650
2090
  setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
1651
2091
  });
1652
- devices = await this.searchDevices();
2092
+ _LedgerAdapter._throwIfAborted(internalSignal);
2093
+ devices = await this._searchDevices(void 0, internalSignal);
2094
+ _LedgerAdapter._throwIfAborted(internalSignal);
1653
2095
  if (devices.length > 0) break;
1654
2096
  }
1655
2097
  }
1656
- if (devices.length > 0) {
2098
+ if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
1657
2099
  try {
1658
2100
  return await this._connectFirstOrSelect(
1659
2101
  devices,
1660
2102
  targetConnectId,
1661
- allowUsbEphemeralFallback
2103
+ allowUsbEphemeralFallback,
2104
+ preserveInteractionId,
2105
+ context,
2106
+ internalSignal
1662
2107
  );
1663
2108
  } catch (err) {
1664
2109
  if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
@@ -1687,45 +2132,107 @@ var _LedgerAdapter = class _LedgerAdapter {
1687
2132
  _LedgerAdapter._throwIfAborted(internalSignal);
1688
2133
  throw new Error("_doConnect aborted");
1689
2134
  }
1690
- async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
2135
+ async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false, preserveInteractionId, context, signal) {
2136
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
1691
2137
  if (targetConnectId) {
1692
2138
  const target = devices.find(
1693
2139
  (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
1694
2140
  );
1695
2141
  if (target) {
1696
- return this._connectDeviceOrThrow(target.connectId);
2142
+ return this._connectDeviceOrThrow(target.connectId, preserveInteractionId, signal);
1697
2143
  }
1698
- if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1 && allowUsbEphemeralFallback) {
2144
+ if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
1699
2145
  debugLog(
1700
2146
  `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
1701
2147
  );
1702
- return this._connectDeviceOrThrow(devices[0].connectId);
2148
+ return this._connectDeviceOrThrow(devices[0].connectId, preserveInteractionId, signal);
1703
2149
  }
1704
- const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1705
- code: HardwareErrorCode3.DeviceNotFound
1706
- });
1707
- if (isLedgerBleConnectionType(this.connector.connectionType)) {
1708
- err._tag = ERROR_TAG.DeviceNotAdvertising;
2150
+ if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
2151
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
2152
+ code: HardwareErrorCode3.DeviceNotFound
2153
+ });
2154
+ if (this._isBleConnection()) {
2155
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
2156
+ }
2157
+ throw err;
1709
2158
  }
1710
- throw err;
1711
2159
  }
1712
- if (isLedgerBleConnectionType(this.connector.connectionType)) {
2160
+ const requiresBleSelection = this._isBleConnection();
2161
+ if (requiresBleSelection && !allowUsbEphemeralFallback) {
1713
2162
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1714
2163
  code: HardwareErrorCode3.DeviceNotFound
1715
2164
  });
1716
2165
  }
1717
- if (devices.length > 1) {
1718
- throw createMultipleUsbLedgerDevicesError();
2166
+ if (requiresBleSelection && context?.allowDeviceSelection !== false) {
2167
+ const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
2168
+ if (context) context.bindingSessionId = bindingSessionId;
2169
+ const { device, requestId } = await requestBleDeviceSelection({
2170
+ emitter: this.emitter,
2171
+ registry: this._uiRegistry,
2172
+ signal: signal ?? new AbortController().signal,
2173
+ scan: async () => (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
2174
+ request: {
2175
+ devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
2176
+ bindingSessionId,
2177
+ rejectedConnectId: context?.rejectedConnectId,
2178
+ context: {
2179
+ kind: "bind-connection",
2180
+ transport: "ble",
2181
+ reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
2182
+ },
2183
+ extra: context?.extra
2184
+ }
2185
+ });
2186
+ if (context) context.selectedConnection = { connectId: device.connectId, requestId };
2187
+ this._bindingSelectionRequestId = requestId;
2188
+ return this._connectDeviceOrThrow(device.connectId, preserveInteractionId, signal);
2189
+ }
2190
+ if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
2191
+ if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
2192
+ throw createHwkError({
2193
+ code: HardwareErrorCode3.DeviceNotFound,
2194
+ message: "Select a Ledger device before continuing"
2195
+ });
2196
+ }
2197
+ const requestId = this._uiRegistry.createRequestId();
2198
+ const waitPromise = this._uiRegistry.wait(
2199
+ UI_REQUEST.REQUEST_SELECT_DEVICE,
2200
+ { requestId }
2201
+ );
2202
+ this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
2203
+ type: UI_REQUEST.REQUEST_SELECT_DEVICE,
2204
+ payload: {
2205
+ devices,
2206
+ requestId,
2207
+ context: requiresBleSelection ? {
2208
+ kind: "bind-connection",
2209
+ transport: "ble",
2210
+ reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
2211
+ } : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
2212
+ extra: context?.extra
2213
+ }
2214
+ });
2215
+ const { sdkConnectId } = await (signal ? this._abortable(signal, waitPromise) : waitPromise);
2216
+ if (signal) _LedgerAdapter._throwIfAborted(signal);
2217
+ const selected = devices.find((device) => device.connectId === sdkConnectId);
2218
+ if (!selected) {
2219
+ throw Object.assign(new Error("Selected Ledger is no longer available"), {
2220
+ code: HardwareErrorCode3.DeviceNotFound
2221
+ });
2222
+ }
2223
+ if (context && requiresBleSelection)
2224
+ context.selectedConnection = { connectId: selected.connectId, requestId };
2225
+ return this._connectDeviceOrThrow(selected.connectId, preserveInteractionId, signal);
1719
2226
  }
1720
2227
  if (devices.length !== 1) {
1721
2228
  throw Object.assign(new Error("Ledger device not found."), {
1722
2229
  code: HardwareErrorCode3.DeviceNotFound
1723
2230
  });
1724
2231
  }
1725
- return this._connectDeviceOrThrow(devices[0].connectId);
2232
+ return this._connectDeviceOrThrow(devices[0].connectId, preserveInteractionId, signal);
1726
2233
  }
1727
- async _connectDeviceOrThrow(chosenConnectId) {
1728
- const result = await this.connectDevice(chosenConnectId);
2234
+ async _connectDeviceOrThrow(chosenConnectId, preserveInteractionId, signal) {
2235
+ const result = await this._connectTarget(chosenConnectId, preserveInteractionId, signal);
1729
2236
  if (!result.success) {
1730
2237
  const payload = result.payload;
1731
2238
  const rethrow = Object.assign(new Error(payload.error), {
@@ -1736,7 +2243,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1736
2243
  }
1737
2244
  throw rethrow;
1738
2245
  }
1739
- return chosenConnectId;
2246
+ return result.payload;
1740
2247
  }
1741
2248
  /**
1742
2249
  * Call the connector with automatic session resolution and disconnect retry.
@@ -1766,26 +2273,121 @@ var _LedgerAdapter = class _LedgerAdapter {
1766
2273
  * lives in one place.
1767
2274
  */
1768
2275
  async _callConnector(sessionId, method, params, signal) {
1769
- const promise = this.connector.call(sessionId, method, params);
2276
+ this._assertConnectorReady(method);
2277
+ const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
2278
+ let promise;
2279
+ try {
2280
+ promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
2281
+ } catch (error) {
2282
+ releaseOperation();
2283
+ throw error;
2284
+ }
1770
2285
  const result = signal ? await this._abortable(signal, promise) : await promise;
1771
2286
  return this._unwrapConnectorResult(result);
1772
2287
  }
2288
+ _assertConnectorReady(method) {
2289
+ if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
2290
+ throw _LedgerAdapter._createDeviceBusyError(method);
2291
+ }
2292
+ }
2293
+ _runConnectorTeardown(task) {
2294
+ const previous = this._connectorTeardownTail;
2295
+ let releaseTail = () => void 0;
2296
+ this._connectorTeardownTail = new Promise((resolve) => {
2297
+ releaseTail = resolve;
2298
+ });
2299
+ this._pendingConnectorTeardowns += 1;
2300
+ return (async () => {
2301
+ try {
2302
+ await previous;
2303
+ await this._waitForConnectorOperationsToDrain();
2304
+ await task();
2305
+ } finally {
2306
+ this._pendingConnectorTeardowns -= 1;
2307
+ releaseTail();
2308
+ }
2309
+ })();
2310
+ }
2311
+ _waitForConnectorOperationsToDrain() {
2312
+ if (this._unsettledConnectorOperations.size === 0) {
2313
+ return Promise.resolve();
2314
+ }
2315
+ return new Promise((resolve) => {
2316
+ this._connectorIdleWaiters.add(resolve);
2317
+ });
2318
+ }
2319
+ _retainConnectorOperation(key) {
2320
+ this._unsettledConnectorOperations.set(
2321
+ key,
2322
+ (this._unsettledConnectorOperations.get(key) ?? 0) + 1
2323
+ );
2324
+ let released = false;
2325
+ return () => {
2326
+ if (released) return;
2327
+ released = true;
2328
+ const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
2329
+ if (remaining > 0) {
2330
+ this._unsettledConnectorOperations.set(key, remaining);
2331
+ } else {
2332
+ this._unsettledConnectorOperations.delete(key);
2333
+ }
2334
+ if (this._unsettledConnectorOperations.size === 0) {
2335
+ for (const resolve of this._connectorIdleWaiters) resolve();
2336
+ this._connectorIdleWaiters.clear();
2337
+ }
2338
+ };
2339
+ }
1773
2340
  async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
1774
- debugLog("[LedgerAdapter][REQ]", { method, connectId: connectId || "(empty)", params });
1775
- const queueKey = connectId || "__ledger_default__";
2341
+ const positionalInteractionId = isHardwareInteractionId(connectId) ? connectId : void 0;
2342
+ if (positionalInteractionId && commonParams?.interactionId && positionalInteractionId !== commonParams.interactionId) {
2343
+ throw createHwkError({
2344
+ code: HardwareErrorCode3.InvalidParams,
2345
+ message: "Conflicting Ledger interaction ids",
2346
+ params: {
2347
+ positionalInteractionId,
2348
+ commonInteractionId: commonParams.interactionId
2349
+ }
2350
+ });
2351
+ }
2352
+ const interactionId = commonParams?.interactionId ?? positionalInteractionId;
2353
+ const interaction = interactionId ? this._interactions.resolve(interactionId) : void 0;
2354
+ const releaseInteractionRetention = interactionId ? this._interactions.retain(interactionId) : void 0;
2355
+ const effectiveConnectId = interaction?.connectId ?? connectId;
2356
+ debugLog("[LedgerAdapter][REQ]", {
2357
+ method,
2358
+ connectId: effectiveConnectId || "(empty)",
2359
+ params
2360
+ });
2361
+ const queueKey = (interactionId ?? effectiveConnectId) || "__ledger_default__";
1776
2362
  try {
1777
2363
  const result = await this._jobQueue.enqueue(
1778
2364
  queueKey,
1779
- async (signal) => this._runConnectorCall(
1780
- connectId,
1781
- method,
1782
- params,
1783
- signal,
1784
- fingerprint,
1785
- permissionDeviceId,
1786
- commonParams,
1787
- installContext
1788
- ),
2365
+ async (signal) => {
2366
+ if (interactionId) this._activeInteractionJobs.add(interactionId);
2367
+ try {
2368
+ return await this._runConnectorCall(
2369
+ effectiveConnectId,
2370
+ method,
2371
+ params,
2372
+ signal,
2373
+ fingerprint,
2374
+ permissionDeviceId,
2375
+ commonParams,
2376
+ installContext ?? {},
2377
+ interactionId
2378
+ );
2379
+ } catch (error) {
2380
+ this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
2381
+ throw error;
2382
+ } finally {
2383
+ if (interactionId) {
2384
+ this._activeInteractionJobs.delete(interactionId);
2385
+ if (this._pendingInteractionDisconnects.delete(interactionId)) {
2386
+ await this._releaseLostInteractionConnection(interactionId);
2387
+ }
2388
+ }
2389
+ }
2390
+ },
1789
2391
  {
1790
2392
  label: method,
1791
2393
  rejectIfBusy: true,
@@ -1806,6 +2408,8 @@ var _LedgerAdapter = class _LedgerAdapter {
1806
2408
  }
1807
2409
  });
1808
2410
  throw err;
2411
+ } finally {
2412
+ releaseInteractionRetention?.();
1809
2413
  }
1810
2414
  }
1811
2415
  /**
@@ -1815,6 +2419,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1815
2419
  _abortable(signal, promise) {
1816
2420
  const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
1817
2421
  if (signal.aborted) {
2422
+ void promise.catch(() => void 0);
1818
2423
  return Promise.reject(getAbortReason());
1819
2424
  }
1820
2425
  return new Promise((resolve, reject) => {
@@ -1841,13 +2446,15 @@ var _LedgerAdapter = class _LedgerAdapter {
1841
2446
  }
1842
2447
  }
1843
2448
  /** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
1844
- async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
2449
+ async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, interactionId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
1845
2450
  _LedgerAdapter._throwIfAborted(signal);
1846
- await this._ensureDevicePermission(
1847
- connectId,
1848
- permissionDeviceId ?? fingerprint?.deviceId,
1849
- signal
1850
- );
2451
+ if (!interactionId) {
2452
+ await this._ensureDevicePermission(
2453
+ connectId,
2454
+ permissionDeviceId ?? fingerprint?.deviceId,
2455
+ signal
2456
+ );
2457
+ }
1851
2458
  _LedgerAdapter._throwIfAborted(signal);
1852
2459
  let effectiveParams = params;
1853
2460
  if (method === "btcGetPublicKey") {
@@ -1861,36 +2468,133 @@ var _LedgerAdapter = class _LedgerAdapter {
1861
2468
  effectiveParams = gatedParams;
1862
2469
  }
1863
2470
  const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
1864
- const resolvedConnectId = await this.ensureConnected(
1865
- connectId,
2471
+ let businessCallStarted = false;
2472
+ const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
2473
+ const knownTransport = this._isBleConnection() ? "ble" : "usb";
2474
+ const hintedConnectId = commonParams?.knownConnections?.find(
2475
+ (connection) => connection.transport === knownTransport
2476
+ );
2477
+ const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
2478
+ const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
2479
+ const connectionAttempt = { ...commonParams };
2480
+ const bundleConnection = installContext?.connection;
2481
+ if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
2482
+ throw createHwkError({
2483
+ code: HardwareErrorCode3.DeviceDisconnected,
2484
+ message: "Ledger all-network connection ended"
2485
+ });
2486
+ }
2487
+ let resolvedConnectId = interactionId ? this._interactions.resolve(interactionId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
2488
+ preferredConnectId,
1866
2489
  signal,
1867
- allowUsbEphemeralFallback
2490
+ allowUsbEphemeralFallback,
2491
+ void 0,
2492
+ connectionAttempt
1868
2493
  );
1869
- const sessionId = this._sessions.get(resolvedConnectId);
2494
+ let sessionId = this._sessions.get(resolvedConnectId);
2495
+ if (sessionId && installContext && !installContext.connection) {
2496
+ installContext.connection = { connectId: resolvedConnectId, sessionId };
2497
+ }
1870
2498
  if (!sessionId) {
2499
+ if (interactionId) {
2500
+ this._interactions.end(interactionId, "disconnect");
2501
+ throw createHwkError({
2502
+ code: HardwareErrorCode3.InteractionEnded,
2503
+ message: "Ledger interaction connection is no longer active",
2504
+ params: { interactionId, reason: "disconnect" }
2505
+ });
2506
+ }
1871
2507
  throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
1872
2508
  _tag: ERROR_TAG.DeviceSessionNotFound
1873
2509
  });
1874
2510
  }
1875
2511
  try {
1876
2512
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1877
- const fp = await this._abortable(
1878
- signal,
1879
- this._verifyDeviceFingerprintWithSession(
1880
- sessionId,
1881
- fingerprint.deviceId,
1882
- fingerprint.chain
1883
- )
1884
- );
1885
- if (!fp.success) {
1886
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1887
- code: HardwareErrorCode3.DeviceMismatch
2513
+ for (; ; ) {
2514
+ const fp = await this._abortable(
2515
+ signal,
2516
+ this._verifyDeviceFingerprintWithSession(
2517
+ sessionId,
2518
+ fingerprint.deviceId,
2519
+ fingerprint.chain
2520
+ )
2521
+ );
2522
+ if (fp.success) break;
2523
+ const binding = interactionId ? this._pendingInteractionBindings.get(interactionId) : connectionAttempt;
2524
+ if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
2525
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2526
+ code: HardwareErrorCode3.DeviceMismatch
2527
+ });
2528
+ }
2529
+ binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
2530
+ binding.rejectedConnectIds.add(resolvedConnectId);
2531
+ binding.rejectedConnectId = resolvedConnectId;
2532
+ this._sessions.delete(resolvedConnectId);
2533
+ await this.connector.disconnect(sessionId);
2534
+ if (interactionId) this._pendingInteractionDisconnects.delete(interactionId);
2535
+ _LedgerAdapter._throwIfAborted(signal);
2536
+ resolvedConnectId = await this._connectFirstOrSelect(
2537
+ [],
2538
+ void 0,
2539
+ true,
2540
+ interactionId,
2541
+ binding,
2542
+ signal
2543
+ );
2544
+ const selectedSession = this._sessions.get(resolvedConnectId);
2545
+ const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
2546
+ if (!selectedSession || !selectedDevice)
2547
+ throw createHwkError({
2548
+ code: HardwareErrorCode3.DeviceDisconnected,
2549
+ message: "Selected Ledger connection ended"
2550
+ });
2551
+ sessionId = selectedSession;
2552
+ if (interactionId)
2553
+ this._interactions.rebind(interactionId, {
2554
+ connectId: resolvedConnectId,
2555
+ device: selectedDevice,
2556
+ connectionKeys: [sessionId]
2557
+ });
2558
+ if (installContext)
2559
+ installContext.connection = { connectId: resolvedConnectId, sessionId };
2560
+ }
2561
+ if (!interactionId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
2562
+ this._verifiedBleReconnectTargets.set(connectId, {
2563
+ connectId: resolvedConnectId,
2564
+ chain: fingerprint.chain,
2565
+ fingerprint: fingerprint.deviceId
1888
2566
  });
1889
2567
  }
2568
+ await this._publishVerifiedBleBinding(
2569
+ resolvedConnectId,
2570
+ fingerprint.chain,
2571
+ fingerprint.deviceId,
2572
+ connectionAttempt,
2573
+ interactionId,
2574
+ connectId,
2575
+ signal
2576
+ );
1890
2577
  }
2578
+ businessCallStarted = true;
1891
2579
  return await this._callConnector(sessionId, method, effectiveParams, signal);
1892
2580
  } catch (err) {
1893
2581
  if (signal.aborted) throw err;
2582
+ if (isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err)) {
2583
+ this._discoveredDevices.delete(resolvedConnectId);
2584
+ if (interactionId) await this._releaseLostInteractionConnection(interactionId);
2585
+ else {
2586
+ this._sessions.delete(resolvedConnectId);
2587
+ await this.connector.disconnect(sessionId).catch(() => void 0);
2588
+ }
2589
+ const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
2590
+ const interactionParams = interactionId ? { interactionId, reason: "disconnect" } : void 0;
2591
+ throw createHwkError({
2592
+ code: interactionId ? HardwareErrorCode3.InteractionEnded : mapLedgerError(err).code,
2593
+ message: "Ledger operation connection was lost; start a new operation",
2594
+ params: ambiguous ? operationMayHaveCompletedParams(method, { interactionId }) : interactionParams,
2595
+ recovery: ambiguous ? { scope: "unknown" } : void 0
2596
+ });
2597
+ }
1894
2598
  const errObj = err;
1895
2599
  debugLog("[LedgerAdapter] connectorCall error:", method, {
1896
2600
  message: errObj?.message,
@@ -1902,6 +2606,65 @@ var _LedgerAdapter = class _LedgerAdapter {
1902
2606
  isNotAdvertising: isDeviceNotAdvertisingError(err),
1903
2607
  isStuckApp: isStuckAppStateError(err)
1904
2608
  });
2609
+ if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
2610
+ await this._waitForDeviceConnect(signal);
2611
+ if (this._sessions.get(resolvedConnectId) !== sessionId) {
2612
+ throw createHwkError({
2613
+ code: interactionId ? HardwareErrorCode3.InteractionEnded : HardwareErrorCode3.DeviceDisconnected,
2614
+ message: "Ledger connection ended while waiting for unlock"
2615
+ });
2616
+ }
2617
+ return this._runConnectorCall(
2618
+ resolvedConnectId,
2619
+ method,
2620
+ effectiveParams,
2621
+ signal,
2622
+ fingerprint,
2623
+ permissionDeviceId,
2624
+ commonParams,
2625
+ installContext,
2626
+ interactionId,
2627
+ lockedRetryBudget - 1
2628
+ );
2629
+ }
2630
+ if (businessCallStarted && isStuckAppStateError(err)) {
2631
+ await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2632
+ if (this._sessions.get(resolvedConnectId) !== sessionId) {
2633
+ throw createHwkError({
2634
+ code: interactionId ? HardwareErrorCode3.InteractionEnded : HardwareErrorCode3.DeviceDisconnected,
2635
+ message: "Ledger connection ended during the app transition"
2636
+ });
2637
+ }
2638
+ try {
2639
+ return await this._callConnector(sessionId, method, effectiveParams, signal);
2640
+ } catch (retryErr) {
2641
+ if (isStuckAppStateError(retryErr)) throw err;
2642
+ if (isDeviceDisconnectedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isTimeoutError(retryErr) || isConnectionLevelError(retryErr)) {
2643
+ if (interactionId) await this._releaseLostInteractionConnection(interactionId);
2644
+ else {
2645
+ this._sessions.delete(resolvedConnectId);
2646
+ this._discoveredDevices.delete(resolvedConnectId);
2647
+ await this.connector.disconnect(sessionId).catch(() => void 0);
2648
+ }
2649
+ throw createHwkError({
2650
+ code: interactionId ? HardwareErrorCode3.InteractionEnded : mapLedgerError(retryErr).code,
2651
+ message: `Ledger ${method} may have completed before the connection was lost`,
2652
+ params: !canReplayHardwareMethodAfterTransportFailure(method) ? operationMayHaveCompletedParams(method, {
2653
+ interactionId,
2654
+ reason: "disconnect"
2655
+ }) : { interactionId, reason: "disconnect" },
2656
+ recovery: !canReplayHardwareMethodAfterTransportFailure(method) ? { scope: "unknown" } : void 0
2657
+ });
2658
+ }
2659
+ throw retryErr;
2660
+ }
2661
+ }
2662
+ if (!interactionId && err?.code === HardwareErrorCode3.DeviceMismatch) {
2663
+ this._sessions.delete(resolvedConnectId);
2664
+ this._discoveredDevices.delete(resolvedConnectId);
2665
+ await this.connector.disconnect(sessionId).catch(() => void 0);
2666
+ throw err;
2667
+ }
1905
2668
  const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
1906
2669
  const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
1907
2670
  if (autoInstallApp && isAppMissing) {
@@ -1957,157 +2720,14 @@ var _LedgerAdapter = class _LedgerAdapter {
1957
2720
  fingerprint,
1958
2721
  permissionDeviceId,
1959
2722
  commonParams,
1960
- installContext
2723
+ installContext,
2724
+ interactionId
1961
2725
  );
1962
2726
  }
1963
2727
  }
1964
- if (isStuckAppStateError(err)) {
1965
- try {
1966
- this._sessions.delete(resolvedConnectId);
1967
- this._discoveredDevices.delete(resolvedConnectId);
1968
- this.connector.reset?.();
1969
- } catch {
1970
- }
1971
- debugLog(
1972
- "[LedgerAdapter] stuck-app retry: method=",
1973
- method,
1974
- "delayMs=",
1975
- _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
1976
- "_tag=",
1977
- err?._tag
1978
- );
1979
- try {
1980
- const retryResult = await this._retryAfterStuckApp(
1981
- resolvedConnectId,
1982
- method,
1983
- effectiveParams,
1984
- signal,
1985
- err,
1986
- fingerprint
1987
- );
1988
- debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
1989
- return retryResult;
1990
- } catch (retryErr) {
1991
- if (signal.aborted) throw retryErr;
1992
- if (isStuckAppStateError(retryErr)) {
1993
- debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
1994
- throw err;
1995
- }
1996
- debugLog(
1997
- "[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
1998
- method,
1999
- "retryErrTag=",
2000
- retryErr?._tag
2001
- );
2002
- throw retryErr;
2003
- }
2004
- }
2005
- if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
2006
- let lastErr = err;
2007
- for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
2008
- if (signal.aborted) throw lastErr;
2009
- try {
2010
- this._sessions.delete(resolvedConnectId);
2011
- this._discoveredDevices.delete(resolvedConnectId);
2012
- if (isDeviceDisconnectedError(lastErr)) {
2013
- try {
2014
- this.connector.reset?.();
2015
- } catch {
2016
- }
2017
- }
2018
- const reConnectId = await this.ensureConnected(
2019
- resolvedConnectId,
2020
- signal,
2021
- allowUsbEphemeralFallback
2022
- );
2023
- const reSessionId = this._sessions.get(reConnectId);
2024
- if (!reSessionId) throw lastErr;
2025
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2026
- const fp = await this._abortable(
2027
- signal,
2028
- this._verifyDeviceFingerprintWithSession(
2029
- reSessionId,
2030
- fingerprint.deviceId,
2031
- fingerprint.chain
2032
- )
2033
- );
2034
- if (!fp.success) {
2035
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2036
- code: HardwareErrorCode3.DeviceMismatch
2037
- });
2038
- }
2039
- }
2040
- return await this._callConnector(reSessionId, method, effectiveParams, signal);
2041
- } catch (retryErr) {
2042
- if (signal.aborted) throw retryErr;
2043
- lastErr = retryErr;
2044
- const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
2045
- if (!canRetry) {
2046
- throw retryErr;
2047
- }
2048
- }
2049
- }
2050
- throw lastErr;
2051
- }
2052
- if (isTimeoutError(err)) {
2053
- debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
2054
- this._discoveredDevices.delete(resolvedConnectId);
2055
- return this._retryWithFreshConnection(
2056
- resolvedConnectId,
2057
- method,
2058
- effectiveParams,
2059
- signal,
2060
- err,
2061
- fingerprint
2062
- );
2063
- }
2064
- if (isConnectionLevelError(err)) {
2065
- debugLog("[LedgerAdapter] connection-level fail-closed reset");
2066
- this._sessions.delete(resolvedConnectId);
2067
- this._discoveredDevices.delete(resolvedConnectId);
2068
- this.connector.reset();
2069
- const codeNum = err?.code;
2070
- throw Object.assign(err, {
2071
- code: codeNum ?? HardwareErrorCode3.DeviceDisconnected
2072
- });
2073
- }
2074
2728
  throw err;
2075
2729
  }
2076
2730
  }
2077
- /**
2078
- * Stuck-app recovery: pause for the device's UI transition, then retry once.
2079
- *
2080
- * Caller has already cleared the session + reset connector. We wait so Stax
2081
- * finishes its post-CloseApp animation, then go through ensureConnected +
2082
- * fingerprint check + call exactly once. Caller decides what to do on a
2083
- * second stuck-app hit.
2084
- */
2085
- async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
2086
- await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2087
- const retryConnectId = await this.ensureConnected(
2088
- resolvedConnectId,
2089
- signal,
2090
- !!fingerprint?.deviceId && !fingerprint.skipFingerprint
2091
- );
2092
- const retrySessionId = this._sessions.get(retryConnectId);
2093
- if (!retrySessionId) throw originalErr;
2094
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2095
- const fp = await this._abortable(
2096
- signal,
2097
- this._verifyDeviceFingerprintWithSession(
2098
- retrySessionId,
2099
- fingerprint.deviceId,
2100
- fingerprint.chain
2101
- )
2102
- );
2103
- if (!fp.success) {
2104
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2105
- code: HardwareErrorCode3.DeviceMismatch
2106
- });
2107
- }
2108
- }
2109
- return this._callConnector(retrySessionId, method, params, signal);
2110
- }
2111
2731
  _sleepAbortable(ms, signal) {
2112
2732
  return new Promise((resolve, reject) => {
2113
2733
  if (signal.aborted) {
@@ -2125,88 +2745,6 @@ var _LedgerAdapter = class _LedgerAdapter {
2125
2745
  signal.addEventListener("abort", onAbort, { once: true });
2126
2746
  });
2127
2747
  }
2128
- /**
2129
- * Clear stale session, reconnect, and retry the call.
2130
- *
2131
- * Timeout recovery starts with a full connector reset. After an APDU
2132
- * timeout, DMK/transport state may still emit malformed responses; retrying
2133
- * on the same DMK can poison the next chain switch.
2134
- */
2135
- async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
2136
- this.connector.reset();
2137
- this._sessions.clear();
2138
- this._discoveredDevices.clear();
2139
- this._connectingPromise = null;
2140
- const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
2141
- const retryConnectId = await this.ensureConnected(
2142
- targetConnectId,
2143
- signal,
2144
- allowUsbEphemeralFallback
2145
- );
2146
- const retrySessionId = this._sessions.get(retryConnectId);
2147
- if (!retrySessionId) {
2148
- throw originalErr;
2149
- }
2150
- try {
2151
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2152
- const fp = await this._abortable(
2153
- signal,
2154
- this._verifyDeviceFingerprintWithSession(
2155
- retrySessionId,
2156
- fingerprint.deviceId,
2157
- fingerprint.chain
2158
- )
2159
- );
2160
- if (!fp.success) {
2161
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2162
- code: HardwareErrorCode3.DeviceMismatch
2163
- });
2164
- }
2165
- }
2166
- return await this._callConnector(retrySessionId, method, params, signal);
2167
- } catch (retryErr) {
2168
- if (signal.aborted) throw retryErr;
2169
- this.connector.reset();
2170
- this._sessions.clear();
2171
- this._discoveredDevices.clear();
2172
- this._connectingPromise = null;
2173
- if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
2174
- throw retryErr;
2175
- }
2176
- debugLog(
2177
- "[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
2178
- );
2179
- this.connector.reset();
2180
- this._sessions.clear();
2181
- this._discoveredDevices.clear();
2182
- this._connectingPromise = null;
2183
- const finalConnectId = await this.ensureConnected(
2184
- targetConnectId,
2185
- signal,
2186
- allowUsbEphemeralFallback
2187
- );
2188
- const finalSessionId = this._sessions.get(finalConnectId);
2189
- if (!finalSessionId) {
2190
- throw originalErr;
2191
- }
2192
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2193
- const fp = await this._abortable(
2194
- signal,
2195
- this._verifyDeviceFingerprintWithSession(
2196
- finalSessionId,
2197
- fingerprint.deviceId,
2198
- fingerprint.chain
2199
- )
2200
- );
2201
- if (!fp.success) {
2202
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2203
- code: HardwareErrorCode3.DeviceMismatch
2204
- });
2205
- }
2206
- }
2207
- return this._callConnector(finalSessionId, method, params, signal);
2208
- }
2209
- }
2210
2748
  /**
2211
2749
  * Ensure OS-level device permission (Bluetooth / USB) before proceeding.
2212
2750
  *
@@ -2276,7 +2814,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2276
2814
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
2277
2815
  const e = err;
2278
2816
  const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
2279
- return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
2817
+ return ledgerFailure(
2818
+ e.code,
2819
+ e.message ?? "Unknown error",
2820
+ e.appName,
2821
+ tag,
2822
+ params,
2823
+ void 0,
2824
+ isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
2825
+ );
2280
2826
  }
2281
2827
  const mapped = mapLedgerError(err);
2282
2828
  return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
@@ -2303,7 +2849,7 @@ var _LedgerAdapter = class _LedgerAdapter {
2303
2849
  deviceId: device.deviceId,
2304
2850
  connectId: device.connectId,
2305
2851
  label: device.name,
2306
- connectionType: this.connector.connectionType,
2852
+ connectionType: device.connectionType ?? this.connector.connectionType,
2307
2853
  rssi: device.rssi,
2308
2854
  isConnectable: device.isConnectable,
2309
2855
  serialNumber: device.serialNumber,
@@ -2603,7 +3149,7 @@ var LedgerDeviceManager = class {
2603
3149
 
2604
3150
  // src/signer/SignerManager.ts
2605
3151
  import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
2606
- import { ContextModuleBuilder } from "@ledgerhq/context-module";
3152
+ import { ContextModuleBuilder, ContextModuleChainID } from "@ledgerhq/context-module";
2607
3153
 
2608
3154
  // src/signer/SignerEth.ts
2609
3155
  import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
@@ -2869,7 +3415,7 @@ var SignerManager = class _SignerManager {
2869
3415
  return (args) => new SignerEthBuilder(args);
2870
3416
  }
2871
3417
  static _createContextModule() {
2872
- const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
3418
+ const contextModule = new ContextModuleBuilder({}).setChain(ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
2873
3419
  return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
2874
3420
  }
2875
3421
  static wrapBlindSigningReportNonBlocking(contextModule) {
@@ -3339,7 +3885,8 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
3339
3885
  }
3340
3886
 
3341
3887
  // src/connector/chains/sol.ts
3342
- import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
3888
+ import { bytesToHex, hexToBytes as hexToBytes3, prepareSolanaOffchainMessageV1 } from "@onekeyfe/hwk-adapter-core";
3889
+ import bs58 from "bs58";
3343
3890
 
3344
3891
  // src/signer/SignerSol.ts
3345
3892
  var SignerSol = class {
@@ -3423,8 +3970,21 @@ async function solSignMessage(ctx, sessionId, params) {
3423
3970
  const path = normalizePath(params.path);
3424
3971
  const messageBytes = hexToBytes3(params.message);
3425
3972
  try {
3973
+ if (params.messageVersion === 1) {
3974
+ const preparedMessage = prepareSolanaOffchainMessageV1({
3975
+ message: messageBytes,
3976
+ requiredSigners: params.requiredSigners
3977
+ });
3978
+ const { SignMessageVersion } = await ctx.importLedgerKit(
3979
+ "@ledgerhq/device-signer-kit-solana"
3980
+ );
3981
+ const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
3982
+ version: SignMessageVersion.Raw
3983
+ });
3984
+ return { signature: decodeBase58Signature(result2.signature) };
3985
+ }
3426
3986
  const result = await solSigner.signMessage(path, messageBytes);
3427
- return { signature: result.signature };
3987
+ return { signature: decodeBase58EnvelopeSignature(result.signature) };
3428
3988
  } catch (err) {
3429
3989
  ctx.invalidateSession(sessionId);
3430
3990
  throw ctx.wrapError(err);
@@ -3432,11 +3992,27 @@ async function solSignMessage(ctx, sessionId, params) {
3432
3992
  ctx.clearCanceller(sessionId);
3433
3993
  }
3434
3994
  }
3995
+ function decodeBase58Signature(signature) {
3996
+ const bytes = bs58.decode(signature);
3997
+ if (bytes.length !== 64) {
3998
+ throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
3999
+ }
4000
+ return bytesToHex(bytes);
4001
+ }
4002
+ function decodeBase58EnvelopeSignature(envelope) {
4003
+ const bytes = bs58.decode(envelope);
4004
+ if (bytes.length < 65 || bytes[0] !== 1) {
4005
+ throw new Error("Ledger Solana signature envelope is invalid");
4006
+ }
4007
+ return bytesToHex(bytes.subarray(1, 65));
4008
+ }
3435
4009
  async function _createSolSigner(ctx, sessionId) {
3436
4010
  const dmk = await ctx.getOrCreateDmk();
3437
- const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit("@ledgerhq/context-module");
4011
+ const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
4012
+ "@ledgerhq/context-module"
4013
+ );
3438
4014
  const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
3439
- const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
4015
+ const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
3440
4016
  const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
3441
4017
  const signer = new SignerSol(sdkSigner);
3442
4018
  signer.onInteraction = (interaction) => {
@@ -3452,6 +4028,86 @@ async function _createSolSigner(ctx, sessionId) {
3452
4028
  return signer;
3453
4029
  }
3454
4030
 
4031
+ // src/connector/chains/zcash.ts
4032
+ import { bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
4033
+
4034
+ // src/signer/SignerZcash.ts
4035
+ var SignerZcash = class {
4036
+ // eslint-disable-next-line no-useless-constructor, no-empty-function
4037
+ constructor(_sdk) {
4038
+ this._sdk = _sdk;
4039
+ }
4040
+ /** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
4041
+ async getFullViewingKey(derivationPath, options) {
4042
+ const action = this._sdk.getFullViewingKey(derivationPath, options);
4043
+ return deviceActionToPromise(
4044
+ action,
4045
+ this.onInteraction,
4046
+ void 0,
4047
+ this.onRegisterCanceller
4048
+ );
4049
+ }
4050
+ /** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
4051
+ async getShieldedAddress(derivationPath, options) {
4052
+ const action = this._sdk.getShieldedAddress(derivationPath, options);
4053
+ return deviceActionToPromise(
4054
+ action,
4055
+ this.onInteraction,
4056
+ void 0,
4057
+ this.onRegisterCanceller
4058
+ );
4059
+ }
4060
+ };
4061
+
4062
+ // src/connector/chains/zcash.ts
4063
+ async function zcashGetFullViewingKey(ctx, sessionId, params) {
4064
+ const signer = await _createZcashSigner(ctx, sessionId);
4065
+ const path = normalizePath(params.path);
4066
+ const mode = params.mode ?? "ufvk";
4067
+ try {
4068
+ const result = await signer.getFullViewingKey(path, { mode });
4069
+ if (result.mode === "ufvk") {
4070
+ return { path: params.path, mode, ufvk: result.fullViewingKey };
4071
+ }
4072
+ return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
4073
+ } catch (err) {
4074
+ ctx.invalidateSession(sessionId);
4075
+ throw ctx.wrapError(err);
4076
+ } finally {
4077
+ ctx.clearCanceller(sessionId);
4078
+ }
4079
+ }
4080
+ async function zcashGetShieldedAddress(ctx, sessionId, params) {
4081
+ const signer = await _createZcashSigner(ctx, sessionId);
4082
+ const path = normalizePath(params.path);
4083
+ try {
4084
+ const result = await signer.getShieldedAddress(path, {
4085
+ checkOnDevice: params.showOnDevice ?? false
4086
+ });
4087
+ return { address: result.address, path: params.path };
4088
+ } catch (err) {
4089
+ ctx.invalidateSession(sessionId);
4090
+ throw ctx.wrapError(err);
4091
+ } finally {
4092
+ ctx.clearCanceller(sessionId);
4093
+ }
4094
+ }
4095
+ async function _createZcashSigner(ctx, sessionId) {
4096
+ const dmk = await ctx.getOrCreateDmk();
4097
+ const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
4098
+ const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
4099
+ const signer = new SignerZcash(sdkSigner);
4100
+ signer.onInteraction = (interaction) => {
4101
+ debugLog("[LedgerConnector] zcash.onInteraction:", interaction);
4102
+ ctx.emit("ui-event", {
4103
+ type: collapseSignerInteraction(interaction),
4104
+ payload: { sessionId }
4105
+ });
4106
+ };
4107
+ signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
4108
+ return signer;
4109
+ }
4110
+
3455
4111
  // src/connector/chains/tron.ts
3456
4112
  import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
3457
4113
  import Trx from "@ledgerhq/hw-app-trx";
@@ -3472,6 +4128,7 @@ var APP_NAME_MAP = {
3472
4128
  BTC: "Bitcoin",
3473
4129
  SOL: "Solana",
3474
4130
  TRX: "Tron",
4131
+ ZEC: "Zcash",
3475
4132
  XRP: "XRP",
3476
4133
  ADA: "Cardano",
3477
4134
  DOT: "Polkadot",
@@ -3985,7 +4642,7 @@ var DeviceApps = class {
3985
4642
  seTargetId: v.seTargetId,
3986
4643
  mcuTargetId: v.mcuTargetId,
3987
4644
  seVersion: v.seVersion,
3988
- seFlagsHex: bytesToHex2(v.seFlags),
4645
+ seFlagsHex: bytesToHex3(v.seFlags),
3989
4646
  mcuSephVersion: v.mcuSephVersion,
3990
4647
  mcuBootloaderVersion: v.mcuBootloaderVersion,
3991
4648
  hwVersion: v.hwVersion
@@ -4059,7 +4716,7 @@ var DeviceApps = class {
4059
4716
  }
4060
4717
  }
4061
4718
  };
4062
- function bytesToHex2(bytes) {
4719
+ function bytesToHex3(bytes) {
4063
4720
  if (!bytes) return "";
4064
4721
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
4065
4722
  }
@@ -4102,7 +4759,8 @@ var METHOD_PREFIX_TO_APP_NAME = {
4102
4759
  evm: "Ethereum",
4103
4760
  btc: "Bitcoin",
4104
4761
  sol: "Solana",
4105
- tron: "Tron"
4762
+ tron: "Tron",
4763
+ zcash: "Zcash"
4106
4764
  };
4107
4765
  var HARDWARE_ERROR_CODE_VALUES = new Set(
4108
4766
  Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
@@ -4142,6 +4800,8 @@ async function defaultLedgerKitImporter(pkg) {
4142
4800
  return import("@ledgerhq/device-signer-kit-bitcoin");
4143
4801
  case "@ledgerhq/device-signer-kit-solana":
4144
4802
  return import("@ledgerhq/device-signer-kit-solana");
4803
+ case "@ledgerhq/device-signer-kit-zcash":
4804
+ return import("@ledgerhq/device-signer-kit-zcash");
4145
4805
  case "@ledgerhq/context-module":
4146
4806
  return import("@ledgerhq/context-module");
4147
4807
  default:
@@ -4559,6 +5219,11 @@ var LedgerConnectorBase = class {
4559
5219
  return solSignTransaction(ctx, sessionId, params);
4560
5220
  case "solSignMessage":
4561
5221
  return solSignMessage(ctx, sessionId, params);
5222
+ // ZCASH
5223
+ case "zcashGetFullViewingKey":
5224
+ return zcashGetFullViewingKey(ctx, sessionId, params);
5225
+ case "zcashGetShieldedAddress":
5226
+ return zcashGetShieldedAddress(ctx, sessionId, params);
4562
5227
  // TRON
4563
5228
  case "tronGetAddress":
4564
5229
  return tronGetAddress(ctx, sessionId, params);
@@ -4888,7 +5553,7 @@ var LedgerConnectorBase = class {
4888
5553
  * at every catch site. Falls through unchanged for unknown methods.
4889
5554
  */
4890
5555
  _ctxForMethod(method) {
4891
- const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
5556
+ const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
4892
5557
  const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
4893
5558
  if (!defaultAppName) return this._ctx;
4894
5559
  return {
@@ -4932,6 +5597,7 @@ export {
4932
5597
  SignerEth,
4933
5598
  SignerManager,
4934
5599
  SignerSol,
5600
+ SignerZcash,
4935
5601
  debugLog,
4936
5602
  deviceActionToPromise,
4937
5603
  isDeviceLockedError,