@onekeyfe/hwk-ledger-adapter 1.2.3-alpha.4 → 1.2.3-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,41 +6,30 @@ import {
6
6
  DeviceJobQueue,
7
7
  EConnectorInteraction,
8
8
  HardwareErrorCode as HardwareErrorCode3,
9
- OperationRegistry,
10
- SDK,
11
9
  TypedEventEmitter,
12
10
  UI_REQUEST,
13
11
  UI_REQUEST_PREEMPTED_TAG,
14
12
  UiRequestRegistry,
15
- canReplayHardwareMethodAfterTransportFailure,
16
13
  createHwkError,
17
14
  deriveDeviceFingerprint,
18
15
  failure as failure2,
19
- isHardwareOperationId,
20
- isHwkRecoveryHint,
21
- operationMayHaveCompletedParams,
22
16
  rehydrateConnectorError,
23
- requestBleDeviceSelection,
24
- requestSaveDeviceBinding,
25
- resolveSearchTargetReusePolicy,
26
17
  success
27
18
  } from "@onekeyfe/hwk-adapter-core";
28
19
 
29
20
  // src/errors.ts
30
- import {
31
- HardwareErrorCode,
32
- defaultOriginForCode,
33
- defaultRecoveryForCode,
34
- enrichErrorMessage
35
- } from "@onekeyfe/hwk-adapter-core";
36
- function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
21
+ import { HardwareErrorCode, enrichErrorMessage } from "@onekeyfe/hwk-adapter-core";
22
+ var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
23
+ function createMultipleUsbLedgerDevicesError() {
24
+ return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
25
+ code: HardwareErrorCode.DeviceOneDeviceOnly
26
+ });
27
+ }
28
+ function ledgerFailure(code, error, appName, tag, params) {
37
29
  const payload = { error, code };
38
30
  if (appName !== void 0) payload.appName = appName;
39
31
  if (tag !== void 0) payload._tag = tag;
40
32
  if (params !== void 0) payload.params = params;
41
- const resolvedOrigin = origin ?? defaultOriginForCode(code);
42
- if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
43
- payload.recovery = recovery ?? defaultRecoveryForCode(code);
44
33
  return { success: false, payload };
45
34
  }
46
35
  var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
@@ -224,6 +213,7 @@ function isBlePairingFailureError(err) {
224
213
  return false;
225
214
  }
226
215
  var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
216
+ ERROR_TAG.DeviceLocked,
227
217
  ERROR_TAG.DeviceNotAdvertising,
228
218
  ERROR_TAG.BlePairingTimeout,
229
219
  ERROR_TAG.BleGattBondingFailed,
@@ -458,30 +448,11 @@ function mapLedgerError(err, opts) {
458
448
  }
459
449
  const errAppName = err && typeof err === "object" ? err.appName : void 0;
460
450
  const appName = errAppName ?? opts?.defaultAppName;
461
- return {
462
- code,
463
- message: enrichErrorMessage(code, originalMessage),
464
- origin: defaultOriginForCode(code),
465
- appName
466
- };
467
- }
468
-
469
- // src/utils/queueKey.ts
470
- var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
471
- function ledgerQueueKey({
472
- operationId,
473
- connectId
474
- }) {
475
- return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
451
+ return { code, message: enrichErrorMessage(code, originalMessage), appName };
476
452
  }
477
453
 
478
454
  // src/adapter/methods/allNetworkGetAddress.ts
479
- import {
480
- HardwareErrorCode as HardwareErrorCode2,
481
- failure,
482
- resolveHardwareOperationTarget,
483
- runAllNetworkGetAddress
484
- } from "@onekeyfe/hwk-adapter-core";
455
+ import { HardwareErrorCode as HardwareErrorCode2, failure, runAllNetworkGetAddress } from "@onekeyfe/hwk-adapter-core";
485
456
 
486
457
  // src/utils/sdkEventBus.ts
487
458
  var listeners = /* @__PURE__ */ new Set();
@@ -538,102 +509,65 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
538
509
  var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
539
510
  function createAllNetworkGetAddress({
540
511
  callChain,
541
- getChainFingerprint,
542
- retainOperation,
543
- errorToFailure,
544
- createCancelScope
512
+ getChainFingerprint
545
513
  }) {
546
514
  return async function allNetworkGetAddress(connectId, _deviceId, params) {
547
- debugLog("[LedgerAdapter][REQ]", {
548
- method: "allNetworkGetAddress",
549
- connectId,
550
- itemCount: params.bundle.length
551
- });
552
- const target = resolveHardwareOperationTarget(connectId, params.operationId, "ledger");
553
- if (!target.success) return target;
554
- const effectiveTargetId = target.payload.targetId ?? "";
555
- let releaseOperationRetention;
556
- try {
557
- releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
558
- } catch (error) {
559
- return errorToFailure(error);
560
- }
561
- const cancelScope = createCancelScope(
562
- ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
563
- );
515
+ debugLog("[LedgerAdapter][REQ]", { method: "allNetworkGetAddress", connectId, params });
564
516
  const installContext = {};
565
517
  const commonParams = {
566
- autoInstallApp: params.autoInstallApp,
567
- operationId: target.payload.operationId,
568
- knownConnections: params.knownConnections,
569
- extra: params.extra,
570
- allowDeviceSelection: params.allowDeviceSelection
518
+ autoInstallApp: params.autoInstallApp
571
519
  };
572
520
  const chainFingerprints = /* @__PURE__ */ new Map();
573
- try {
574
- const result = await runAllNetworkGetAddress({
575
- connectId: effectiveTargetId,
576
- deviceId: _deviceId,
577
- params,
578
- normalizeItem: normalizeLedgerAllNetworkItem,
579
- buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
580
- callItem: async ({ method, chain, item }) => {
581
- if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
582
- const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
583
- return callAllNetworkMethod(
584
- callChain,
585
- effectiveTargetId,
586
- itemDeviceId,
587
- method,
588
- item,
589
- commonParams,
590
- installContext
591
- );
592
- },
593
- attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
594
- getChainFingerprint,
595
- effectiveTargetId,
521
+ const result = await runAllNetworkGetAddress({
522
+ connectId,
523
+ deviceId: _deviceId,
524
+ params,
525
+ normalizeItem: normalizeLedgerAllNetworkItem,
526
+ buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
527
+ callItem: async ({ method, chain, item }) => {
528
+ const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
529
+ return callAllNetworkMethod(
530
+ callChain,
531
+ connectId,
532
+ itemDeviceId,
533
+ method,
596
534
  item,
597
- chain,
598
- payload,
599
- chainFingerprints,
600
- installContext,
601
- cancelScope.signal
602
- ),
603
- shouldAbortBundle: isTopLevelAllNetworkFailure,
604
- buildTopLevelFailure: (response) => {
605
- const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
606
- return failure(
607
- code,
608
- response.payload?.error ?? "All-network get-address aborted",
609
- response.payload?.params
610
- );
611
- }
612
- });
613
- debugLog("[LedgerAdapter][RES]", {
614
- method: "allNetworkGetAddress",
615
- success: result.success,
616
- payload: result
617
- });
618
- return result;
619
- } finally {
620
- cancelScope.release();
621
- releaseOperationRetention?.();
622
- }
535
+ commonParams,
536
+ installContext
537
+ );
538
+ },
539
+ attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
540
+ getChainFingerprint,
541
+ connectId,
542
+ item,
543
+ chain,
544
+ payload,
545
+ chainFingerprints
546
+ ),
547
+ shouldAbortBundle: isTopLevelAllNetworkFailure,
548
+ buildTopLevelFailure: (response) => {
549
+ const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
550
+ return failure(
551
+ code,
552
+ response.payload?.error ?? "All-network get-address aborted",
553
+ response.payload?.params
554
+ );
555
+ }
556
+ });
557
+ debugLog("[LedgerAdapter][RES]", {
558
+ method: "allNetworkGetAddress",
559
+ success: result.success,
560
+ payload: result
561
+ });
562
+ return result;
623
563
  };
624
564
  }
625
- function buildCancelledFailure(signal) {
626
- const reason = signal.reason;
627
- const code = typeof reason?.code === "number" ? reason.code : HardwareErrorCode2.UserAborted;
628
- const message = typeof reason?.message === "string" ? reason.message : "";
629
- return failure(code, message || "All-network get-address cancelled");
630
- }
631
565
  function isTopLevelAllNetworkFailure(response) {
632
566
  if (response.success) {
633
567
  return false;
634
568
  }
635
569
  const code = response.payload?.code;
636
- return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.DeviceDisconnected || code === HardwareErrorCode2.OperationTimeout || code === HardwareErrorCode2.TransportError || code === HardwareErrorCode2.OperationEnded || code === HardwareErrorCode2.OperationNotFound || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
570
+ return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
637
571
  }
638
572
  function getItemDeviceId(item) {
639
573
  const { deviceId } = item;
@@ -663,18 +597,8 @@ function normalizeLedgerAllNetworkItem(method, item) {
663
597
  const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
664
598
  return coin ? { ...item, coin } : item;
665
599
  }
666
- async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
667
- const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
668
- if (!knownFingerprint && cancelSignal.aborted) {
669
- const cancelled = buildCancelledFailure(cancelSignal);
670
- return { ...item, success: false, payload: cancelled.payload };
671
- }
672
- const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
673
- getChainFingerprint,
674
- context.connection?.connectId ?? connectId,
675
- chain,
676
- context
677
- );
600
+ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
601
+ const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(getChainFingerprint, connectId, chain);
678
602
  if (!fingerprint) {
679
603
  return buildFingerprintBootstrapFailure(item, chain);
680
604
  }
@@ -695,8 +619,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
695
619
  }
696
620
  };
697
621
  }
698
- async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
699
- const response = await getChainFingerprint(connectId, chain, context);
622
+ async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
623
+ const response = await getChainFingerprint(connectId, chain);
700
624
  return response.success ? response.payload : "";
701
625
  }
702
626
  function buildFingerprintBootstrapFailure(item, chain) {
@@ -801,27 +725,8 @@ var _LedgerAdapter = class _LedgerAdapter {
801
725
  constructor(connector, options) {
802
726
  this.vendor = "ledger";
803
727
  this.emitter = new TypedEventEmitter();
804
- this._operations = new OperationRegistry({
805
- vendor: "ledger",
806
- onEnded: (operation, reason) => {
807
- const binding = this._pendingOperationBindings.get(operation.operationId);
808
- if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
809
- this._finishBleBinding("cancelled");
810
- }
811
- this._pendingOperationBindings.delete(operation.operationId);
812
- this.emitter.emit(SDK.OPERATION_ENDED, {
813
- type: SDK.OPERATION_ENDED,
814
- payload: { operationId: operation.operationId, reason }
815
- });
816
- if (reason === "timeout") {
817
- void this._releaseOperationConnection(operation);
818
- }
819
- }
820
- });
821
728
  this._discoveredDevices = /* @__PURE__ */ new Map();
822
729
  this._sessions = /* @__PURE__ */ new Map();
823
- this._pendingOperationBindings = /* @__PURE__ */ new Map();
824
- this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
825
730
  this._uiRegistry = new UiRequestRegistry();
826
731
  // BTC App rejects account index >= 100 unless display=true. Cached per
827
732
  // adapter instance: first 100+ path asks the user once via UI request,
@@ -835,31 +740,10 @@ var _LedgerAdapter = class _LedgerAdapter {
835
740
  this._deviceAuthenticityQueueTail = Promise.resolve();
836
741
  // Shared across concurrent callers — only `cancel()` aborts.
837
742
  this._doConnectAbortController = null;
838
- this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
839
- this._connectorIdleWaiters = /* @__PURE__ */ new Set();
840
- this._resetPromise = null;
841
- this._stateGeneration = 0;
842
- this._connectorTeardownTail = Promise.resolve();
843
- this._pendingConnectorTeardowns = 0;
844
- this._activeOperationJobs = /* @__PURE__ */ new Set();
845
- this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
846
743
  this._installProgressLastEmittedValue = -Infinity;
847
744
  this.allNetworkGetAddress = createAllNetworkGetAddress({
848
745
  callChain: this.callChain.bind(this),
849
- getChainFingerprint: async (connectId, chain, context) => {
850
- try {
851
- const fingerprint = await this._computeChainFingerprint(
852
- chain,
853
- (method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
854
- );
855
- return success(fingerprint);
856
- } catch (error) {
857
- return this.errorToFailure(error);
858
- }
859
- },
860
- retainOperation: (operationId) => this._operations.retain(operationId),
861
- errorToFailure: (error) => this.errorToFailure(error),
862
- createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
746
+ getChainFingerprint: (connectId, chain) => this.getChainFingerprint(connectId, "", chain)
863
747
  });
864
748
  // ---------------------------------------------------------------------------
865
749
  // Private helpers
@@ -869,7 +753,7 @@ var _LedgerAdapter = class _LedgerAdapter {
869
753
  *
870
754
  * - If a session already exists for the given connectId, reuse it.
871
755
  * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
872
- * - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
756
+ * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
873
757
  */
874
758
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
875
759
  this._connectingPromise = null;
@@ -886,12 +770,6 @@ var _LedgerAdapter = class _LedgerAdapter {
886
770
  });
887
771
  };
888
772
  this.deviceDisconnectHandler = (data) => {
889
- const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
890
- if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
891
- this._pendingOperationDisconnects.add(activeOperation.operationId);
892
- } else {
893
- this._operations.endByConnectionKey(data.connectId, "disconnect");
894
- }
895
773
  this._discoveredDevices.delete(data.connectId);
896
774
  this._sessions.delete(data.connectId);
897
775
  this.emitter.emit(DEVICE.DISCONNECT, {
@@ -948,28 +826,11 @@ var _LedgerAdapter = class _LedgerAdapter {
948
826
  this._jobQueue = new DeviceJobQueue();
949
827
  this.registerEventListeners();
950
828
  }
951
- _finishBleBinding(status) {
952
- const selectionRequestId = this._bindingSelectionRequestId;
953
- this._bindingSelectionRequestId = void 0;
954
- if (selectionRequestId)
955
- this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
956
- type: UI_REQUEST.DEVICE_BINDING_STATUS,
957
- payload: { selectionRequestId, status }
958
- });
959
- }
960
- _isBleConnection() {
961
- return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
962
- }
963
829
  // Transport
964
830
  get activeTransport() {
965
- return this._isBleConnection() ? "ble" : "hid";
831
+ return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
966
832
  }
967
833
  getAvailableTransports() {
968
- if (this.connector.availableTransports) {
969
- return this.connector.availableTransports.map(
970
- (transport) => transport === "ble" ? "ble" : "hid"
971
- );
972
- }
973
834
  return this.activeTransport ? [this.activeTransport] : [];
974
835
  }
975
836
  // Connector is bound at construction; switching requires a new adapter.
@@ -986,39 +847,21 @@ var _LedgerAdapter = class _LedgerAdapter {
986
847
  * The next operation will re-discover and re-connect automatically.
987
848
  */
988
849
  resetState() {
989
- void this._resetStateAndDisconnectSessions();
990
- }
991
- _resetStateAndDisconnectSessions() {
992
- if (this._resetPromise) return this._resetPromise;
993
- const sessionIds = new Set(this._sessions.values());
994
- this._stateGeneration += 1;
995
- this._finishBleBinding("cancelled");
996
- this._operations.endAll("runtime-reset");
997
- this._doConnectAbortController?.abort();
998
850
  this._discoveredDevices.clear();
999
851
  this._sessions.clear();
1000
- this._verifiedBleReconnectTargets.clear();
1001
852
  this._connectingPromise = null;
1002
- this._doConnectAbortController = null;
1003
853
  this._uiRegistry.reset();
1004
854
  this._jobQueue.clear();
1005
855
  this._btcHighIndexConfirmedThisSession = false;
1006
- const resetPromise = this._runConnectorTeardown(async () => {
1007
- for (const sessionId of sessionIds) {
1008
- await this.connector.disconnect(sessionId).catch(() => void 0);
1009
- }
1010
- });
1011
- this._resetPromise = resetPromise;
1012
- return resetPromise.finally(() => {
1013
- if (this._resetPromise === resetPromise) {
1014
- this._resetPromise = null;
1015
- }
1016
- });
1017
856
  }
1018
857
  async dispose() {
1019
- await this._resetStateAndDisconnectSessions();
858
+ this._uiRegistry.reset();
859
+ this._jobQueue.clear();
1020
860
  this.unregisterEventListeners();
1021
861
  this.connector.reset();
862
+ this._discoveredDevices.clear();
863
+ this._sessions.clear();
864
+ this._btcHighIndexConfirmedThisSession = false;
1022
865
  this.emitter.removeAllListeners();
1023
866
  }
1024
867
  uiResponse(response) {
@@ -1028,32 +871,17 @@ var _LedgerAdapter = class _LedgerAdapter {
1028
871
  // Device management
1029
872
  // ---------------------------------------------------------------------------
1030
873
  async searchDevices(options) {
1031
- return this._searchDevices(options);
1032
- }
1033
- async _searchDevices(options, signal) {
1034
874
  debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
1035
875
  try {
1036
876
  if (options?.resetSession) {
1037
- await this._resetStateAndDisconnectSessions();
1038
- } else {
1039
- await this._connectorTeardownTail;
1040
- }
1041
- await this._ensureDevicePermission(void 0, void 0, signal);
1042
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1043
- const stateGeneration = this._stateGeneration;
1044
- const devices = await this.connector.searchDevices(
1045
- options?.transportType ? {
1046
- transportType: options.transportType,
1047
- waitForAll: options.waitForAllTransports
1048
- } : void 0
1049
- );
1050
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1051
- if (stateGeneration !== this._stateGeneration) {
1052
- throw createHwkError({
1053
- code: HardwareErrorCode3.UserAborted,
1054
- message: "Ledger discovery was reset"
1055
- });
877
+ this._doConnectAbortController?.abort();
878
+ this._sessions.clear();
879
+ this._connectingPromise = null;
880
+ this._doConnectAbortController = null;
881
+ this._btcHighIndexConfirmedThisSession = false;
1056
882
  }
883
+ await this._ensureDevicePermission();
884
+ const devices = await this.connector.searchDevices();
1057
885
  this._discoveredDevices.clear();
1058
886
  for (const d of devices) {
1059
887
  if (d.connectId) {
@@ -1061,7 +889,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1061
889
  }
1062
890
  }
1063
891
  if (this._discoveredDevices.size === 0) {
1064
- await this._ensureDevicePermission(void 0, void 0, signal);
892
+ await this._ensureDevicePermission();
1065
893
  }
1066
894
  const result = Array.from(this._discoveredDevices.values());
1067
895
  debugLog("[LedgerAdapter][RES]", {
@@ -1080,285 +908,41 @@ var _LedgerAdapter = class _LedgerAdapter {
1080
908
  throw err;
1081
909
  }
1082
910
  }
1083
- async searchDeviceTargets(options) {
1084
- const devices = await this.searchDevices(options);
1085
- return devices.map((device) => ({
1086
- searchTargetId: device.connectId,
1087
- searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
1088
- vendor: "ledger",
1089
- connectionType: device.connectionType,
1090
- kind: "physical",
1091
- label: device.label,
1092
- model: device.model,
1093
- modelName: device.modelName,
1094
- serialNumber: device.serialNumber
1095
- }));
1096
- }
1097
- async listConnectionTargets(options) {
1098
- const targets = await this.searchDeviceTargets(options);
1099
- return targets.map(({ searchTargetId, ...target }) => ({
1100
- ...target,
1101
- targetId: searchTargetId
1102
- }));
1103
- }
1104
911
  // USB single-session invariant: evict all sessions, best-effort (see connectDevice).
1105
- async _evictAllSessions(preserveOperationId) {
1106
- this._operations.endAll("explicit", preserveOperationId);
912
+ async _evictAllSessions() {
1107
913
  if (this._sessions.size === 0) return;
1108
914
  const stale = [...this._sessions.values()];
1109
915
  this._sessions.clear();
1110
- await this._runConnectorTeardown(async () => {
1111
- for (const sid of stale) {
1112
- try {
1113
- await this.connector.disconnect(sid);
1114
- } catch {
1115
- }
916
+ for (const sid of stale) {
917
+ try {
918
+ await this.connector.disconnect(sid);
919
+ } catch {
1116
920
  }
1117
- });
921
+ }
1118
922
  }
1119
923
  static _createDeviceBusyError(method) {
1120
924
  return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
1121
925
  code: HardwareErrorCode3.DeviceBusy
1122
926
  });
1123
927
  }
1124
- async connectDevice(searchTargetId) {
1125
- try {
1126
- return await this._jobQueue.enqueue(
1127
- searchTargetId || "__ledger_connect__",
1128
- async () => {
1129
- const connected = await this._connectTarget(searchTargetId);
1130
- if (!connected.success) return connected;
1131
- return this._createOperation(searchTargetId, connected.payload);
1132
- },
1133
- {
1134
- label: "connectDevice",
1135
- rejectIfBusy: true,
1136
- busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
1137
- }
1138
- );
1139
- } catch (error) {
1140
- return this.errorToFailure(error);
1141
- }
1142
- }
1143
- async bindBleDevice(params) {
1144
- if (params.identity.vendor !== "ledger" || !params.identity.value) {
1145
- return failure2(HardwareErrorCode3.InvalidParams, "Ledger wallet identity is required");
1146
- }
1147
- const { chain, value: expectedFingerprint } = params.identity;
1148
- try {
1149
- return await this._jobQueue.enqueue(
1150
- expectedFingerprint,
1151
- async (signal) => {
1152
- if (!this.getAvailableTransports().includes("ble")) {
1153
- throw createHwkError({
1154
- code: HardwareErrorCode3.TransportNotAvailable,
1155
- message: "Ledger Bluetooth transport is not available"
1156
- });
1157
- }
1158
- this._activeConnectionType = "ble";
1159
- await this._ensureDevicePermission(void 0, void 0, signal);
1160
- const attempt = {
1161
- extra: params.extra,
1162
- bindingReason: "manual-rebind"
1163
- };
1164
- try {
1165
- for (; ; ) {
1166
- const connectId = await this._connectFirstOrSelect(
1167
- [],
1168
- void 0,
1169
- true,
1170
- void 0,
1171
- attempt,
1172
- signal
1173
- );
1174
- const sessionId = this._sessions.get(connectId);
1175
- if (!sessionId) {
1176
- throw createHwkError({
1177
- code: HardwareErrorCode3.DeviceDisconnected,
1178
- message: "Selected Ledger connection ended"
1179
- });
1180
- }
1181
- let saved = false;
1182
- try {
1183
- const installContext = {
1184
- connection: { connectId, sessionId }
1185
- };
1186
- const fingerprint = await this._computeChainFingerprint(
1187
- chain,
1188
- (method, callParams) => this._runConnectorCall(
1189
- connectId,
1190
- method,
1191
- callParams,
1192
- signal,
1193
- void 0,
1194
- void 0,
1195
- { autoInstallApp: true },
1196
- installContext
1197
- )
1198
- );
1199
- if (fingerprint !== expectedFingerprint) {
1200
- attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
1201
- attempt.rejectedConnectIds.add(connectId);
1202
- attempt.rejectedConnectId = connectId;
1203
- } else {
1204
- const persisted = await this._publishVerifiedBleBinding(
1205
- connectId,
1206
- chain,
1207
- fingerprint,
1208
- attempt,
1209
- void 0,
1210
- signal
1211
- );
1212
- if (!persisted) {
1213
- throw createHwkError({
1214
- code: HardwareErrorCode3.UnknownError,
1215
- message: "Bluetooth binding could not be saved",
1216
- origin: "host"
1217
- });
1218
- }
1219
- saved = true;
1220
- return success(connectId);
1221
- }
1222
- } finally {
1223
- if (!saved && this._sessions.get(connectId) === sessionId) {
1224
- this._sessions.delete(connectId);
1225
- const teardown = this._runConnectorTeardown(
1226
- () => this.connector.disconnect(sessionId)
1227
- ).catch(() => void 0);
1228
- if (!signal.aborted) await teardown;
1229
- }
1230
- }
1231
- }
1232
- } catch (error) {
1233
- this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
1234
- throw error;
1235
- }
1236
- },
1237
- {
1238
- label: "bindBleDevice",
1239
- rejectIfBusy: true,
1240
- busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
1241
- }
1242
- );
1243
- } catch (error) {
1244
- return this.errorToFailure(error);
1245
- }
1246
- }
1247
- async acquireOperation(connectId, context) {
1248
- try {
1249
- return await this._jobQueue.enqueue(
1250
- connectId || "__ledger_acquire__",
1251
- async (signal) => {
1252
- const transport = this._isBleConnection() ? "ble" : "usb";
1253
- const hint = context.knownConnections?.find(
1254
- (connection) => connection.transport === transport
1255
- );
1256
- const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
1257
- await this._ensureDevicePermission(target, void 0, signal);
1258
- const attempt = {
1259
- ...context,
1260
- extra: context.extra ? { ...context.extra } : void 0
1261
- };
1262
- try {
1263
- const resolvedConnectId = await this.ensureConnected(
1264
- target,
1265
- signal,
1266
- true,
1267
- void 0,
1268
- attempt
1269
- );
1270
- _LedgerAdapter._throwIfAborted(signal);
1271
- const result = this._createOperation(connectId, resolvedConnectId);
1272
- if (result.success && attempt.selectedConnection) {
1273
- this._pendingOperationBindings.set(result.payload, attempt);
1274
- }
1275
- return result;
1276
- } catch (error) {
1277
- this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
1278
- throw error;
1279
- }
1280
- },
1281
- {
1282
- label: "acquireOperation",
1283
- rejectIfBusy: true,
1284
- busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
1285
- }
1286
- );
1287
- } catch (error) {
1288
- return this.errorToFailure(error);
1289
- }
1290
- }
1291
- _createOperation(searchTargetId, resolvedConnectId) {
1292
- this._operations.endByConnectionKey(resolvedConnectId, "explicit");
1293
- const sessionId = this._sessions.get(resolvedConnectId);
1294
- if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
1295
- const device = this._discoveredDevices.get(resolvedConnectId) ?? {
1296
- vendor: "ledger",
1297
- model: "unknown",
1298
- firmwareVersion: "",
1299
- deviceId: "",
1300
- connectId: resolvedConnectId,
1301
- connectionType: this.connector.connectionType
1302
- };
1303
- const operation = this._operations.create({
1304
- searchTargetId,
1305
- connectId: resolvedConnectId,
1306
- device,
1307
- connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
1308
- });
1309
- return success(operation.operationId);
1310
- }
1311
- async _connectTarget(connectId, preserveOperationId, signal) {
928
+ async connectDevice(connectId) {
1312
929
  debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
1313
930
  try {
1314
- this._assertConnectorReady("connectDevice");
1315
- const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
1316
- if (discoveredType === "usb" || discoveredType === "ble") {
1317
- this._activeConnectionType = discoveredType;
1318
- }
1319
- if (this._isBleConnection() && !connectId) {
931
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
1320
932
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
1321
933
  code: HardwareErrorCode3.DeviceNotFound
1322
934
  });
1323
935
  }
1324
- await this._ensureDevicePermission(connectId, void 0, signal);
1325
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1326
- if (this.connector.availableTransports && this.connector.availableTransports.length > 1) {
1327
- await this._evictAllSessions(preserveOperationId);
1328
- } else if (this._isBleConnection()) {
1329
- const previousSessionId = this._sessions.get(connectId);
1330
- this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
1331
- if (previousSessionId) {
1332
- this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
1333
- this._sessions.delete(connectId);
1334
- await this.connector.disconnect(previousSessionId).catch(() => void 0);
1335
- }
1336
- } else {
1337
- await this._evictAllSessions(preserveOperationId);
1338
- }
1339
- if (signal) _LedgerAdapter._throwIfAborted(signal);
1340
- const stateGeneration = this._stateGeneration;
1341
- const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
1342
- let session;
1343
- try {
1344
- session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
1345
- transportType: this._activeConnectionType ?? "usb"
1346
- }) : await this.connector.connect(connectId);
1347
- if (signal?.aborted || stateGeneration !== this._stateGeneration) {
1348
- await this.connector.disconnect(session.sessionId).catch(() => void 0);
1349
- throw Object.assign(new Error("Ledger connection aborted"), {
1350
- code: HardwareErrorCode3.UserAborted
1351
- });
1352
- }
1353
- } finally {
1354
- releaseOperation();
936
+ if (!isLedgerBleConnectionType(this.connector.connectionType)) {
937
+ await this._evictAllSessions();
1355
938
  }
1356
- const resolvedConnectId = session.deviceInfo?.connectId || connectId;
1357
- this._sessions.set(resolvedConnectId, session.sessionId);
939
+ await this._ensureDevicePermission(connectId);
940
+ const session = await this.connector.connect(connectId);
941
+ this._sessions.set(connectId, session.sessionId);
1358
942
  if (session.deviceInfo) {
1359
- this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
943
+ this._discoveredDevices.set(connectId, session.deviceInfo);
1360
944
  }
1361
- const result = success(resolvedConnectId);
945
+ const result = success(connectId);
1362
946
  debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
1363
947
  return result;
1364
948
  } catch (err) {
@@ -1371,60 +955,30 @@ var _LedgerAdapter = class _LedgerAdapter {
1371
955
  return failureResult;
1372
956
  }
1373
957
  }
1374
- async releaseOperation(operationId) {
1375
- const operation = this._operations.find(operationId);
1376
- if (!operation) {
1377
- this._operations.resolve(operationId);
1378
- return;
1379
- }
1380
- const endedOperation = this._operations.end(operationId, "explicit");
1381
- if (!endedOperation) return;
1382
- const { connectId } = operation;
958
+ async disconnectDevice(connectId) {
1383
959
  debugLog("[LedgerAdapter][REQ]", {
1384
- method: "releaseOperation",
960
+ method: "disconnectDevice",
1385
961
  connectId,
1386
962
  params: { connectId }
1387
963
  });
1388
964
  try {
1389
- await this._releaseOperationConnection(endedOperation);
1390
- debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
965
+ const sessionId = this._sessions.get(connectId);
966
+ if (sessionId) {
967
+ await this.connector.disconnect(sessionId);
968
+ this._sessions.delete(connectId);
969
+ }
970
+ debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
1391
971
  } catch (err) {
1392
972
  const e = err;
1393
973
  debugLog("[LedgerAdapter][RES]", {
1394
- method: "releaseOperation",
974
+ method: "disconnectDevice",
1395
975
  success: false,
1396
976
  error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
1397
977
  });
1398
978
  throw err;
1399
979
  }
1400
980
  }
1401
- async _releaseOperationConnection(operation) {
1402
- const sessionIds = /* @__PURE__ */ new Set();
1403
- for (const [connectId, sessionId] of this._sessions) {
1404
- if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
1405
- this._sessions.delete(connectId);
1406
- sessionIds.add(sessionId);
1407
- }
1408
- }
1409
- for (const sessionId of sessionIds) {
1410
- await this._runConnectorTeardown(
1411
- () => this.connector.disconnect(sessionId).catch(() => void 0)
1412
- );
1413
- }
1414
- }
1415
- async _releaseLostOperationConnection(operationId) {
1416
- const endedOperation = this._operations.end(operationId, "disconnect");
1417
- if (!endedOperation) return;
1418
- this._discoveredDevices.delete(endedOperation.connectId);
1419
- await this._releaseOperationConnection(endedOperation);
1420
- }
1421
- async getDeviceInfo(connectIdOrOperationId, deviceId) {
1422
- let connectId;
1423
- try {
1424
- connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
1425
- } catch (error) {
1426
- return this.errorToFailure(error);
1427
- }
981
+ async getDeviceInfo(connectId, deviceId) {
1428
982
  debugLog("[LedgerAdapter][REQ]", {
1429
983
  method: "getDeviceInfo",
1430
984
  connectId,
@@ -1462,6 +1016,9 @@ var _LedgerAdapter = class _LedgerAdapter {
1462
1016
  throw err;
1463
1017
  }
1464
1018
  }
1019
+ getSupportedChains() {
1020
+ return ["evm", "btc", "sol", "tron"];
1021
+ }
1465
1022
  // ---------------------------------------------------------------------------
1466
1023
  // Chain call helper
1467
1024
  // ---------------------------------------------------------------------------
@@ -1496,21 +1053,13 @@ var _LedgerAdapter = class _LedgerAdapter {
1496
1053
  if (params && typeof params === "object") {
1497
1054
  const {
1498
1055
  autoInstallApp,
1499
- operationId,
1500
1056
  passphraseState: _passphraseState,
1501
1057
  useEmptyPassphrase: _useEmptyPassphrase,
1502
- knownConnections,
1503
- extra,
1504
- allowDeviceSelection,
1505
1058
  ...rest
1506
1059
  } = params;
1507
1060
  return {
1508
1061
  commonParams: {
1509
- autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
1510
- operationId: typeof operationId === "string" ? operationId : void 0,
1511
- knownConnections,
1512
- extra,
1513
- allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0
1062
+ autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
1514
1063
  },
1515
1064
  rest
1516
1065
  };
@@ -1686,27 +1235,6 @@ var _LedgerAdapter = class _LedgerAdapter {
1686
1235
  );
1687
1236
  }
1688
1237
  // ---------------------------------------------------------------------------
1689
- // Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
1690
- // ---------------------------------------------------------------------------
1691
- zcashGetFullViewingKey(connectId, deviceId, params) {
1692
- return this.callChainWithMergedParams(
1693
- connectId,
1694
- deviceId,
1695
- "zcash",
1696
- "zcashGetFullViewingKey",
1697
- params
1698
- );
1699
- }
1700
- zcashGetShieldedAddress(connectId, deviceId, params) {
1701
- return this.callChainWithMergedParams(
1702
- connectId,
1703
- deviceId,
1704
- "zcash",
1705
- "zcashGetShieldedAddress",
1706
- params
1707
- );
1708
- }
1709
- // ---------------------------------------------------------------------------
1710
1238
  // App management — OS-level Ledger app install / list. Bypasses fingerprint
1711
1239
  // and chain-handler dispatch; installApp progress is forwarded to the adapter
1712
1240
  // emitter via 'ui-event' AppInstallProgress events.
@@ -1795,7 +1323,7 @@ var _LedgerAdapter = class _LedgerAdapter {
1795
1323
  );
1796
1324
  }
1797
1325
  await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
1798
- await this._resetStateAndDisconnectSessions();
1326
+ this.resetState();
1799
1327
  }
1800
1328
  const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
1801
1329
  if (!result.isGenuine) {
@@ -1821,10 +1349,10 @@ var _LedgerAdapter = class _LedgerAdapter {
1821
1349
  if (relayUrl) {
1822
1350
  try {
1823
1351
  await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
1824
- await this._resetStateAndDisconnectSessions();
1352
+ this.resetState();
1825
1353
  } catch {
1826
1354
  this.connector.reset();
1827
- await this._resetStateAndDisconnectSessions();
1355
+ this.resetState();
1828
1356
  }
1829
1357
  }
1830
1358
  }
@@ -1836,20 +1364,6 @@ var _LedgerAdapter = class _LedgerAdapter {
1836
1364
  this.emitter.off(event, listener);
1837
1365
  }
1838
1366
  cancel(connectId) {
1839
- const namedOperationIsLive = (id) => {
1840
- try {
1841
- this._operations.resolve(id);
1842
- return true;
1843
- } catch {
1844
- return false;
1845
- }
1846
- };
1847
- if (isHardwareOperationId(connectId) && !namedOperationIsLive(connectId)) {
1848
- debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
1849
- connectId
1850
- });
1851
- return;
1852
- }
1853
1367
  const userAbortReason = Object.assign(new Error("User aborted operation"), {
1854
1368
  code: HardwareErrorCode3.UserAborted,
1855
1369
  _tag: ERROR_TAG.UserAborted
@@ -1860,46 +1374,14 @@ var _LedgerAdapter = class _LedgerAdapter {
1860
1374
  this._lastCancelReason = void 0;
1861
1375
  }
1862
1376
  }, 2e3);
1863
- const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
1864
- let operationId;
1865
- if (isHardwareOperationId(connectId)) {
1866
- operationId = connectId;
1867
- } else if (!connectId && isHardwareOperationId(activeJobId)) {
1868
- operationId = activeJobId;
1869
- }
1870
- let resolvedConnectId = connectId;
1871
- if (operationId) {
1872
- try {
1873
- resolvedConnectId = this._operations.resolve(operationId).connectId;
1874
- } catch {
1875
- resolvedConnectId = void 0;
1876
- }
1877
- }
1878
1377
  this._uiRegistry.cancel();
1879
- this._finishBleBinding("cancelled");
1880
- const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
1881
- const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
1882
- if (!connectId) this._pendingOperationBindings.clear();
1883
- else if (pendingOperationId) {
1884
- this._pendingOperationBindings.delete(pendingOperationId);
1885
- }
1886
- const queueKeys = /* @__PURE__ */ new Set();
1887
- if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
1888
- if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
1889
- if (queueKeys.size) {
1890
- let cancelledAnyJob = false;
1891
- for (const key of queueKeys) {
1892
- cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
1893
- }
1894
- debugLog("[LedgerAdapter] cancel routed to queue keys", {
1895
- queueKeys: [...queueKeys],
1896
- cancelledAnyJob
1897
- });
1378
+ if (connectId) {
1379
+ this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
1898
1380
  } else {
1899
1381
  this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
1900
1382
  }
1901
- if (resolvedConnectId) {
1902
- const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
1383
+ if (connectId) {
1384
+ const sessionId = this._sessions.get(connectId) ?? connectId;
1903
1385
  void this.connector.cancel(sessionId);
1904
1386
  } else {
1905
1387
  for (const sid of this._sessions.values()) void this.connector.cancel(sid);
@@ -1911,98 +1393,41 @@ var _LedgerAdapter = class _LedgerAdapter {
1911
1393
  // ---------------------------------------------------------------------------
1912
1394
  // Chain fingerprint
1913
1395
  // ---------------------------------------------------------------------------
1914
- /** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
1915
1396
  async getChainFingerprint(connectId, deviceId, chain) {
1916
1397
  try {
1917
1398
  const fingerprint = await this._computeChainFingerprint(
1918
1399
  chain,
1919
1400
  (method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
1920
1401
  );
1921
- if (deviceId) {
1922
- if (fingerprint !== deviceId) {
1923
- return failure2(
1924
- HardwareErrorCode3.DeviceMismatch,
1925
- formatDeviceMismatchError(deviceId, fingerprint)
1926
- );
1927
- }
1928
- if (isHardwareOperationId(connectId)) {
1929
- const operation = this._operations.resolve(connectId);
1930
- await this._publishVerifiedBleBinding(
1931
- operation.connectId,
1932
- chain,
1933
- fingerprint,
1934
- void 0,
1935
- connectId
1936
- );
1937
- }
1938
- }
1939
1402
  return success(fingerprint);
1940
1403
  } catch (err) {
1941
1404
  debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
1942
1405
  return this.errorToFailure(err);
1943
1406
  }
1944
1407
  }
1945
- /** Discovery may select a BLE target before a later call verifies its wallet. */
1946
- async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, operationId, signal) {
1947
- const binding = operationId ? this._pendingOperationBindings.get(operationId) : attempt;
1948
- if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) {
1949
- return false;
1950
- }
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 };
1951
1414
  try {
1952
- const outcome = await requestSaveDeviceBinding(
1953
- this.emitter,
1954
- this._uiRegistry,
1955
- {
1956
- selectionRequestId: binding.selectedConnection.requestId,
1957
- connection: { transport: "ble", connectId },
1958
- identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
1959
- extra: binding.extra
1960
- },
1961
- signal
1415
+ const fingerprint = await this._computeChainFingerprint(
1416
+ chain,
1417
+ (method, params) => this._callConnector(sessionId, method, params)
1962
1418
  );
1963
- if (operationId) this._operations.resolve(operationId);
1964
- if (!outcome.saved) {
1965
- debugLog("[LedgerAdapter] BLE binding not persisted by host", {
1966
- connectId,
1967
- reason: outcome.reason
1968
- });
1969
- }
1970
- return outcome.saved;
1971
- } catch (error) {
1972
- if (operationId) {
1973
- this._pendingOperationBindings.delete(operationId);
1974
- this._operations.end(operationId, "explicit");
1419
+ if (fingerprint === deviceId) {
1420
+ return { success: true };
1975
1421
  }
1976
- const sessionId = this._sessions.get(connectId);
1977
- this._sessions.delete(connectId);
1978
- if (sessionId) {
1979
- const teardown = this._runConnectorTeardown(
1980
- () => this.connector.disconnect(sessionId)
1981
- ).catch(() => void 0);
1982
- if (!signal?.aborted) await teardown;
1983
- }
1984
- throw error;
1985
- } finally {
1986
- if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
1987
- this._bindingSelectionRequestId = void 0;
1988
- }
1989
- if (operationId) {
1990
- this._pendingOperationBindings.delete(operationId);
1422
+ return { success: false, expected: deviceId, actual: fingerprint };
1423
+ } catch (err) {
1424
+ const mapped = mapLedgerError(err);
1425
+ if (mapped.code === HardwareErrorCode3.WrongApp || mapped.code === HardwareErrorCode3.DeviceLocked) {
1426
+ return { success: true };
1991
1427
  }
1428
+ throw err;
1992
1429
  }
1993
1430
  }
1994
- /** Verify on the acquired session without re-entering the job queue. */
1995
- async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
1996
- if (!deviceId) return { success: true };
1997
- const fingerprint = await this._computeChainFingerprint(
1998
- chain,
1999
- (method, params) => this._callConnector(sessionId, method, params)
2000
- );
2001
- if (fingerprint === deviceId) {
2002
- return { success: true };
2003
- }
2004
- return { success: false, expected: deviceId, actual: fingerprint };
2005
- }
2006
1431
  /**
2007
1432
  * Compute the chain fingerprint via a caller-supplied call strategy.
2008
1433
  *
@@ -2030,8 +1455,6 @@ var _LedgerAdapter = class _LedgerAdapter {
2030
1455
  address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
2031
1456
  } else if (chain === "tron") {
2032
1457
  address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
2033
- } else if (chain === "zcash") {
2034
- address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
2035
1458
  } else {
2036
1459
  throw new Error(`Unsupported chain for fingerprint: ${chain}`);
2037
1460
  }
@@ -2161,17 +1584,16 @@ var _LedgerAdapter = class _LedgerAdapter {
2161
1584
  // Layer 1 entry. Caller signal only races the outer awaiter; the shared
2162
1585
  // `_doConnect` runs under its own internal controller so caller A's cancel
2163
1586
  // doesn't kill caller B's await.
2164
- async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
1587
+ async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
2165
1588
  if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
2166
- this._assertConnectorReady("connectDevice");
2167
- if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
1589
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
2168
1590
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
2169
1591
  code: HardwareErrorCode3.DeviceNotFound
2170
1592
  });
2171
1593
  }
2172
1594
  if (connectId && this._sessions.has(connectId)) return connectId;
2173
1595
  if (!connectId && this._sessions.size > 0) {
2174
- if (!this._isBleConnection() && this._sessions.size > 1) {
1596
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
2175
1597
  throw Object.assign(
2176
1598
  new Error(
2177
1599
  "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
@@ -2181,24 +1603,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2181
1603
  }
2182
1604
  return this._sessions.keys().next().value;
2183
1605
  }
2184
- if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
2185
- const controller = new AbortController();
2186
- this._doConnectAbortController = controller;
2187
- const innerSignal = controller.signal;
1606
+ if (!this._connectingPromise) {
1607
+ this._doConnectAbortController = new AbortController();
1608
+ const innerSignal = this._doConnectAbortController.signal;
2188
1609
  this._connectingPromise = (async () => {
2189
1610
  try {
2190
- return await this._doConnect(
2191
- innerSignal,
2192
- connectId,
2193
- allowUsbEphemeralFallback,
2194
- preserveOperationId,
2195
- context
2196
- );
1611
+ return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback);
2197
1612
  } finally {
2198
- if (this._doConnectAbortController === controller) {
2199
- this._connectingPromise = null;
2200
- this._doConnectAbortController = null;
2201
- }
1613
+ this._connectingPromise = null;
1614
+ this._doConnectAbortController = null;
2202
1615
  }
2203
1616
  })();
2204
1617
  }
@@ -2207,51 +1620,22 @@ var _LedgerAdapter = class _LedgerAdapter {
2207
1620
  // Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
2208
1621
  // Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
2209
1622
  // throw DeviceNotFound so the user is kicked out of the loop.
2210
- async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
2211
- _LedgerAdapter._throwIfAborted(internalSignal);
2212
- if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
2213
- this._activeConnectionType = "usb";
2214
- const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
2215
- _LedgerAdapter._throwIfAborted(internalSignal);
2216
- if (usbDevices.length > 0) {
2217
- const knownUsb = context?.knownConnections?.find(
2218
- (connection) => connection.transport === "usb"
2219
- );
2220
- const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
2221
- return this._connectFirstOrSelect(
2222
- usbDevices,
2223
- usbTarget,
2224
- allowUsbEphemeralFallback,
2225
- preserveOperationId,
2226
- context,
2227
- internalSignal
2228
- );
2229
- }
2230
- this._activeConnectionType = "ble";
2231
- const knownBle = context?.knownConnections?.find(
2232
- (connection) => connection.transport === "ble"
2233
- );
2234
- if (knownBle?.transport === "ble") {
2235
- return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
2236
- }
2237
- if (targetConnectId && context?.knownConnections === void 0) {
2238
- throw createHwkError({
2239
- code: HardwareErrorCode3.DeviceNotFound,
2240
- message: "Ledger connection metadata is required before starting Bluetooth binding"
2241
- });
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
+ }
2242
1638
  }
2243
- const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
2244
- return this._connectFirstOrSelect(
2245
- bleDevices,
2246
- void 0,
2247
- allowUsbEphemeralFallback,
2248
- preserveOperationId,
2249
- context,
2250
- internalSignal
2251
- );
2252
- }
2253
- if (this._isBleConnection() && targetConnectId) {
2254
- return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
2255
1639
  }
2256
1640
  let confirms = 0;
2257
1641
  while (!internalSignal.aborted) {
@@ -2259,28 +1643,22 @@ var _LedgerAdapter = class _LedgerAdapter {
2259
1643
  type: EConnectorInteraction.Searching,
2260
1644
  payload: { sessionId: "" }
2261
1645
  });
2262
- let devices = await this._searchDevices(void 0, internalSignal);
2263
- _LedgerAdapter._throwIfAborted(internalSignal);
1646
+ let devices = await this.searchDevices();
2264
1647
  if (devices.length === 0) {
2265
1648
  for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
2266
1649
  await new Promise((resolve) => {
2267
1650
  setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
2268
1651
  });
2269
- _LedgerAdapter._throwIfAborted(internalSignal);
2270
- devices = await this._searchDevices(void 0, internalSignal);
2271
- _LedgerAdapter._throwIfAborted(internalSignal);
1652
+ devices = await this.searchDevices();
2272
1653
  if (devices.length > 0) break;
2273
1654
  }
2274
1655
  }
2275
- if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
1656
+ if (devices.length > 0) {
2276
1657
  try {
2277
1658
  return await this._connectFirstOrSelect(
2278
1659
  devices,
2279
1660
  targetConnectId,
2280
- allowUsbEphemeralFallback,
2281
- preserveOperationId,
2282
- context,
2283
- internalSignal
1661
+ allowUsbEphemeralFallback
2284
1662
  );
2285
1663
  } catch (err) {
2286
1664
  if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
@@ -2309,133 +1687,45 @@ var _LedgerAdapter = class _LedgerAdapter {
2309
1687
  _LedgerAdapter._throwIfAborted(internalSignal);
2310
1688
  throw new Error("_doConnect aborted");
2311
1689
  }
2312
- async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
2313
- _LedgerAdapter._throwIfAborted(signal);
1690
+ async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
2314
1691
  if (targetConnectId) {
2315
1692
  const target = devices.find(
2316
1693
  (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
2317
1694
  );
2318
1695
  if (target) {
2319
- return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
1696
+ return this._connectDeviceOrThrow(target.connectId);
2320
1697
  }
2321
- if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
1698
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1 && allowUsbEphemeralFallback) {
2322
1699
  debugLog(
2323
1700
  `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
2324
1701
  );
2325
- return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
1702
+ return this._connectDeviceOrThrow(devices[0].connectId);
2326
1703
  }
2327
- if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
2328
- const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
2329
- code: HardwareErrorCode3.DeviceNotFound
2330
- });
2331
- if (this._isBleConnection()) {
2332
- err._tag = ERROR_TAG.DeviceNotAdvertising;
2333
- }
2334
- throw err;
1704
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1705
+ code: HardwareErrorCode3.DeviceNotFound
1706
+ });
1707
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1708
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
2335
1709
  }
1710
+ throw err;
2336
1711
  }
2337
- const requiresBleSelection = this._isBleConnection();
2338
- if (requiresBleSelection && !allowUsbEphemeralFallback) {
1712
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
2339
1713
  throw Object.assign(new Error("Ledger BLE connectId is required."), {
2340
1714
  code: HardwareErrorCode3.DeviceNotFound
2341
1715
  });
2342
1716
  }
2343
- if (requiresBleSelection && context?.allowDeviceSelection !== false) {
2344
- const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
2345
- if (context) context.bindingSessionId = bindingSessionId;
2346
- const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
2347
- const knownUsb = context?.knownConnections?.find(
2348
- (connection) => connection.transport === "usb"
2349
- );
2350
- const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
2351
- const { device, requestId } = await requestBleDeviceSelection({
2352
- emitter: this.emitter,
2353
- registry: this._uiRegistry,
2354
- signal,
2355
- allowUsbFallback,
2356
- scan: async () => {
2357
- if (allowUsbFallback) {
2358
- const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
2359
- const candidate = usbDevices.find(
2360
- (device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
2361
- ) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
2362
- if (candidate) return [candidate];
2363
- }
2364
- return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
2365
- },
2366
- request: {
2367
- devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
2368
- bindingSessionId,
2369
- rejectedConnectId: context?.rejectedConnectId,
2370
- context: {
2371
- kind: "bind-connection",
2372
- transport: "ble",
2373
- reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
2374
- },
2375
- extra: context?.extra
2376
- }
2377
- });
2378
- if (device.connectionType === "usb") {
2379
- this._activeConnectionType = "usb";
2380
- if (context) context.selectedConnection = void 0;
2381
- if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
2382
- this._bindingSelectionRequestId = void 0;
2383
- this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
2384
- type: UI_REQUEST.DEVICE_BINDING_STATUS,
2385
- payload: { selectionRequestId: requestId, status: "cancelled" }
2386
- });
2387
- return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
2388
- }
2389
- if (context) context.selectedConnection = { connectId: device.connectId, requestId };
2390
- this._bindingSelectionRequestId = requestId;
2391
- return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
2392
- }
2393
- if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
2394
- if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
2395
- throw createHwkError({
2396
- code: HardwareErrorCode3.DeviceNotFound,
2397
- message: "Select a Ledger device before continuing"
2398
- });
2399
- }
2400
- const requestId = this._uiRegistry.createRequestId();
2401
- const waitPromise = this._uiRegistry.wait(
2402
- UI_REQUEST.REQUEST_SELECT_DEVICE,
2403
- { requestId }
2404
- );
2405
- this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
2406
- type: UI_REQUEST.REQUEST_SELECT_DEVICE,
2407
- payload: {
2408
- devices,
2409
- requestId,
2410
- context: requiresBleSelection ? {
2411
- kind: "bind-connection",
2412
- transport: "ble",
2413
- reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
2414
- } : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
2415
- extra: context?.extra
2416
- }
2417
- });
2418
- const { sdkConnectId } = await (signal ? this._abortable(signal, waitPromise) : waitPromise);
2419
- if (signal) _LedgerAdapter._throwIfAborted(signal);
2420
- const selected = devices.find((device) => device.connectId === sdkConnectId);
2421
- if (!selected) {
2422
- throw Object.assign(new Error("Selected Ledger is no longer available"), {
2423
- code: HardwareErrorCode3.DeviceNotFound
2424
- });
2425
- }
2426
- if (context && requiresBleSelection)
2427
- context.selectedConnection = { connectId: selected.connectId, requestId };
2428
- return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
1717
+ if (devices.length > 1) {
1718
+ throw createMultipleUsbLedgerDevicesError();
2429
1719
  }
2430
1720
  if (devices.length !== 1) {
2431
1721
  throw Object.assign(new Error("Ledger device not found."), {
2432
1722
  code: HardwareErrorCode3.DeviceNotFound
2433
1723
  });
2434
1724
  }
2435
- return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
1725
+ return this._connectDeviceOrThrow(devices[0].connectId);
2436
1726
  }
2437
- async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
2438
- const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
1727
+ async _connectDeviceOrThrow(chosenConnectId) {
1728
+ const result = await this.connectDevice(chosenConnectId);
2439
1729
  if (!result.success) {
2440
1730
  const payload = result.payload;
2441
1731
  const rethrow = Object.assign(new Error(payload.error), {
@@ -2446,7 +1736,7 @@ var _LedgerAdapter = class _LedgerAdapter {
2446
1736
  }
2447
1737
  throw rethrow;
2448
1738
  }
2449
- return result.payload;
1739
+ return chosenConnectId;
2450
1740
  }
2451
1741
  /**
2452
1742
  * Call the connector with automatic session resolution and disconnect retry.
@@ -2476,122 +1766,26 @@ var _LedgerAdapter = class _LedgerAdapter {
2476
1766
  * lives in one place.
2477
1767
  */
2478
1768
  async _callConnector(sessionId, method, params, signal) {
2479
- this._assertConnectorReady(method);
2480
- if (signal?.aborted) throw this._abortReason(signal);
2481
- const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
2482
- let promise;
2483
- try {
2484
- promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
2485
- } catch (error) {
2486
- releaseOperation();
2487
- throw error;
2488
- }
1769
+ const promise = this.connector.call(sessionId, method, params);
2489
1770
  const result = signal ? await this._abortable(signal, promise) : await promise;
2490
1771
  return this._unwrapConnectorResult(result);
2491
1772
  }
2492
- _assertConnectorReady(method) {
2493
- if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
2494
- throw _LedgerAdapter._createDeviceBusyError(method);
2495
- }
2496
- }
2497
- _runConnectorTeardown(task) {
2498
- const previous = this._connectorTeardownTail;
2499
- let releaseTail = () => void 0;
2500
- this._connectorTeardownTail = new Promise((resolve) => {
2501
- releaseTail = resolve;
2502
- });
2503
- this._pendingConnectorTeardowns += 1;
2504
- return (async () => {
2505
- try {
2506
- await previous;
2507
- await this._waitForConnectorOperationsToDrain();
2508
- await task();
2509
- } finally {
2510
- this._pendingConnectorTeardowns -= 1;
2511
- releaseTail();
2512
- }
2513
- })();
2514
- }
2515
- _waitForConnectorOperationsToDrain() {
2516
- if (this._unsettledConnectorOperations.size === 0) {
2517
- return Promise.resolve();
2518
- }
2519
- return new Promise((resolve) => {
2520
- this._connectorIdleWaiters.add(resolve);
2521
- });
2522
- }
2523
- _retainConnectorOperation(key) {
2524
- this._unsettledConnectorOperations.set(
2525
- key,
2526
- (this._unsettledConnectorOperations.get(key) ?? 0) + 1
2527
- );
2528
- let released = false;
2529
- return () => {
2530
- if (released) return;
2531
- released = true;
2532
- const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
2533
- if (remaining > 0) {
2534
- this._unsettledConnectorOperations.set(key, remaining);
2535
- } else {
2536
- this._unsettledConnectorOperations.delete(key);
2537
- }
2538
- if (this._unsettledConnectorOperations.size === 0) {
2539
- for (const resolve of this._connectorIdleWaiters) resolve();
2540
- this._connectorIdleWaiters.clear();
2541
- }
2542
- };
2543
- }
2544
1773
  async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
2545
- const positionalOperationId = isHardwareOperationId(connectId) ? connectId : void 0;
2546
- if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
2547
- throw createHwkError({
2548
- code: HardwareErrorCode3.InvalidParams,
2549
- message: "Conflicting Ledger operation ids",
2550
- params: {
2551
- positionalOperationId,
2552
- commonOperationId: commonParams.operationId
2553
- }
2554
- });
2555
- }
2556
- const operationId = commonParams?.operationId ?? positionalOperationId;
2557
- const operation = operationId ? this._operations.resolve(operationId) : void 0;
2558
- const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
2559
- const effectiveConnectId = operation?.connectId ?? connectId;
2560
- debugLog("[LedgerAdapter][REQ]", {
2561
- method,
2562
- connectId: effectiveConnectId || "(empty)",
2563
- params
2564
- });
2565
- const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
1774
+ debugLog("[LedgerAdapter][REQ]", { method, connectId: connectId || "(empty)", params });
1775
+ const queueKey = connectId || "__ledger_default__";
2566
1776
  try {
2567
1777
  const result = await this._jobQueue.enqueue(
2568
1778
  queueKey,
2569
- async (signal) => {
2570
- if (operationId) this._activeOperationJobs.add(operationId);
2571
- try {
2572
- return await this._runConnectorCall(
2573
- effectiveConnectId,
2574
- method,
2575
- params,
2576
- signal,
2577
- fingerprint,
2578
- permissionDeviceId,
2579
- commonParams,
2580
- installContext ?? {},
2581
- operationId
2582
- );
2583
- } catch (error) {
2584
- this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
2585
- throw error;
2586
- } finally {
2587
- if (operationId) {
2588
- this._activeOperationJobs.delete(operationId);
2589
- if (this._pendingOperationDisconnects.delete(operationId)) {
2590
- await this._releaseLostOperationConnection(operationId);
2591
- }
2592
- }
2593
- }
2594
- },
1779
+ async (signal) => this._runConnectorCall(
1780
+ connectId,
1781
+ method,
1782
+ params,
1783
+ signal,
1784
+ fingerprint,
1785
+ permissionDeviceId,
1786
+ commonParams,
1787
+ installContext
1788
+ ),
2595
1789
  {
2596
1790
  label: method,
2597
1791
  rejectIfBusy: true,
@@ -2612,22 +1806,15 @@ var _LedgerAdapter = class _LedgerAdapter {
2612
1806
  }
2613
1807
  });
2614
1808
  throw err;
2615
- } finally {
2616
- releaseOperationRetention?.();
2617
1809
  }
2618
1810
  }
2619
1811
  /**
2620
1812
  * Race a promise against an abort signal. On abort, rejects with
2621
1813
  * signal.reason → instance _lastCancelReason → generic Error('Aborted').
2622
1814
  */
2623
- /** Hermes/RN polyfills don't always populate signal.reason; fall back. */
2624
- _abortReason(signal) {
2625
- return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
2626
- }
2627
1815
  _abortable(signal, promise) {
2628
- const getAbortReason = () => this._abortReason(signal);
1816
+ const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
2629
1817
  if (signal.aborted) {
2630
- void promise.catch(() => void 0);
2631
1818
  return Promise.reject(getAbortReason());
2632
1819
  }
2633
1820
  return new Promise((resolve, reject) => {
@@ -2654,15 +1841,13 @@ var _LedgerAdapter = class _LedgerAdapter {
2654
1841
  }
2655
1842
  }
2656
1843
  /** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
2657
- async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
1844
+ async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
2658
1845
  _LedgerAdapter._throwIfAborted(signal);
2659
- if (!operationId) {
2660
- await this._ensureDevicePermission(
2661
- connectId,
2662
- permissionDeviceId ?? fingerprint?.deviceId,
2663
- signal
2664
- );
2665
- }
1846
+ await this._ensureDevicePermission(
1847
+ connectId,
1848
+ permissionDeviceId ?? fingerprint?.deviceId,
1849
+ signal
1850
+ );
2666
1851
  _LedgerAdapter._throwIfAborted(signal);
2667
1852
  let effectiveParams = params;
2668
1853
  if (method === "btcGetPublicKey") {
@@ -2676,132 +1861,36 @@ var _LedgerAdapter = class _LedgerAdapter {
2676
1861
  effectiveParams = gatedParams;
2677
1862
  }
2678
1863
  const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
2679
- let businessCallStarted = false;
2680
- const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
2681
- const knownTransport = this._isBleConnection() ? "ble" : "usb";
2682
- const hintedConnectId = commonParams?.knownConnections?.find(
2683
- (connection) => connection.transport === knownTransport
2684
- );
2685
- const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
2686
- const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
2687
- const connectionAttempt = { ...commonParams };
2688
- const bundleConnection = installContext?.connection;
2689
- if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
2690
- throw createHwkError({
2691
- code: HardwareErrorCode3.DeviceDisconnected,
2692
- message: "Ledger all-network connection ended"
2693
- });
2694
- }
2695
- let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
2696
- preferredConnectId,
1864
+ const resolvedConnectId = await this.ensureConnected(
1865
+ connectId,
2697
1866
  signal,
2698
- allowUsbEphemeralFallback,
2699
- void 0,
2700
- connectionAttempt
1867
+ allowUsbEphemeralFallback
2701
1868
  );
2702
- let sessionId = this._sessions.get(resolvedConnectId);
2703
- if (sessionId && installContext && !installContext.connection) {
2704
- installContext.connection = { connectId: resolvedConnectId, sessionId };
2705
- }
1869
+ const sessionId = this._sessions.get(resolvedConnectId);
2706
1870
  if (!sessionId) {
2707
- if (operationId) {
2708
- this._operations.end(operationId, "disconnect");
2709
- throw createHwkError({
2710
- code: HardwareErrorCode3.OperationEnded,
2711
- message: "Ledger operation connection is no longer active",
2712
- params: { operationId, reason: "disconnect" }
2713
- });
2714
- }
2715
1871
  throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
2716
1872
  _tag: ERROR_TAG.DeviceSessionNotFound
2717
1873
  });
2718
1874
  }
2719
1875
  try {
2720
1876
  if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2721
- for (; ; ) {
2722
- const fp = await this._abortable(
2723
- signal,
2724
- this._verifyDeviceFingerprintWithSession(
2725
- sessionId,
2726
- fingerprint.deviceId,
2727
- fingerprint.chain
2728
- )
2729
- );
2730
- if (fp.success) break;
2731
- const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
2732
- if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
2733
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2734
- code: HardwareErrorCode3.DeviceMismatch
2735
- });
2736
- }
2737
- binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
2738
- binding.rejectedConnectIds.add(resolvedConnectId);
2739
- binding.rejectedConnectId = resolvedConnectId;
2740
- this._sessions.delete(resolvedConnectId);
2741
- await this.connector.disconnect(sessionId);
2742
- if (operationId) this._pendingOperationDisconnects.delete(operationId);
2743
- _LedgerAdapter._throwIfAborted(signal);
2744
- resolvedConnectId = await this._connectFirstOrSelect(
2745
- [],
2746
- void 0,
2747
- true,
2748
- operationId,
2749
- binding,
2750
- signal
2751
- );
2752
- const selectedSession = this._sessions.get(resolvedConnectId);
2753
- const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
2754
- if (!selectedSession || !selectedDevice)
2755
- throw createHwkError({
2756
- code: HardwareErrorCode3.DeviceDisconnected,
2757
- message: "Selected Ledger connection ended"
2758
- });
2759
- sessionId = selectedSession;
2760
- if (operationId)
2761
- this._operations.rebind(operationId, {
2762
- connectId: resolvedConnectId,
2763
- device: selectedDevice,
2764
- connectionKeys: [sessionId]
2765
- });
2766
- if (installContext)
2767
- installContext.connection = { connectId: resolvedConnectId, sessionId };
2768
- }
2769
- await this._publishVerifiedBleBinding(
2770
- resolvedConnectId,
2771
- fingerprint.chain,
2772
- fingerprint.deviceId,
2773
- connectionAttempt,
2774
- operationId,
2775
- signal
1877
+ const fp = await this._abortable(
1878
+ signal,
1879
+ this._verifyDeviceFingerprintWithSession(
1880
+ sessionId,
1881
+ fingerprint.deviceId,
1882
+ fingerprint.chain
1883
+ )
2776
1884
  );
2777
- if (!operationId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
2778
- this._verifiedBleReconnectTargets.set(connectId, {
2779
- connectId: resolvedConnectId,
2780
- chain: fingerprint.chain,
2781
- fingerprint: fingerprint.deviceId
1885
+ if (!fp.success) {
1886
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1887
+ code: HardwareErrorCode3.DeviceMismatch
2782
1888
  });
2783
1889
  }
2784
1890
  }
2785
- businessCallStarted = true;
2786
1891
  return await this._callConnector(sessionId, method, effectiveParams, signal);
2787
1892
  } catch (err) {
2788
1893
  if (signal.aborted) throw err;
2789
- if (isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err)) {
2790
- this._discoveredDevices.delete(resolvedConnectId);
2791
- if (operationId) await this._releaseLostOperationConnection(operationId);
2792
- else {
2793
- this._sessions.delete(resolvedConnectId);
2794
- await this.connector.disconnect(sessionId).catch(() => void 0);
2795
- }
2796
- const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
2797
- const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
2798
- throw createHwkError({
2799
- code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(err).code,
2800
- message: "Ledger operation connection was lost; start a new operation",
2801
- params: ambiguous ? operationMayHaveCompletedParams(method, { operationId }) : interactionParams,
2802
- recovery: ambiguous ? { scope: "unknown" } : void 0
2803
- });
2804
- }
2805
1894
  const errObj = err;
2806
1895
  debugLog("[LedgerAdapter] connectorCall error:", method, {
2807
1896
  message: errObj?.message,
@@ -2813,65 +1902,6 @@ var _LedgerAdapter = class _LedgerAdapter {
2813
1902
  isNotAdvertising: isDeviceNotAdvertisingError(err),
2814
1903
  isStuckApp: isStuckAppStateError(err)
2815
1904
  });
2816
- if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
2817
- await this._waitForDeviceConnect(signal);
2818
- if (this._sessions.get(resolvedConnectId) !== sessionId) {
2819
- throw createHwkError({
2820
- code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
2821
- message: "Ledger connection ended while waiting for unlock"
2822
- });
2823
- }
2824
- return this._runConnectorCall(
2825
- resolvedConnectId,
2826
- method,
2827
- effectiveParams,
2828
- signal,
2829
- fingerprint,
2830
- permissionDeviceId,
2831
- commonParams,
2832
- installContext,
2833
- operationId,
2834
- lockedRetryBudget - 1
2835
- );
2836
- }
2837
- if (businessCallStarted && isStuckAppStateError(err)) {
2838
- await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2839
- if (this._sessions.get(resolvedConnectId) !== sessionId) {
2840
- throw createHwkError({
2841
- code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
2842
- message: "Ledger connection ended during the app transition"
2843
- });
2844
- }
2845
- try {
2846
- return await this._callConnector(sessionId, method, effectiveParams, signal);
2847
- } catch (retryErr) {
2848
- if (isStuckAppStateError(retryErr)) throw err;
2849
- if (isDeviceDisconnectedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isTimeoutError(retryErr) || isConnectionLevelError(retryErr)) {
2850
- if (operationId) await this._releaseLostOperationConnection(operationId);
2851
- else {
2852
- this._sessions.delete(resolvedConnectId);
2853
- this._discoveredDevices.delete(resolvedConnectId);
2854
- await this.connector.disconnect(sessionId).catch(() => void 0);
2855
- }
2856
- throw createHwkError({
2857
- code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(retryErr).code,
2858
- message: `Ledger ${method} may have completed before the connection was lost`,
2859
- params: !canReplayHardwareMethodAfterTransportFailure(method) ? operationMayHaveCompletedParams(method, {
2860
- operationId,
2861
- reason: "disconnect"
2862
- }) : { operationId, reason: "disconnect" },
2863
- recovery: !canReplayHardwareMethodAfterTransportFailure(method) ? { scope: "unknown" } : void 0
2864
- });
2865
- }
2866
- throw retryErr;
2867
- }
2868
- }
2869
- if (!operationId && err?.code === HardwareErrorCode3.DeviceMismatch) {
2870
- this._sessions.delete(resolvedConnectId);
2871
- this._discoveredDevices.delete(resolvedConnectId);
2872
- await this.connector.disconnect(sessionId).catch(() => void 0);
2873
- throw err;
2874
- }
2875
1905
  const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
2876
1906
  const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
2877
1907
  if (autoInstallApp && isAppMissing) {
@@ -2927,14 +1957,157 @@ var _LedgerAdapter = class _LedgerAdapter {
2927
1957
  fingerprint,
2928
1958
  permissionDeviceId,
2929
1959
  commonParams,
2930
- installContext,
2931
- operationId
1960
+ installContext
1961
+ );
1962
+ }
1963
+ }
1964
+ if (isStuckAppStateError(err)) {
1965
+ try {
1966
+ this._sessions.delete(resolvedConnectId);
1967
+ this._discoveredDevices.delete(resolvedConnectId);
1968
+ this.connector.reset?.();
1969
+ } catch {
1970
+ }
1971
+ debugLog(
1972
+ "[LedgerAdapter] stuck-app retry: method=",
1973
+ method,
1974
+ "delayMs=",
1975
+ _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
1976
+ "_tag=",
1977
+ err?._tag
1978
+ );
1979
+ try {
1980
+ const retryResult = await this._retryAfterStuckApp(
1981
+ resolvedConnectId,
1982
+ method,
1983
+ effectiveParams,
1984
+ signal,
1985
+ err,
1986
+ fingerprint
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
2932
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
+ }
2933
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
+ });
2934
2073
  }
2935
2074
  throw err;
2936
2075
  }
2937
2076
  }
2077
+ /**
2078
+ * Stuck-app recovery: pause for the device's UI transition, then retry once.
2079
+ *
2080
+ * Caller has already cleared the session + reset connector. We wait so Stax
2081
+ * finishes its post-CloseApp animation, then go through ensureConnected +
2082
+ * fingerprint check + call exactly once. Caller decides what to do on a
2083
+ * second stuck-app hit.
2084
+ */
2085
+ async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
2086
+ await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
2087
+ const retryConnectId = await this.ensureConnected(
2088
+ resolvedConnectId,
2089
+ signal,
2090
+ !!fingerprint?.deviceId && !fingerprint.skipFingerprint
2091
+ );
2092
+ const retrySessionId = this._sessions.get(retryConnectId);
2093
+ if (!retrySessionId) throw originalErr;
2094
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
2095
+ const fp = await this._abortable(
2096
+ signal,
2097
+ this._verifyDeviceFingerprintWithSession(
2098
+ retrySessionId,
2099
+ fingerprint.deviceId,
2100
+ fingerprint.chain
2101
+ )
2102
+ );
2103
+ if (!fp.success) {
2104
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
2105
+ code: HardwareErrorCode3.DeviceMismatch
2106
+ });
2107
+ }
2108
+ }
2109
+ return this._callConnector(retrySessionId, method, params, signal);
2110
+ }
2938
2111
  _sleepAbortable(ms, signal) {
2939
2112
  return new Promise((resolve, reject) => {
2940
2113
  if (signal.aborted) {
@@ -2952,6 +2125,88 @@ var _LedgerAdapter = class _LedgerAdapter {
2952
2125
  signal.addEventListener("abort", onAbort, { once: true });
2953
2126
  });
2954
2127
  }
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
+ }
2955
2210
  /**
2956
2211
  * Ensure OS-level device permission (Bluetooth / USB) before proceeding.
2957
2212
  *
@@ -3021,15 +2276,7 @@ var _LedgerAdapter = class _LedgerAdapter {
3021
2276
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
3022
2277
  const e = err;
3023
2278
  const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
3024
- return ledgerFailure(
3025
- e.code,
3026
- e.message ?? "Unknown error",
3027
- e.appName,
3028
- tag,
3029
- params,
3030
- void 0,
3031
- isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
3032
- );
2279
+ return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
3033
2280
  }
3034
2281
  const mapped = mapLedgerError(err);
3035
2282
  return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
@@ -3056,7 +2303,7 @@ var _LedgerAdapter = class _LedgerAdapter {
3056
2303
  deviceId: device.deviceId,
3057
2304
  connectId: device.connectId,
3058
2305
  label: device.name,
3059
- connectionType: device.connectionType ?? this.connector.connectionType,
2306
+ connectionType: this.connector.connectionType,
3060
2307
  rssi: device.rssi,
3061
2308
  isConnectable: device.isConnectable,
3062
2309
  serialNumber: device.serialNumber,
@@ -3082,8 +2329,8 @@ var LedgerAdapter = _LedgerAdapter;
3082
2329
 
3083
2330
  // src/connector/LedgerConnectorBase.ts
3084
2331
  import {
3085
- EConnectorInteraction as EConnectorInteraction3,
3086
- HardwareErrorCode as HardwareErrorCode7,
2332
+ EConnectorInteraction as EConnectorInteraction4,
2333
+ HardwareErrorCode as HardwareErrorCode8,
3087
2334
  isKnownNonTargetHardwareVendor,
3088
2335
  serializeConnectorError
3089
2336
  } from "@onekeyfe/hwk-adapter-core";
@@ -3356,7 +2603,7 @@ var LedgerDeviceManager = class {
3356
2603
 
3357
2604
  // src/signer/SignerManager.ts
3358
2605
  import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
3359
- import { ContextModuleBuilder, ContextModuleChainID } from "@ledgerhq/context-module";
2606
+ import { ContextModuleBuilder } from "@ledgerhq/context-module";
3360
2607
 
3361
2608
  // src/signer/SignerEth.ts
3362
2609
  import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
@@ -3622,7 +2869,7 @@ var SignerManager = class _SignerManager {
3622
2869
  return (args) => new SignerEthBuilder(args);
3623
2870
  }
3624
2871
  static _createContextModule() {
3625
- const contextModule = new ContextModuleBuilder({}).setChain(ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
2872
+ const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
3626
2873
  return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
3627
2874
  }
3628
2875
  static wrapBlindSigningReportNonBlocking(contextModule) {
@@ -4092,8 +3339,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
4092
3339
  }
4093
3340
 
4094
3341
  // src/connector/chains/sol.ts
4095
- import { bytesToHex, hexToBytes as hexToBytes3, prepareSolanaOffchainMessageV1 } from "@onekeyfe/hwk-adapter-core";
4096
- import bs58 from "bs58";
3342
+ import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
4097
3343
 
4098
3344
  // src/signer/SignerSol.ts
4099
3345
  var SignerSol = class {
@@ -4177,21 +3423,8 @@ async function solSignMessage(ctx, sessionId, params) {
4177
3423
  const path = normalizePath(params.path);
4178
3424
  const messageBytes = hexToBytes3(params.message);
4179
3425
  try {
4180
- if (params.messageVersion === 1) {
4181
- const preparedMessage = prepareSolanaOffchainMessageV1({
4182
- message: messageBytes,
4183
- requiredSigners: params.requiredSigners
4184
- });
4185
- const { SignMessageVersion } = await ctx.importLedgerKit(
4186
- "@ledgerhq/device-signer-kit-solana"
4187
- );
4188
- const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
4189
- version: SignMessageVersion.Raw
4190
- });
4191
- return { signature: decodeBase58Signature(result2.signature) };
4192
- }
4193
3426
  const result = await solSigner.signMessage(path, messageBytes);
4194
- return { signature: decodeBase58EnvelopeSignature(result.signature) };
3427
+ return { signature: result.signature };
4195
3428
  } catch (err) {
4196
3429
  ctx.invalidateSession(sessionId);
4197
3430
  throw ctx.wrapError(err);
@@ -4199,27 +3432,11 @@ async function solSignMessage(ctx, sessionId, params) {
4199
3432
  ctx.clearCanceller(sessionId);
4200
3433
  }
4201
3434
  }
4202
- function decodeBase58Signature(signature) {
4203
- const bytes = bs58.decode(signature);
4204
- if (bytes.length !== 64) {
4205
- throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
4206
- }
4207
- return bytesToHex(bytes);
4208
- }
4209
- function decodeBase58EnvelopeSignature(envelope) {
4210
- const bytes = bs58.decode(envelope);
4211
- if (bytes.length < 65 || bytes[0] !== 1) {
4212
- throw new Error("Ledger Solana signature envelope is invalid");
4213
- }
4214
- return bytesToHex(bytes.subarray(1, 65));
4215
- }
4216
3435
  async function _createSolSigner(ctx, sessionId) {
4217
3436
  const dmk = await ctx.getOrCreateDmk();
4218
- const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
4219
- "@ledgerhq/context-module"
4220
- );
3437
+ const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit("@ledgerhq/context-module");
4221
3438
  const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
4222
- const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
3439
+ const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
4223
3440
  const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
4224
3441
  const signer = new SignerSol(sdkSigner);
4225
3442
  signer.onInteraction = (interaction) => {
@@ -4235,192 +3452,361 @@ async function _createSolSigner(ctx, sessionId) {
4235
3452
  return signer;
4236
3453
  }
4237
3454
 
4238
- // src/connector/chains/zcash.ts
4239
- import { bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
3455
+ // src/connector/chains/tron.ts
3456
+ import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
3457
+ import Trx from "@ledgerhq/hw-app-trx";
4240
3458
 
4241
- // src/signer/SignerZcash.ts
4242
- var SignerZcash = class {
4243
- // eslint-disable-next-line no-useless-constructor, no-empty-function
4244
- constructor(_sdk) {
4245
- this._sdk = _sdk;
4246
- }
4247
- /** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
4248
- async getFullViewingKey(derivationPath, options) {
4249
- const action = this._sdk.getFullViewingKey(derivationPath, options);
4250
- return deviceActionToPromise(
4251
- action,
4252
- this.onInteraction,
4253
- void 0,
4254
- this.onRegisterCanceller
4255
- );
4256
- }
4257
- /** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
4258
- async getShieldedAddress(derivationPath, options) {
4259
- const action = this._sdk.getShieldedAddress(derivationPath, options);
4260
- return deviceActionToPromise(
4261
- action,
4262
- this.onInteraction,
4263
- void 0,
4264
- this.onRegisterCanceller
4265
- );
4266
- }
4267
- };
3459
+ // src/connector/chains/legacyChainCall.ts
3460
+ import { EConnectorInteraction as EConnectorInteraction3 } from "@onekeyfe/hwk-adapter-core";
4268
3461
 
4269
- // src/connector/chains/zcash.ts
4270
- async function zcashGetFullViewingKey(ctx, sessionId, params) {
4271
- const signer = await _createZcashSigner(ctx, sessionId);
4272
- const path = normalizePath(params.path);
4273
- const mode = params.mode ?? "ufvk";
4274
- try {
4275
- const result = await signer.getFullViewingKey(path, { mode });
4276
- if (result.mode === "ufvk") {
4277
- return { path: params.path, mode, ufvk: result.fullViewingKey };
3462
+ // src/app/AppManager.ts
3463
+ import {
3464
+ CloseAppCommand,
3465
+ GetAppAndVersionCommand,
3466
+ OpenAppCommand,
3467
+ isSuccessCommandResult
3468
+ } from "@ledgerhq/device-management-kit";
3469
+ import { HardwareErrorCode as HardwareErrorCode6 } from "@onekeyfe/hwk-adapter-core";
3470
+ var APP_NAME_MAP = {
3471
+ ETH: "Ethereum",
3472
+ BTC: "Bitcoin",
3473
+ SOL: "Solana",
3474
+ TRX: "Tron",
3475
+ XRP: "XRP",
3476
+ ADA: "Cardano",
3477
+ DOT: "Polkadot",
3478
+ ATOM: "Cosmos"
3479
+ };
3480
+ var DASHBOARD_APP_NAME = "BOLOS";
3481
+ var AppManager = class {
3482
+ constructor(dmk, options) {
3483
+ this._dmk = dmk;
3484
+ this._waitMs = options?.waitMs ?? 1e3;
3485
+ this._maxRetries = options?.maxRetries ?? 10;
3486
+ }
3487
+ /**
3488
+ * Return the Ledger app name for a given chain ticker,
3489
+ * or undefined if the chain is not supported.
3490
+ */
3491
+ static getAppName(chain) {
3492
+ return APP_NAME_MAP[chain];
3493
+ }
3494
+ /**
3495
+ * Ensure the target app is open on the device identified by `sessionId`.
3496
+ *
3497
+ * Flow:
3498
+ * 1. Check the currently running app.
3499
+ * 2. If it is already the target, return immediately.
3500
+ * 3. If a different app is running (not dashboard), close it first.
3501
+ * 4. Open the target app.
3502
+ * 5. Poll until the device confirms the target app is running.
3503
+ */
3504
+ /**
3505
+ * @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
3506
+ * device is about to display "Open <app>" on screen and wait for the
3507
+ * user's button press. UI consumers should show their "open app" prompt
3508
+ * in response. NOT called when the target app is already open (no user
3509
+ * interaction needed in that case).
3510
+ *
3511
+ * Important: OpenAppCommand is blocking. It does not resolve until the user
3512
+ * has physically confirmed on the device, so anything that runs AFTER
3513
+ * `await this._openApp(...)` lands AFTER the prompt is already gone.
3514
+ * Hence the callback must fire BEFORE that await.
3515
+ */
3516
+ async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
3517
+ const currentApp = await this._getCurrentApp(sessionId);
3518
+ if (currentApp === targetAppName) {
3519
+ return;
4278
3520
  }
4279
- return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
4280
- } catch (err) {
4281
- ctx.invalidateSession(sessionId);
4282
- throw ctx.wrapError(err);
4283
- } finally {
4284
- ctx.clearCanceller(sessionId);
3521
+ if (!this._isDashboard(currentApp)) {
3522
+ await this._closeCurrentApp(sessionId);
3523
+ await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
3524
+ }
3525
+ onConfirmOnDevice?.();
3526
+ await this._openApp(sessionId, targetAppName);
3527
+ await this._waitForApp(sessionId, targetAppName);
4285
3528
  }
4286
- }
4287
- async function zcashGetShieldedAddress(ctx, sessionId, params) {
4288
- const signer = await _createZcashSigner(ctx, sessionId);
4289
- const path = normalizePath(params.path);
4290
- try {
4291
- const result = await signer.getShieldedAddress(path, {
4292
- checkOnDevice: params.showOnDevice ?? false
3529
+ // ---------------------------------------------------------------------------
3530
+ // Private helpers
3531
+ // ---------------------------------------------------------------------------
3532
+ async _getCurrentApp(sessionId) {
3533
+ const result = await this._dmk.sendCommand({
3534
+ sessionId,
3535
+ command: new GetAppAndVersionCommand()
4293
3536
  });
4294
- return { address: result.address, path: params.path };
4295
- } catch (err) {
4296
- ctx.invalidateSession(sessionId);
4297
- throw ctx.wrapError(err);
4298
- } finally {
4299
- ctx.clearCanceller(sessionId);
3537
+ if (isSuccessCommandResult(result)) {
3538
+ debugLog("[AppManager] currentApp:", result.data.name);
3539
+ return result.data.name;
3540
+ }
3541
+ const errResult = result;
3542
+ const dmkErr = errResult.error ?? {};
3543
+ const original = dmkErr.originalError;
3544
+ debugLog(
3545
+ "[AppManager] _getCurrentApp failed sessionId=",
3546
+ sessionId,
3547
+ "tag=",
3548
+ dmkErr._tag,
3549
+ "errorCode=",
3550
+ dmkErr.errorCode,
3551
+ "message=",
3552
+ dmkErr.message,
3553
+ "originalErrorMessage=",
3554
+ original?.message ?? String(original ?? "")
3555
+ );
3556
+ throw Object.assign(
3557
+ new Error(
3558
+ dmkErr.message ?? "Failed to get current app from device"
3559
+ ),
3560
+ {
3561
+ _tag: dmkErr._tag,
3562
+ errorCode: dmkErr.errorCode,
3563
+ originalError: original
3564
+ }
3565
+ );
3566
+ }
3567
+ async _openApp(sessionId, appName) {
3568
+ const result = await this._dmk.sendCommand({
3569
+ sessionId,
3570
+ command: new OpenAppCommand({ appName })
3571
+ });
3572
+ if (!isSuccessCommandResult(result)) {
3573
+ const dmkErr = result.error;
3574
+ const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
3575
+ const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
3576
+ debugLog(
3577
+ "[AppManager] openApp failed:",
3578
+ appName,
3579
+ "errorCode:",
3580
+ errorCode,
3581
+ "tag:",
3582
+ dmkErr._tag
3583
+ );
3584
+ let code;
3585
+ if (errorCode === "6807" || /unknown application/i.test(message)) {
3586
+ code = HardwareErrorCode6.AppNotInstalled;
3587
+ } else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
3588
+ code = HardwareErrorCode6.UserRejected;
3589
+ }
3590
+ throw Object.assign(new Error(`Failed to open "${appName}"`), {
3591
+ _tag: ERROR_TAG.OpenAppCommand,
3592
+ code,
3593
+ errorCode,
3594
+ statusCode: errorCode,
3595
+ appName,
3596
+ originalError: dmkErr
3597
+ });
3598
+ }
3599
+ }
3600
+ async _closeCurrentApp(sessionId) {
3601
+ debugLog("[AppManager] closeCurrentApp");
3602
+ await this._dmk.sendCommand({
3603
+ sessionId,
3604
+ command: new CloseAppCommand()
3605
+ });
3606
+ }
3607
+ /**
3608
+ * Poll the device until the expected app is reported as running,
3609
+ * or throw after `_maxRetries` attempts.
3610
+ */
3611
+ async _waitForApp(sessionId, expectedAppName) {
3612
+ let lastSeen = "";
3613
+ for (let i = 0; i < this._maxRetries; i++) {
3614
+ await this._wait();
3615
+ const current = await this._getCurrentApp(sessionId);
3616
+ lastSeen = current;
3617
+ if (current === expectedAppName) {
3618
+ return;
3619
+ }
3620
+ }
3621
+ debugLog(
3622
+ "[AppManager] waitForApp exhausted: expected=",
3623
+ expectedAppName,
3624
+ "lastSeen=",
3625
+ lastSeen
3626
+ );
3627
+ throw new Error(
3628
+ `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
3629
+ );
3630
+ }
3631
+ _isDashboard(appName) {
3632
+ return appName === DASHBOARD_APP_NAME;
3633
+ }
3634
+ _wait() {
3635
+ return new Promise((resolve) => setTimeout(resolve, this._waitMs));
4300
3636
  }
3637
+ };
3638
+
3639
+ // src/connector/chains/legacyChainCall.ts
3640
+ function isLegacyWrongAppError(err, _appName) {
3641
+ return isWrongAppError(err);
4301
3642
  }
4302
- async function _createZcashSigner(ctx, sessionId) {
4303
- const dmk = await ctx.getOrCreateDmk();
4304
- const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
4305
- const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
4306
- const signer = new SignerZcash(sdkSigner);
4307
- signer.onInteraction = (interaction) => {
4308
- debugLog("[LedgerConnector] zcash.onInteraction:", interaction);
3643
+ async function withLegacyChainCall(ctx, sessionId, options, action) {
3644
+ const { appName, needsConfirmation } = options;
3645
+ let openAppPromptShown = false;
3646
+ const onAppOpenPrompt = () => {
3647
+ openAppPromptShown = true;
4309
3648
  ctx.emit("ui-event", {
4310
- type: collapseSignerInteraction(interaction),
3649
+ type: EConnectorInteraction3.ConfirmOpenApp,
4311
3650
  payload: { sessionId }
4312
3651
  });
4313
3652
  };
4314
- signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
4315
- return signer;
3653
+ const closeOpenAppUiIfShown = () => {
3654
+ if (openAppPromptShown) {
3655
+ ctx.emit("ui-event", {
3656
+ type: EConnectorInteraction3.InteractionComplete,
3657
+ payload: { sessionId }
3658
+ });
3659
+ openAppPromptShown = false;
3660
+ }
3661
+ };
3662
+ try {
3663
+ await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
3664
+ } catch (err) {
3665
+ debugLog(
3666
+ "[LegacyChainCall] pre-flight ensureAppOpen failed:",
3667
+ appName,
3668
+ err?.message
3669
+ );
3670
+ closeOpenAppUiIfShown();
3671
+ throw ctx.wrapError(err, { defaultAppName: appName });
3672
+ }
3673
+ const runOnce = async () => {
3674
+ let confirmEmitted = false;
3675
+ if (needsConfirmation) {
3676
+ ctx.emit("ui-event", {
3677
+ type: EConnectorInteraction3.ConfirmOnDevice,
3678
+ payload: { sessionId }
3679
+ });
3680
+ confirmEmitted = true;
3681
+ }
3682
+ try {
3683
+ return await action(sessionId);
3684
+ } finally {
3685
+ if (confirmEmitted || openAppPromptShown) {
3686
+ ctx.emit("ui-event", {
3687
+ type: EConnectorInteraction3.InteractionComplete,
3688
+ payload: { sessionId }
3689
+ });
3690
+ openAppPromptShown = false;
3691
+ }
3692
+ }
3693
+ };
3694
+ try {
3695
+ return await runOnce();
3696
+ } catch (err) {
3697
+ if (!isLegacyWrongAppError(err, appName)) {
3698
+ debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
3699
+ ctx.invalidateSession(sessionId);
3700
+ throw ctx.wrapError(err, { defaultAppName: appName });
3701
+ }
3702
+ debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
3703
+ try {
3704
+ await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
3705
+ } catch (switchErr) {
3706
+ debugLog(
3707
+ "[LegacyChainCall] retry ensureAppOpen failed:",
3708
+ appName,
3709
+ switchErr?.message
3710
+ );
3711
+ closeOpenAppUiIfShown();
3712
+ throw ctx.wrapError(switchErr, { defaultAppName: appName });
3713
+ }
3714
+ ctx.clearAllSigners();
3715
+ const result = await runOnce();
3716
+ debugLog("[LegacyChainCall] retry succeeded:", appName);
3717
+ return result;
3718
+ }
3719
+ }
3720
+ async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
3721
+ const dmk = await ctx.getOrCreateDmk();
3722
+ const appManager = new AppManager(dmk);
3723
+ await appManager.ensureAppOpen(sessionId, appName, onPrompt);
4316
3724
  }
4317
3725
 
4318
- // src/connector/chains/tron.ts
4319
- import { HardwareErrorCode as HardwareErrorCode6, bytesToHex as bytesToHex3, hexToBytes as hexToBytes4 } from "@onekeyfe/hwk-adapter-core";
4320
-
4321
- // src/signer/SignerTron.ts
4322
- var SignerTron = class {
4323
- // eslint-disable-next-line no-useless-constructor, no-empty-function
4324
- constructor(_sdk) {
4325
- this._sdk = _sdk;
4326
- }
4327
- /** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
4328
- async getAddress(derivationPath, options) {
4329
- const action = this._sdk.getAddress(derivationPath, options);
4330
- const result = await deviceActionToPromise(
4331
- action,
4332
- this.onInteraction,
4333
- void 0,
4334
- this.onRegisterCanceller
4335
- );
4336
- return { address: result.address, publicKey: result.publicKey };
3726
+ // src/transport/DmkTransport.ts
3727
+ import Transport from "@ledgerhq/hw-transport";
3728
+ var DmkTransport = class extends Transport {
3729
+ constructor(dmk, sessionId) {
3730
+ super();
3731
+ this._dmk = dmk;
3732
+ this._sessionId = sessionId;
4337
3733
  }
4338
- /** Sign a protobuf-encoded raw transaction. */
4339
- async signTransaction(derivationPath, transaction, options) {
4340
- const action = this._sdk.signTransaction(derivationPath, transaction, options);
4341
- return deviceActionToPromise(
4342
- action,
4343
- this.onInteraction,
4344
- void 0,
4345
- this.onRegisterCanceller
4346
- );
3734
+ async exchange(apdu) {
3735
+ const response = await this._dmk.sendApdu({
3736
+ sessionId: this._sessionId,
3737
+ apdu: new Uint8Array(apdu)
3738
+ });
3739
+ const { data, statusCode } = response;
3740
+ const result = Buffer.alloc(data.length + 2);
3741
+ if (data.length > 0) {
3742
+ result.set(data, 0);
3743
+ }
3744
+ result.set(statusCode, data.length);
3745
+ return result;
4347
3746
  }
4348
- /** Sign a personal message (TIP-191). */
4349
- async signPersonalMessage(derivationPath, message, options) {
4350
- const action = this._sdk.signPersonalMessage(derivationPath, message, options);
4351
- return deviceActionToPromise(
4352
- action,
4353
- this.onInteraction,
4354
- void 0,
4355
- this.onRegisterCanceller
4356
- );
3747
+ async close() {
4357
3748
  }
4358
3749
  };
4359
3750
 
4360
3751
  // src/connector/chains/tron.ts
4361
3752
  async function tronGetAddress(ctx, sessionId, params) {
4362
- const tronSigner = await _createTronSigner(ctx, sessionId);
4363
3753
  const path = normalizePath(params.path);
4364
- try {
4365
- const result = await tronSigner.getAddress(path, {
4366
- checkOnDevice: params.showOnDevice ?? false
4367
- });
4368
- return { address: result.address, publicKey: result.publicKey, path: params.path };
4369
- } catch (err) {
4370
- ctx.invalidateSession(sessionId);
4371
- throw ctx.wrapError(err);
4372
- } finally {
4373
- ctx.clearCanceller(sessionId);
4374
- }
3754
+ const showOnDevice = params.showOnDevice ?? false;
3755
+ return withLegacyChainCall(
3756
+ ctx,
3757
+ sessionId,
3758
+ {
3759
+ appName: "Tron",
3760
+ // Only show "confirm on device" UI when the device is actually going
3761
+ // to display the address for the user to verify.
3762
+ needsConfirmation: showOnDevice
3763
+ },
3764
+ async (sid) => {
3765
+ const trx = await _createTrx(ctx, sid);
3766
+ const result = await trx.getAddress(path, showOnDevice);
3767
+ return { address: result.address, publicKey: result.publicKey, path: params.path };
3768
+ }
3769
+ );
4375
3770
  }
4376
3771
  async function tronSignTransaction(ctx, sessionId, params) {
4377
3772
  if (!params.rawTxHex) {
4378
3773
  throw Object.assign(
4379
3774
  new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
4380
- { code: HardwareErrorCode6.InvalidParams }
3775
+ { code: HardwareErrorCode7.InvalidParams }
4381
3776
  );
4382
3777
  }
4383
- const tronSigner = await _createTronSigner(ctx, sessionId);
4384
3778
  const path = normalizePath(params.path);
4385
- try {
4386
- const signature = await tronSigner.signTransaction(path, hexToBytes4(params.rawTxHex));
4387
- return { signature: bytesToHex3(signature) };
4388
- } catch (err) {
4389
- ctx.invalidateSession(sessionId);
4390
- throw ctx.wrapError(err);
4391
- } finally {
4392
- ctx.clearCanceller(sessionId);
4393
- }
3779
+ return withLegacyChainCall(
3780
+ ctx,
3781
+ sessionId,
3782
+ { appName: "Tron", needsConfirmation: true },
3783
+ async (sid) => {
3784
+ const trx = await _createTrx(ctx, sid);
3785
+ const signature = await trx.signTransaction(
3786
+ path,
3787
+ params.rawTxHex,
3788
+ params.tokenSignatures ?? []
3789
+ );
3790
+ return { signature };
3791
+ }
3792
+ );
4394
3793
  }
4395
3794
  async function tronSignMessage(ctx, sessionId, params) {
4396
- const tronSigner = await _createTronSigner(ctx, sessionId);
4397
3795
  const path = normalizePath(params.path);
4398
- try {
4399
- const signature = await tronSigner.signPersonalMessage(path, hexToBytes4(params.messageHex));
4400
- return { signature: bytesToHex3(signature) };
4401
- } catch (err) {
4402
- ctx.invalidateSession(sessionId);
4403
- throw ctx.wrapError(err);
4404
- } finally {
4405
- ctx.clearCanceller(sessionId);
4406
- }
3796
+ return withLegacyChainCall(
3797
+ ctx,
3798
+ sessionId,
3799
+ { appName: "Tron", needsConfirmation: true },
3800
+ async (sid) => {
3801
+ const trx = await _createTrx(ctx, sid);
3802
+ const signature = await trx.signPersonalMessage(path, params.messageHex);
3803
+ return { signature };
3804
+ }
3805
+ );
4407
3806
  }
4408
- async function _createTronSigner(ctx, sessionId) {
3807
+ async function _createTrx(ctx, sessionId) {
4409
3808
  const dmk = await ctx.getOrCreateDmk();
4410
- const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
4411
- const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
4412
- const signer = new SignerTron(sdkSigner);
4413
- signer.onInteraction = (interaction) => {
4414
- debugLog("[LedgerConnector] tron.onInteraction:", interaction);
4415
- ctx.emit("ui-event", {
4416
- type: collapseSignerInteraction(interaction),
4417
- payload: { sessionId }
4418
- });
4419
- };
4420
- signer.onRegisterCanceller = (cancel) => {
4421
- ctx.registerCanceller(sessionId, cancel);
4422
- };
4423
- return signer;
3809
+ return new Trx(new DmkTransport(dmk, sessionId));
4424
3810
  }
4425
3811
 
4426
3812
  // src/device-apps/customActions.ts
@@ -4599,7 +3985,7 @@ var DeviceApps = class {
4599
3985
  seTargetId: v.seTargetId,
4600
3986
  mcuTargetId: v.mcuTargetId,
4601
3987
  seVersion: v.seVersion,
4602
- seFlagsHex: bytesToHex4(v.seFlags),
3988
+ seFlagsHex: bytesToHex2(v.seFlags),
4603
3989
  mcuSephVersion: v.mcuSephVersion,
4604
3990
  mcuBootloaderVersion: v.mcuBootloaderVersion,
4605
3991
  hwVersion: v.hwVersion
@@ -4673,7 +4059,7 @@ var DeviceApps = class {
4673
4059
  }
4674
4060
  }
4675
4061
  };
4676
- function bytesToHex4(bytes) {
4062
+ function bytesToHex2(bytes) {
4677
4063
  if (!bytes) return "";
4678
4064
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
4679
4065
  }
@@ -4716,11 +4102,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
4716
4102
  evm: "Ethereum",
4717
4103
  btc: "Bitcoin",
4718
4104
  sol: "Solana",
4719
- tron: "Tron",
4720
- zcash: "Zcash"
4105
+ tron: "Tron"
4721
4106
  };
4722
4107
  var HARDWARE_ERROR_CODE_VALUES = new Set(
4723
- Object.values(HardwareErrorCode7).filter((value) => typeof value === "number")
4108
+ Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
4724
4109
  );
4725
4110
  var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
4726
4111
  var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
@@ -4757,10 +4142,6 @@ async function defaultLedgerKitImporter(pkg) {
4757
4142
  return import("@ledgerhq/device-signer-kit-bitcoin");
4758
4143
  case "@ledgerhq/device-signer-kit-solana":
4759
4144
  return import("@ledgerhq/device-signer-kit-solana");
4760
- case "@ledgerhq/device-signer-kit-tron":
4761
- return import("@ledgerhq/device-signer-kit-tron");
4762
- case "@ledgerhq/device-signer-kit-zcash":
4763
- return import("@ledgerhq/device-signer-kit-zcash");
4764
4145
  case "@ledgerhq/context-module":
4765
4146
  return import("@ledgerhq/context-module");
4766
4147
  default:
@@ -4919,7 +4300,7 @@ var LedgerConnectorBase = class {
4919
4300
  `Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
4920
4301
  );
4921
4302
  err._tag = ERROR_TAG.BlePairingTimeout;
4922
- err.code = HardwareErrorCode7.BlePairingTimeout;
4303
+ err.code = HardwareErrorCode8.BlePairingTimeout;
4923
4304
  reject(err);
4924
4305
  }, HANG_CEILING_MS);
4925
4306
  });
@@ -4952,7 +4333,7 @@ var LedgerConnectorBase = class {
4952
4333
  "Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
4953
4334
  );
4954
4335
  err._tag = ERROR_TAG.DeviceNotAdvertising;
4955
- err.code = HardwareErrorCode7.DeviceNotFound;
4336
+ err.code = HardwareErrorCode8.DeviceNotFound;
4956
4337
  throw err;
4957
4338
  };
4958
4339
  const doConnect = async (path) => {
@@ -5016,7 +4397,7 @@ var LedgerConnectorBase = class {
5016
4397
  "Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
5017
4398
  );
5018
4399
  wrapped._tag = ERROR_TAG.BleGattBondingFailed;
5019
- wrapped.code = HardwareErrorCode7.BlePairingTimeout;
4400
+ wrapped.code = HardwareErrorCode8.BlePairingTimeout;
5020
4401
  wrapped.originalError = err;
5021
4402
  throw wrapped;
5022
4403
  }
@@ -5094,7 +4475,7 @@ var LedgerConnectorBase = class {
5094
4475
  this._unwatchSessionState(sessionId);
5095
4476
  this._signerManager?.invalidate(sessionId);
5096
4477
  this._cancellers.get(sessionId)?.({
5097
- code: HardwareErrorCode7.DeviceDisconnected,
4478
+ code: HardwareErrorCode8.DeviceDisconnected,
5098
4479
  tag: "DeviceDisconnected",
5099
4480
  message: "Device disconnected"
5100
4481
  });
@@ -5120,7 +4501,7 @@ var LedgerConnectorBase = class {
5120
4501
  success: false,
5121
4502
  error: serializeConnectorError(
5122
4503
  Object.assign(new Error("Ledger app is unresponsive"), {
5123
- code: HardwareErrorCode7.DeviceAppStuck,
4504
+ code: HardwareErrorCode8.DeviceAppStuck,
5124
4505
  _tag: ERROR_TAG.DeviceAppStuck,
5125
4506
  originalError: err
5126
4507
  })
@@ -5132,7 +4513,7 @@ var LedgerConnectorBase = class {
5132
4513
  success: false,
5133
4514
  error: serializeConnectorError(
5134
4515
  Object.assign(new Error("Device communication interrupted, please retry"), {
5135
- code: HardwareErrorCode7.TransportError,
4516
+ code: HardwareErrorCode8.TransportError,
5136
4517
  _tag: ERROR_TAG.DeviceTransportStuck,
5137
4518
  originalError: err
5138
4519
  })
@@ -5178,11 +4559,6 @@ var LedgerConnectorBase = class {
5178
4559
  return solSignTransaction(ctx, sessionId, params);
5179
4560
  case "solSignMessage":
5180
4561
  return solSignMessage(ctx, sessionId, params);
5181
- // ZCASH
5182
- case "zcashGetFullViewingKey":
5183
- return zcashGetFullViewingKey(ctx, sessionId, params);
5184
- case "zcashGetShieldedAddress":
5185
- return zcashGetShieldedAddress(ctx, sessionId, params);
5186
4562
  // TRON
5187
4563
  case "tronGetAddress":
5188
4564
  return tronGetAddress(ctx, sessionId, params);
@@ -5207,7 +4583,7 @@ var LedgerConnectorBase = class {
5207
4583
  try {
5208
4584
  return await apps.install(p.appName, ({ progress }) => {
5209
4585
  ctx.emit("ui-event", {
5210
- type: EConnectorInteraction3.AppInstallProgress,
4586
+ type: EConnectorInteraction4.AppInstallProgress,
5211
4587
  payload: {
5212
4588
  sessionId,
5213
4589
  appName: p.appName,
@@ -5512,7 +4888,7 @@ var LedgerConnectorBase = class {
5512
4888
  * at every catch site. Falls through unchanged for unknown methods.
5513
4889
  */
5514
4890
  _ctxForMethod(method) {
5515
- const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
4891
+ const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
5516
4892
  const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
5517
4893
  if (!defaultAppName) return this._ctx;
5518
4894
  return {
@@ -5546,209 +4922,6 @@ var LedgerConnectorBase = class {
5546
4922
  return error;
5547
4923
  }
5548
4924
  };
5549
-
5550
- // src/transport/DmkTransport.ts
5551
- import Transport from "@ledgerhq/hw-transport";
5552
- var DmkTransport = class extends Transport {
5553
- constructor(dmk, sessionId) {
5554
- super();
5555
- this._dmk = dmk;
5556
- this._sessionId = sessionId;
5557
- }
5558
- async exchange(apdu) {
5559
- const response = await this._dmk.sendApdu({
5560
- sessionId: this._sessionId,
5561
- apdu: new Uint8Array(apdu)
5562
- });
5563
- const { data, statusCode } = response;
5564
- const result = Buffer.alloc(data.length + 2);
5565
- if (data.length > 0) {
5566
- result.set(data, 0);
5567
- }
5568
- result.set(statusCode, data.length);
5569
- return result;
5570
- }
5571
- async close() {
5572
- }
5573
- };
5574
-
5575
- // src/app/AppManager.ts
5576
- import {
5577
- CloseAppCommand,
5578
- GetAppAndVersionCommand,
5579
- OpenAppCommand,
5580
- isSuccessCommandResult
5581
- } from "@ledgerhq/device-management-kit";
5582
- import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
5583
- var APP_NAME_MAP = {
5584
- ETH: "Ethereum",
5585
- BTC: "Bitcoin",
5586
- SOL: "Solana",
5587
- TRX: "Tron",
5588
- ZEC: "Zcash",
5589
- XRP: "XRP",
5590
- ADA: "Cardano",
5591
- DOT: "Polkadot",
5592
- ATOM: "Cosmos"
5593
- };
5594
- var DASHBOARD_APP_NAME = "BOLOS";
5595
- var AppManager = class {
5596
- constructor(dmk, options) {
5597
- this._dmk = dmk;
5598
- this._waitMs = options?.waitMs ?? 1e3;
5599
- this._maxRetries = options?.maxRetries ?? 10;
5600
- }
5601
- /**
5602
- * Return the Ledger app name for a given chain ticker,
5603
- * or undefined if the chain is not supported.
5604
- */
5605
- static getAppName(chain) {
5606
- return APP_NAME_MAP[chain];
5607
- }
5608
- /**
5609
- * Ensure the target app is open on the device identified by `sessionId`.
5610
- *
5611
- * Flow:
5612
- * 1. Check the currently running app.
5613
- * 2. If it is already the target, return immediately.
5614
- * 3. If a different app is running (not dashboard), close it first.
5615
- * 4. Open the target app.
5616
- * 5. Poll until the device confirms the target app is running.
5617
- */
5618
- /**
5619
- * @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
5620
- * device is about to display "Open <app>" on screen and wait for the
5621
- * user's button press. UI consumers should show their "open app" prompt
5622
- * in response. NOT called when the target app is already open (no user
5623
- * interaction needed in that case).
5624
- *
5625
- * Important: OpenAppCommand is blocking. It does not resolve until the user
5626
- * has physically confirmed on the device, so anything that runs AFTER
5627
- * `await this._openApp(...)` lands AFTER the prompt is already gone.
5628
- * Hence the callback must fire BEFORE that await.
5629
- */
5630
- async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
5631
- const currentApp = await this._getCurrentApp(sessionId);
5632
- if (currentApp === targetAppName) {
5633
- return;
5634
- }
5635
- if (!this._isDashboard(currentApp)) {
5636
- await this._closeCurrentApp(sessionId);
5637
- await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
5638
- }
5639
- onConfirmOnDevice?.();
5640
- await this._openApp(sessionId, targetAppName);
5641
- await this._waitForApp(sessionId, targetAppName);
5642
- }
5643
- // ---------------------------------------------------------------------------
5644
- // Private helpers
5645
- // ---------------------------------------------------------------------------
5646
- async _getCurrentApp(sessionId) {
5647
- const result = await this._dmk.sendCommand({
5648
- sessionId,
5649
- command: new GetAppAndVersionCommand()
5650
- });
5651
- if (isSuccessCommandResult(result)) {
5652
- debugLog("[AppManager] currentApp:", result.data.name);
5653
- return result.data.name;
5654
- }
5655
- const errResult = result;
5656
- const dmkErr = errResult.error ?? {};
5657
- const original = dmkErr.originalError;
5658
- debugLog(
5659
- "[AppManager] _getCurrentApp failed sessionId=",
5660
- sessionId,
5661
- "tag=",
5662
- dmkErr._tag,
5663
- "errorCode=",
5664
- dmkErr.errorCode,
5665
- "message=",
5666
- dmkErr.message,
5667
- "originalErrorMessage=",
5668
- original?.message ?? String(original ?? "")
5669
- );
5670
- throw Object.assign(
5671
- new Error(
5672
- dmkErr.message ?? "Failed to get current app from device"
5673
- ),
5674
- {
5675
- _tag: dmkErr._tag,
5676
- errorCode: dmkErr.errorCode,
5677
- originalError: original
5678
- }
5679
- );
5680
- }
5681
- async _openApp(sessionId, appName) {
5682
- const result = await this._dmk.sendCommand({
5683
- sessionId,
5684
- command: new OpenAppCommand({ appName })
5685
- });
5686
- if (!isSuccessCommandResult(result)) {
5687
- const dmkErr = result.error;
5688
- const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
5689
- const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
5690
- debugLog(
5691
- "[AppManager] openApp failed:",
5692
- appName,
5693
- "errorCode:",
5694
- errorCode,
5695
- "tag:",
5696
- dmkErr._tag
5697
- );
5698
- let code;
5699
- if (errorCode === "6807" || /unknown application/i.test(message)) {
5700
- code = HardwareErrorCode8.AppNotInstalled;
5701
- } else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
5702
- code = HardwareErrorCode8.UserRejected;
5703
- }
5704
- throw Object.assign(new Error(`Failed to open "${appName}"`), {
5705
- _tag: ERROR_TAG.OpenAppCommand,
5706
- code,
5707
- errorCode,
5708
- statusCode: errorCode,
5709
- appName,
5710
- originalError: dmkErr
5711
- });
5712
- }
5713
- }
5714
- async _closeCurrentApp(sessionId) {
5715
- debugLog("[AppManager] closeCurrentApp");
5716
- await this._dmk.sendCommand({
5717
- sessionId,
5718
- command: new CloseAppCommand()
5719
- });
5720
- }
5721
- /**
5722
- * Poll the device until the expected app is reported as running,
5723
- * or throw after `_maxRetries` attempts.
5724
- */
5725
- async _waitForApp(sessionId, expectedAppName) {
5726
- let lastSeen = "";
5727
- for (let i = 0; i < this._maxRetries; i++) {
5728
- await this._wait();
5729
- const current = await this._getCurrentApp(sessionId);
5730
- lastSeen = current;
5731
- if (current === expectedAppName) {
5732
- return;
5733
- }
5734
- }
5735
- debugLog(
5736
- "[AppManager] waitForApp exhausted: expected=",
5737
- expectedAppName,
5738
- "lastSeen=",
5739
- lastSeen
5740
- );
5741
- throw new Error(
5742
- `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
5743
- );
5744
- }
5745
- _isDashboard(appName) {
5746
- return appName === DASHBOARD_APP_NAME;
5747
- }
5748
- _wait() {
5749
- return new Promise((resolve) => setTimeout(resolve, this._waitMs));
5750
- }
5751
- };
5752
4925
  export {
5753
4926
  AppManager,
5754
4927
  DmkTransport,
@@ -5759,7 +4932,6 @@ export {
5759
4932
  SignerEth,
5760
4933
  SignerManager,
5761
4934
  SignerSol,
5762
- SignerZcash,
5763
4935
  debugLog,
5764
4936
  deviceActionToPromise,
5765
4937
  isDeviceLockedError,