@onekeyfe/hwk-ledger-adapter 1.1.26-alpha.8 → 1.1.26-patch.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -39,13 +39,16 @@ __export(index_exports, {
39
39
  SignerEth: () => SignerEth,
40
40
  SignerManager: () => SignerManager,
41
41
  SignerSol: () => SignerSol,
42
+ debugLog: () => debugLog,
42
43
  deviceActionToPromise: () => deviceActionToPromise,
43
- extractBleHexId: () => extractBleHexId,
44
- isDebugEnabled: () => isDebugEnabled,
45
44
  isDeviceLockedError: () => isDeviceLockedError,
45
+ isLedgerBleConnectionType: () => isLedgerBleConnectionType,
46
+ isLedgerBleDescriptor: () => isLedgerBleDescriptor,
47
+ isLedgerDmkBleTransport: () => isLedgerDmkBleTransport,
46
48
  ledgerFailure: () => ledgerFailure,
47
49
  mapLedgerError: () => mapLedgerError,
48
- setDebugEnabled: () => setDebugEnabled
50
+ offSdkEvent: () => offSdkEvent,
51
+ onSdkEvent: () => onSdkEvent
49
52
  });
50
53
  module.exports = __toCommonJS(index_exports);
51
54
 
@@ -54,24 +57,32 @@ var import_hwk_adapter_core2 = require("@onekeyfe/hwk-adapter-core");
54
57
 
55
58
  // src/errors.ts
56
59
  var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
57
- function ledgerFailure(code, error, appName) {
60
+ var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
61
+ function createMultipleUsbLedgerDevicesError() {
62
+ return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
63
+ code: import_hwk_adapter_core.HardwareErrorCode.DeviceOneDeviceOnly
64
+ });
65
+ }
66
+ function ledgerFailure(code, error, appName, tag, params) {
58
67
  const payload = { error, code };
59
68
  if (appName !== void 0) payload.appName = appName;
69
+ if (tag !== void 0) payload._tag = tag;
70
+ if (params !== void 0) payload.params = params;
60
71
  return { success: false, payload };
61
72
  }
62
73
  var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
63
74
  var USER_REJECTED_CODES = /* @__PURE__ */ new Set(["6985", "27013"]);
64
75
  var WRONG_APP_CODES = /* @__PURE__ */ new Set(["6e00", "28160", "6d00", "27904", "6a83", "27267"]);
65
76
  var APP_NOT_INSTALLED_CODES = /* @__PURE__ */ new Set(["6807", "26631"]);
66
- var STEP_BLIND_SIGN_FALLBACK = "signer.eth.steps.blindSignTransactionFallback";
77
+ var STEP_BLIND_SIGN_TRANSACTION_FALLBACK = "signer.eth.steps.blindSignTransactionFallback";
67
78
  function getEthAppErrorCode(err) {
68
79
  if (!err || typeof err !== "object") return null;
69
80
  const e = err;
70
- if (e._tag === "EthAppCommandError" && typeof e.errorCode === "string") {
81
+ if (e._tag === ERROR_TAG.EthAppCommand && typeof e.errorCode === "string") {
71
82
  return e.errorCode.toLowerCase();
72
83
  }
73
84
  const orig = e.originalError;
74
- if (orig?._tag === "EthAppCommandError" && typeof orig.errorCode === "string") {
85
+ if (orig?._tag === ERROR_TAG.EthAppCommand && typeof orig.errorCode === "string") {
75
86
  return orig.errorCode.toLowerCase();
76
87
  }
77
88
  return null;
@@ -128,13 +139,8 @@ function mapBtcAppError(hex) {
128
139
  return null;
129
140
  }
130
141
  }
131
- function mapEthAppError(ethCode, lastStep) {
142
+ function mapEthAppErrorCode(ethCode) {
132
143
  switch (ethCode) {
133
- case "6a80":
134
- if (lastStep === STEP_BLIND_SIGN_FALLBACK) {
135
- return import_hwk_adapter_core.HardwareErrorCode.EvmBlindSigningRequired;
136
- }
137
- return null;
138
144
  case "6984":
139
145
  return import_hwk_adapter_core.HardwareErrorCode.EvmClearSignPluginMissing;
140
146
  case "6a84":
@@ -147,16 +153,133 @@ function mapEthAppError(ethCode, lastStep) {
147
153
  return null;
148
154
  }
149
155
  }
156
+ function classifyEthAppError(err) {
157
+ const ethCode = getEthAppErrorCode(err);
158
+ if (!ethCode) return null;
159
+ if (ethCode === "6a80") {
160
+ return hasBlindSignFallbackStep(err) ? import_hwk_adapter_core.HardwareErrorCode.EvmBlindSigningRequired : null;
161
+ }
162
+ return mapEthAppErrorCode(ethCode);
163
+ }
164
+ var ERROR_TAG = {
165
+ // SDK-mint
166
+ DeviceNotAdvertising: "DeviceNotAdvertisingError",
167
+ // BLE scan miss
168
+ DeviceNotInDiscoveryCache: "DeviceNotInDiscoveryCacheError",
169
+ // dm.connect() before enumerate
170
+ BlePairingTimeout: "BlePairingTimeoutError",
171
+ // SMP 30s timeout
172
+ BleGattBondingFailed: "BleGattBondingFailedError",
173
+ // other GATT failure
174
+ UserAborted: "UserAborted",
175
+ DeviceAppStuck: "DeviceAppStuck",
176
+ // chain app wedged (APDU 0x6901)
177
+ DeviceTransportStuck: "DeviceTransportStuck",
178
+ // DMK transport queue wedged
179
+ // DMK-reuse (DMK throws same string; we synthesize too)
180
+ DeviceLocked: "DeviceLockedError",
181
+ DeviceNotRecognized: "DeviceNotRecognizedError",
182
+ DeviceSessionNotFound: "DeviceSessionNotFound",
183
+ OpenAppCommand: "OpenAppCommandError",
184
+ // DMK-only (read only)
185
+ EthAppCommand: "EthAppCommandError",
186
+ UserRefusedOnDevice: "UserRefusedOnDevice",
187
+ WrongAppOpened: "WrongAppOpenedError",
188
+ InvalidStatusWord: "InvalidStatusWordError",
189
+ AlreadySendingApdu: "AlreadySendingApduError",
190
+ UnknownDeviceExchange: "UnknownDeviceExchangeError",
191
+ NoAccessibleDevice: "NoAccessibleDeviceError",
192
+ UnknownDevice: "UnknownDeviceError",
193
+ DeviceSessionRefresher: "DeviceSessionRefresherError",
194
+ DeviceNotInitialized: "DeviceNotInitializedError",
195
+ OpeningConnection: "OpeningConnectionError",
196
+ DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
197
+ DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
198
+ Disconnect: "DisconnectError",
199
+ ReconnectionFailed: "ReconnectionFailedError",
200
+ WebHIDDisconnect: "WebHIDDisconnectError",
201
+ // ble-plx surfaces this when GATT notification setup fails — typical
202
+ // outcome when the user doesn't confirm pairing on the device, or the
203
+ // existing bond is invalid. Observed in production after ~30s.
204
+ PairingRefused: "PairingRefusedError"
205
+ };
150
206
  function isDeviceLockedError(err) {
151
207
  if (!err || typeof err !== "object") return false;
152
208
  const e = err;
153
209
  if (e.errorCode != null && LOCKED_ERROR_CODES.has(String(e.errorCode))) return true;
154
210
  if (e.statusCode != null && LOCKED_ERROR_CODES.has(String(e.statusCode))) return true;
155
- if (e._tag === "DeviceLockedError") return true;
211
+ if (e._tag === ERROR_TAG.DeviceLocked) return true;
156
212
  if (e.originalError != null && isDeviceLockedError(e.originalError)) return true;
157
213
  if (e.error != null && e._tag && isDeviceLockedError(e.error)) return true;
158
214
  return false;
159
215
  }
216
+ function isDeviceNotAdvertisingError(err) {
217
+ if (!err || typeof err !== "object") return false;
218
+ return err._tag === ERROR_TAG.DeviceNotAdvertising;
219
+ }
220
+ var PAIRING_FAILURE_TAGS = /* @__PURE__ */ new Set([
221
+ ERROR_TAG.BlePairingTimeout,
222
+ ERROR_TAG.BleGattBondingFailed,
223
+ ERROR_TAG.PairingRefused
224
+ ]);
225
+ function isBlePairingFailureError(err) {
226
+ if (!err || typeof err !== "object") return false;
227
+ const tag = err._tag;
228
+ if (tag && PAIRING_FAILURE_TAGS.has(tag)) return true;
229
+ const orig = err.originalError;
230
+ if (orig != null && isBlePairingFailureError(orig)) return true;
231
+ return false;
232
+ }
233
+ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
234
+ ERROR_TAG.DeviceLocked,
235
+ ERROR_TAG.DeviceNotAdvertising,
236
+ ERROR_TAG.BlePairingTimeout,
237
+ ERROR_TAG.BleGattBondingFailed,
238
+ ERROR_TAG.PairingRefused,
239
+ ERROR_TAG.DeviceNotRecognized,
240
+ ERROR_TAG.NoAccessibleDevice,
241
+ ERROR_TAG.UnknownDevice,
242
+ ERROR_TAG.DeviceSessionNotFound,
243
+ ERROR_TAG.DeviceSessionRefresher,
244
+ ERROR_TAG.DeviceNotInitialized,
245
+ ERROR_TAG.OpeningConnection,
246
+ ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
247
+ ERROR_TAG.DeviceDisconnectedWhileSending,
248
+ ERROR_TAG.Disconnect,
249
+ ERROR_TAG.ReconnectionFailed,
250
+ ERROR_TAG.WebHIDDisconnect
251
+ ]);
252
+ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
253
+ ERROR_TAG.NoAccessibleDevice,
254
+ ERROR_TAG.UnknownDevice,
255
+ ERROR_TAG.DeviceNotInitialized,
256
+ // SDK-internal: dm.connect() called before _discovered was populated.
257
+ // Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
258
+ ERROR_TAG.DeviceNotInDiscoveryCache
259
+ ]);
260
+ var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
261
+ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
262
+ ERROR_TAG.DeviceNotRecognized,
263
+ ERROR_TAG.DeviceSessionNotFound,
264
+ ERROR_TAG.DeviceSessionRefresher,
265
+ ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
266
+ ERROR_TAG.DeviceDisconnectedWhileSending,
267
+ ERROR_TAG.Disconnect,
268
+ ERROR_TAG.ReconnectionFailed,
269
+ ERROR_TAG.WebHIDDisconnect
270
+ ]);
271
+ function isConnectionLevelError(err) {
272
+ if (!err || typeof err !== "object") return false;
273
+ const tag = err._tag;
274
+ if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
275
+ const e = err;
276
+ if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
277
+ if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
278
+ return false;
279
+ }
280
+ function isKnownConnectionTag(tag) {
281
+ return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
282
+ }
160
283
  function hasStatusCode(err, codeSet) {
161
284
  if (!err || typeof err !== "object") return false;
162
285
  const e = err;
@@ -166,10 +289,39 @@ function hasStatusCode(err, codeSet) {
166
289
  if (e.error != null && e._tag && hasStatusCode(e.error, codeSet)) return true;
167
290
  return false;
168
291
  }
292
+ function hasBlindSignFallbackStep(err) {
293
+ if (!err || typeof err !== "object") return false;
294
+ const e = err;
295
+ if (e._lastStep === STEP_BLIND_SIGN_TRANSACTION_FALLBACK) return true;
296
+ if (Array.isArray(e._deviceActionSteps) && e._deviceActionSteps.includes(STEP_BLIND_SIGN_TRANSACTION_FALLBACK)) {
297
+ return true;
298
+ }
299
+ if (e.originalError != null && hasBlindSignFallbackStep(e.originalError)) return true;
300
+ if (e.error != null && e._tag && hasBlindSignFallbackStep(e.error)) return true;
301
+ return false;
302
+ }
303
+ function isDeviceNotFoundError(err) {
304
+ if (!err || typeof err !== "object") return false;
305
+ const tag = err._tag;
306
+ if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
307
+ const e = err;
308
+ if (e.originalError != null && isDeviceNotFoundError(e.originalError)) return true;
309
+ if (e.error != null && e._tag && isDeviceNotFoundError(e.error)) return true;
310
+ return false;
311
+ }
312
+ function isDeviceBusyError(err) {
313
+ if (!err || typeof err !== "object") return false;
314
+ const tag = err._tag;
315
+ if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
316
+ const e = err;
317
+ if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
318
+ if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
319
+ return false;
320
+ }
169
321
  function isUserRejectedError(err) {
170
322
  if (!err || typeof err !== "object") return false;
171
323
  const e = err;
172
- if (e._tag === "UserRefusedOnDevice") return true;
324
+ if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
173
325
  if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
174
326
  if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
175
327
  return false;
@@ -177,7 +329,7 @@ function isUserRejectedError(err) {
177
329
  function isWrongAppError(err) {
178
330
  if (!err || typeof err !== "object") return false;
179
331
  const e = err;
180
- if (e._tag === "WrongAppOpenedError" || e._tag === "InvalidStatusWordError") {
332
+ if (e._tag === ERROR_TAG.WrongAppOpened || e._tag === ERROR_TAG.InvalidStatusWord) {
181
333
  if (hasStatusCode(err, WRONG_APP_CODES)) return true;
182
334
  }
183
335
  if (typeof e.message === "string") {
@@ -191,7 +343,6 @@ function isWrongAppError(err) {
191
343
  function isAppNotInstalledError(err) {
192
344
  if (!err || typeof err !== "object") return false;
193
345
  const e = err;
194
- if (e._tag === "OpenAppCommandError") return true;
195
346
  if (typeof e.message === "string" && /unknown application/i.test(e.message)) return true;
196
347
  if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
197
348
  return false;
@@ -199,7 +350,8 @@ function isAppNotInstalledError(err) {
199
350
  function isDeviceDisconnectedError(err) {
200
351
  if (!err || typeof err !== "object") return false;
201
352
  const e = err;
202
- if (e._tag === "DeviceNotRecognizedError" || e._tag === "DeviceSessionNotFound") return true;
353
+ const tag = e._tag;
354
+ if (typeof tag === "string" && DEVICE_DISCONNECTED_TAGS.has(tag)) return true;
203
355
  if (typeof e.message === "string") {
204
356
  const msg = e.message.toLowerCase();
205
357
  if (msg.includes("disconnected") || msg.includes("not found") || msg.includes("no device") || msg.includes("unplugged"))
@@ -219,7 +371,22 @@ function isTimeoutError(err) {
219
371
  if (e.code === import_hwk_adapter_core.HardwareErrorCode.OperationTimeout) return true;
220
372
  return false;
221
373
  }
222
- function mapLedgerError(err) {
374
+ function isAppStuckByApdu(err) {
375
+ if (!err || typeof err !== "object") return false;
376
+ const tag = err._tag;
377
+ if (tag === ERROR_TAG.DeviceAppStuck) return true;
378
+ return extractApduHex(err) === "6901";
379
+ }
380
+ function isTransportStuck(err) {
381
+ if (!err || typeof err !== "object") return false;
382
+ const tag = err._tag;
383
+ return tag === ERROR_TAG.DeviceTransportStuck || // already wrapped form
384
+ tag === ERROR_TAG.UnknownDeviceExchange || tag === ERROR_TAG.AlreadySendingApdu;
385
+ }
386
+ function isStuckAppStateError(err) {
387
+ return isAppStuckByApdu(err) || isTransportStuck(err);
388
+ }
389
+ function mapLedgerError(err, opts) {
223
390
  let originalMessage = "Unknown Ledger error";
224
391
  if (err instanceof Error) {
225
392
  originalMessage = err.message;
@@ -230,60 +397,127 @@ function mapLedgerError(err) {
230
397
  let code;
231
398
  if (isDeviceLockedError(err)) {
232
399
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceLocked;
400
+ } else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
401
+ code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound;
402
+ } else if (isDeviceBusyError(err)) {
403
+ code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
404
+ } else if (isBlePairingFailureError(err)) {
405
+ code = import_hwk_adapter_core.HardwareErrorCode.BlePairingTimeout;
233
406
  } else if (isUserRejectedError(err)) {
234
407
  code = import_hwk_adapter_core.HardwareErrorCode.UserRejected;
235
408
  } else if (isWrongAppError(err)) {
236
409
  code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
237
410
  } else if (isAppNotInstalledError(err)) {
238
- code = import_hwk_adapter_core.HardwareErrorCode.AppNotOpen;
411
+ code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
239
412
  } else if (isDeviceDisconnectedError(err)) {
240
413
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
241
414
  } else if (isTimeoutError(err)) {
242
415
  code = import_hwk_adapter_core.HardwareErrorCode.OperationTimeout;
243
416
  } else {
244
- const ethCode = getEthAppErrorCode(err);
245
- const lastStep = err && typeof err === "object" ? err._lastStep : void 0;
246
- const ethMapped = ethCode ? mapEthAppError(ethCode, lastStep) : null;
417
+ const ethMapped = classifyEthAppError(err);
247
418
  const apduHex = ethMapped ? null : extractApduHex(err);
248
419
  const chainMapped = apduHex ? mapSolanaAppError(apduHex) ?? mapTronAppError(apduHex) ?? mapBtcAppError(apduHex) : null;
249
420
  code = ethMapped ?? chainMapped ?? import_hwk_adapter_core.HardwareErrorCode.UnknownError;
250
421
  }
251
- const appName = err && typeof err === "object" ? err.appName : void 0;
422
+ const errAppName = err && typeof err === "object" ? err.appName : void 0;
423
+ const appName = errAppName ?? opts?.defaultAppName;
252
424
  return { code, message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage), appName };
253
425
  }
254
426
 
255
- // src/utils/debugLog.ts
256
- var enabled = false;
257
- function setDebugEnabled(value) {
258
- enabled = value;
427
+ // src/utils/ledgerDmkTransport.ts
428
+ function isLedgerDmkBleTransport(transport) {
429
+ const normalized = transport?.toUpperCase();
430
+ return normalized === "BLE" || normalized?.endsWith("_BLE") === true;
259
431
  }
260
- function isDebugEnabled() {
261
- return enabled;
432
+ function isLedgerBleConnectionType(connectionType) {
433
+ return connectionType === "ble";
262
434
  }
263
- function debugLog(...args) {
264
- if (enabled) {
265
- console.debug(...args);
435
+ function isLedgerBleDescriptor(connectionType, descriptor) {
436
+ return isLedgerBleConnectionType(connectionType) || isLedgerDmkBleTransport(descriptor.transport);
437
+ }
438
+
439
+ // src/utils/sdkEventBus.ts
440
+ var listeners = /* @__PURE__ */ new Set();
441
+ function onSdkEvent(listener) {
442
+ listeners.add(listener);
443
+ return () => {
444
+ listeners.delete(listener);
445
+ };
446
+ }
447
+ function offSdkEvent(listener) {
448
+ listeners.delete(listener);
449
+ }
450
+ function emitSdkEvent(event) {
451
+ if (listeners.size === 0) return;
452
+ for (const listener of listeners) {
453
+ try {
454
+ listener(event);
455
+ } catch {
456
+ }
266
457
  }
267
458
  }
268
- function debugError(...args) {
269
- if (enabled) {
270
- console.error(...args);
459
+ function emitLog(level, ...args) {
460
+ if (listeners.size === 0) return;
461
+ const message = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
462
+ emitSdkEvent({ type: "log", level, message });
463
+ }
464
+ function safeStringify(value) {
465
+ if (value instanceof Error) {
466
+ return value.stack ? `${value.message}
467
+ ${value.stack}` : value.message;
468
+ }
469
+ try {
470
+ return JSON.stringify(value);
471
+ } catch {
472
+ return String(value);
271
473
  }
272
474
  }
273
475
 
476
+ // src/utils/debugLog.ts
477
+ function debugLog(...args) {
478
+ emitLog("debug", ...args);
479
+ }
480
+ function debugError(...args) {
481
+ emitLog("error", ...args);
482
+ }
483
+
274
484
  // src/adapter/LedgerAdapter.ts
275
485
  function formatDeviceMismatchError(expected, actual) {
276
486
  return `Wrong device: expected ${expected}, got ${actual}`;
277
487
  }
488
+ var BTC_HIGH_INDEX_THRESHOLD = 100;
489
+ function btcAccountIndexFromPath(path) {
490
+ const segments = path.replace(/^m\//, "").split("/");
491
+ if (segments.length < 3) return null;
492
+ const accountSeg = segments[2].replace(/['h]$/i, "");
493
+ const accountIndex = parseInt(accountSeg, 10);
494
+ return Number.isFinite(accountIndex) ? accountIndex : null;
495
+ }
278
496
  var _LedgerAdapter = class _LedgerAdapter {
279
- constructor(connector, options) {
497
+ constructor(connector) {
280
498
  this.vendor = "ledger";
281
499
  this.emitter = new import_hwk_adapter_core2.TypedEventEmitter();
282
- // Device cache: tracks discovered devices from connector events
283
500
  this._discoveredDevices = /* @__PURE__ */ new Map();
284
- // Session tracking: maps connectId -> sessionId
285
501
  this._sessions = /* @__PURE__ */ new Map();
286
502
  this._uiRegistry = new import_hwk_adapter_core2.UiRequestRegistry();
503
+ // BTC App rejects account index >= 100 unless display=true. Cached per
504
+ // adapter instance: first 100+ path asks the user once via UI request,
505
+ // subsequent 100+ paths in the same session auto-promote silently.
506
+ // The Ledger device itself still requires a per-call confirmation — that's
507
+ // the Ledger app's safety boundary, not ours to bypass.
508
+ this._btcHighIndexConfirmedThisSession = false;
509
+ // Shared across concurrent callers — only `cancel()` aborts.
510
+ this._doConnectAbortController = null;
511
+ // ---------------------------------------------------------------------------
512
+ // Private helpers
513
+ // ---------------------------------------------------------------------------
514
+ /**
515
+ * Ensure at least one device is connected and return a valid connectId.
516
+ *
517
+ * - If a session already exists for the given connectId, reuse it.
518
+ * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
519
+ * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
520
+ */
287
521
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
288
522
  this._connectingPromise = null;
289
523
  // ---------------------------------------------------------------------------
@@ -306,47 +540,30 @@ var _LedgerAdapter = class _LedgerAdapter {
306
540
  payload: { connectId: data.connectId }
307
541
  });
308
542
  };
309
- this.uiRequestHandler = (data) => {
310
- this.handleUiEvent(data);
311
- };
312
- this.uiEventHandler = (data) => {
313
- this.handleUiEvent(data);
543
+ // Forward low-level connector 'ui-event' (the four EConnectorInteraction values)
544
+ // to the public hw.emitter so consumers only need to subscribe in one place
545
+ // (hw.on instead of also reaching into connector.on).
546
+ this.uiEventForwarder = (event) => {
547
+ this.emitter.emit("ui-event", event);
314
548
  };
315
549
  this.connector = connector;
316
- this._handleSelectDevice = options?.handleSelectDevice ?? false;
317
- this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue({
318
- emit: (event, data) => this.emitter.emit(event, data),
319
- uiRegistry: this._uiRegistry
320
- });
550
+ this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue();
321
551
  this.registerEventListeners();
322
552
  }
323
- /**
324
- * Classify a method's interruptibility.
325
- * - Signing / typed data / transaction → 'confirm' (user may decide via preemption UI)
326
- * - Read-only queries (getAddress / getPublicKey / getMasterFingerprint) → 'safe'
327
- * (auto-cancels any pending read for the same device)
328
- */
329
- static _getInterruptibility(method) {
330
- if (method.toLowerCase().includes("sign")) return "confirm";
331
- return "safe";
332
- }
333
- // ---------------------------------------------------------------------------
334
553
  // Transport
335
- // ---------------------------------------------------------------------------
336
- // Transport is decided at connector creation time. These methods
337
- // satisfy the IHardwareWallet interface with sensible defaults.
338
554
  get activeTransport() {
339
- return this.connector.connectionType === "ble" ? "ble" : "hid";
555
+ return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
340
556
  }
341
557
  getAvailableTransports() {
342
558
  return this.activeTransport ? [this.activeTransport] : [];
343
559
  }
344
- async switchTransport(_type) {
560
+ // Connector is bound at construction; switching requires a new adapter.
561
+ switchTransport(_type) {
562
+ return Promise.resolve();
345
563
  }
346
- // ---------------------------------------------------------------------------
347
564
  // Lifecycle
348
- // ---------------------------------------------------------------------------
349
- async init(_config) {
565
+ init(_config) {
566
+ return Promise.resolve();
350
567
  }
351
568
  /**
352
569
  * Clear cached device/session state without tearing down the adapter.
@@ -359,6 +576,7 @@ var _LedgerAdapter = class _LedgerAdapter {
359
576
  this._connectingPromise = null;
360
577
  this._uiRegistry.reset();
361
578
  this._jobQueue.clear();
579
+ this._btcHighIndexConfirmedThisSession = false;
362
580
  }
363
581
  async dispose() {
364
582
  this._uiRegistry.reset();
@@ -367,6 +585,7 @@ var _LedgerAdapter = class _LedgerAdapter {
367
585
  this.connector.reset();
368
586
  this._discoveredDevices.clear();
369
587
  this._sessions.clear();
588
+ this._btcHighIndexConfirmedThisSession = false;
370
589
  this.emitter.removeAllListeners();
371
590
  }
372
591
  uiResponse(response) {
@@ -375,10 +594,17 @@ var _LedgerAdapter = class _LedgerAdapter {
375
594
  // ---------------------------------------------------------------------------
376
595
  // Device management
377
596
  // ---------------------------------------------------------------------------
378
- async searchDevices() {
597
+ async searchDevices(options) {
598
+ if (options?.resetSession) {
599
+ this._doConnectAbortController?.abort();
600
+ this._sessions.clear();
601
+ this._connectingPromise = null;
602
+ this._doConnectAbortController = null;
603
+ this._btcHighIndexConfirmedThisSession = false;
604
+ }
379
605
  await this._ensureDevicePermission();
380
606
  const devices = await this.connector.searchDevices();
381
- debugLog("[DMK] adapter.searchDevices raw:", JSON.stringify(devices));
607
+ this._discoveredDevices.clear();
382
608
  for (const d of devices) {
383
609
  if (d.connectId) {
384
610
  this._discoveredDevices.set(d.connectId, this.connectorDeviceToDeviceInfo(d));
@@ -387,11 +613,41 @@ var _LedgerAdapter = class _LedgerAdapter {
387
613
  if (this._discoveredDevices.size === 0) {
388
614
  await this._ensureDevicePermission();
389
615
  }
616
+ debugLog(
617
+ `[LedgerAdapter] searchDevices() return count=${this._discoveredDevices.size} ids=[${[
618
+ ...this._discoveredDevices.keys()
619
+ ].join(",")}]`
620
+ );
390
621
  return Array.from(this._discoveredDevices.values());
391
622
  }
623
+ // USB single-session invariant: evict all sessions, best-effort (see connectDevice).
624
+ async _evictAllSessions() {
625
+ if (this._sessions.size === 0) return;
626
+ const stale = [...this._sessions.values()];
627
+ this._sessions.clear();
628
+ for (const sid of stale) {
629
+ try {
630
+ await this.connector.disconnect(sid);
631
+ } catch {
632
+ }
633
+ }
634
+ }
635
+ static _createDeviceBusyError(method) {
636
+ return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
637
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceBusy
638
+ });
639
+ }
392
640
  async connectDevice(connectId) {
393
- await this._ensureDevicePermission(connectId);
394
641
  try {
642
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
643
+ throw Object.assign(new Error("Ledger BLE connectId is required."), {
644
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
645
+ });
646
+ }
647
+ if (!isLedgerBleConnectionType(this.connector.connectionType)) {
648
+ await this._evictAllSessions();
649
+ }
650
+ await this._ensureDevicePermission(connectId);
395
651
  const session = await this.connector.connect(connectId);
396
652
  this._sessions.set(connectId, session.sessionId);
397
653
  if (session.deviceInfo) {
@@ -427,7 +683,6 @@ var _LedgerAdapter = class _LedgerAdapter {
427
683
  // Chain call helper
428
684
  // ---------------------------------------------------------------------------
429
685
  async callChain(connectId, deviceId, chain, method, params, skipFingerprint = false) {
430
- await this._ensureDevicePermission(connectId, deviceId);
431
686
  try {
432
687
  const result = await this.connectorCall(connectId, method, params, {
433
688
  chain,
@@ -439,45 +694,12 @@ var _LedgerAdapter = class _LedgerAdapter {
439
694
  return this.errorToFailure(err);
440
695
  }
441
696
  }
442
- /**
443
- * Batch version of callChain — checks permission once,
444
- * fingerprint is verified on the first call inside connectorCall.
445
- */
446
- async callChainBatch(connectId, deviceId, chain, method, params, onProgress, skipFingerprint = false) {
447
- await this._ensureDevicePermission(connectId, deviceId);
448
- const results = [];
449
- for (let i = 0; i < params.length; i++) {
450
- try {
451
- const result = await this.connectorCall(connectId, method, params[i], {
452
- chain,
453
- deviceId,
454
- // Only verify fingerprint on the first call in the batch
455
- skipFingerprint: skipFingerprint || i > 0
456
- });
457
- results.push(result);
458
- onProgress?.({ index: i, total: params.length });
459
- } catch (err) {
460
- return this.errorToFailure(err);
461
- }
462
- }
463
- return (0, import_hwk_adapter_core2.success)(results);
464
- }
465
697
  // ---------------------------------------------------------------------------
466
698
  // EVM chain methods
467
699
  // ---------------------------------------------------------------------------
468
700
  evmGetAddress(connectId, deviceId, params) {
469
701
  return this.callChain(connectId, deviceId, "evm", "evmGetAddress", params);
470
702
  }
471
- evmGetAddresses(connectId, deviceId, params, onProgress) {
472
- return this.callChainBatch(
473
- connectId,
474
- deviceId,
475
- "evm",
476
- "evmGetAddress",
477
- params,
478
- onProgress
479
- );
480
- }
481
703
  evmSignTransaction(connectId, deviceId, params) {
482
704
  return this.callChain(connectId, deviceId, "evm", "evmSignTransaction", params);
483
705
  }
@@ -493,16 +715,6 @@ var _LedgerAdapter = class _LedgerAdapter {
493
715
  btcGetAddress(connectId, deviceId, params) {
494
716
  return this.callChain(connectId, deviceId, "btc", "btcGetAddress", params);
495
717
  }
496
- btcGetAddresses(connectId, deviceId, params, onProgress) {
497
- return this.callChainBatch(
498
- connectId,
499
- deviceId,
500
- "btc",
501
- "btcGetAddress",
502
- params,
503
- onProgress
504
- );
505
- }
506
718
  btcGetPublicKey(connectId, deviceId, params) {
507
719
  return this.callChain(connectId, deviceId, "btc", "btcGetPublicKey", params);
508
720
  }
@@ -530,16 +742,6 @@ var _LedgerAdapter = class _LedgerAdapter {
530
742
  solGetAddress(connectId, deviceId, params) {
531
743
  return this.callChain(connectId, deviceId, "sol", "solGetAddress", params);
532
744
  }
533
- solGetAddresses(connectId, deviceId, params, onProgress) {
534
- return this.callChainBatch(
535
- connectId,
536
- deviceId,
537
- "sol",
538
- "solGetAddress",
539
- params,
540
- onProgress
541
- );
542
- }
543
745
  solSignTransaction(connectId, deviceId, params) {
544
746
  return this.callChain(connectId, deviceId, "sol", "solSignTransaction", params);
545
747
  }
@@ -550,38 +752,13 @@ var _LedgerAdapter = class _LedgerAdapter {
550
752
  // TRON chain methods
551
753
  // ---------------------------------------------------------------------------
552
754
  tronGetAddress(connectId, deviceId, params) {
553
- return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params, true);
554
- }
555
- tronGetAddresses(connectId, deviceId, params, onProgress) {
556
- return this.callChainBatch(
557
- connectId,
558
- deviceId,
559
- "tron",
560
- "tronGetAddress",
561
- params,
562
- onProgress,
563
- true
564
- );
755
+ return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params);
565
756
  }
566
757
  tronSignTransaction(connectId, deviceId, params) {
567
- return this.callChain(
568
- connectId,
569
- deviceId,
570
- "tron",
571
- "tronSignTransaction",
572
- params,
573
- true
574
- );
758
+ return this.callChain(connectId, deviceId, "tron", "tronSignTransaction", params);
575
759
  }
576
760
  tronSignMessage(connectId, deviceId, params) {
577
- return this.callChain(
578
- connectId,
579
- deviceId,
580
- "tron",
581
- "tronSignMessage",
582
- params,
583
- true
584
- );
761
+ return this.callChain(connectId, deviceId, "tron", "tronSignMessage", params);
585
762
  }
586
763
  on(event, listener) {
587
764
  this.emitter.on(event, listener);
@@ -590,30 +767,41 @@ var _LedgerAdapter = class _LedgerAdapter {
590
767
  this.emitter.off(event, listener);
591
768
  }
592
769
  cancel(connectId) {
593
- const sessionId = this._sessions.get(connectId) ?? connectId;
594
- this._jobQueue.forceCancelActive(connectId || "__ledger_default__");
595
- void this.connector.cancel(sessionId);
770
+ const userAbortReason = Object.assign(new Error("User aborted operation"), {
771
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
772
+ _tag: ERROR_TAG.UserAborted
773
+ });
774
+ this._lastCancelReason = userAbortReason;
775
+ setTimeout(() => {
776
+ if (this._lastCancelReason === userAbortReason) {
777
+ this._lastCancelReason = void 0;
778
+ }
779
+ }, 2e3);
780
+ this._uiRegistry.cancel();
781
+ if (connectId) {
782
+ this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
783
+ } else {
784
+ this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
785
+ }
786
+ if (connectId) {
787
+ const sessionId = this._sessions.get(connectId) ?? connectId;
788
+ void this.connector.cancel(sessionId);
789
+ } else {
790
+ for (const sid of this._sessions.values()) void this.connector.cancel(sid);
791
+ }
792
+ if (this._connectingPromise) {
793
+ this._doConnectAbortController?.abort(userAbortReason);
794
+ }
596
795
  }
597
796
  // ---------------------------------------------------------------------------
598
797
  // Chain fingerprint
599
798
  // ---------------------------------------------------------------------------
600
799
  async getChainFingerprint(connectId, deviceId, chain) {
601
- debugLog(
602
- "[LedgerAdapter] getChainFingerprint called, chain:",
603
- chain,
604
- "connectId:",
605
- connectId || "(empty)",
606
- "sessions:",
607
- this._sessions.size
608
- );
609
- await this._ensureDevicePermission(connectId, deviceId);
610
- debugLog("[LedgerAdapter] getChainFingerprint permission ok, computing fingerprint");
611
800
  try {
612
801
  const fingerprint = await this._computeChainFingerprint(
613
802
  chain,
614
- (method, params) => this.connectorCall(connectId, method, params)
803
+ (method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
615
804
  );
616
- debugLog("[LedgerAdapter] getChainFingerprint result:", fingerprint?.substring(0, 20));
617
805
  return (0, import_hwk_adapter_core2.success)(fingerprint);
618
806
  } catch (err) {
619
807
  debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
@@ -684,102 +872,252 @@ var _LedgerAdapter = class _LedgerAdapter {
684
872
  if (signal?.aborted) {
685
873
  _LedgerAdapter._throwIfAborted(signal);
686
874
  }
875
+ const waitPromise = this._uiRegistry.wait(
876
+ import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
877
+ );
687
878
  this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
688
879
  type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT,
689
880
  payload: {
881
+ vendor: "ledger",
882
+ reason: "device-not-found",
690
883
  message: "Please connect and unlock your Ledger device"
691
884
  }
692
885
  });
693
- const waitPromise = this._uiRegistry.wait(
694
- import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
695
- );
696
886
  let payload;
697
- if (signal) {
698
- const onAbort = () => {
699
- this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT);
700
- };
701
- signal.addEventListener("abort", onAbort, { once: true });
702
- try {
887
+ try {
888
+ if (signal) {
889
+ const onAbort = () => {
890
+ this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT);
891
+ };
892
+ signal.addEventListener("abort", onAbort, { once: true });
893
+ try {
894
+ payload = await waitPromise;
895
+ } finally {
896
+ signal.removeEventListener("abort", onAbort);
897
+ }
898
+ } else {
703
899
  payload = await waitPromise;
704
- } finally {
705
- signal.removeEventListener("abort", onAbort);
706
900
  }
707
- } else {
708
- payload = await waitPromise;
901
+ } catch (err) {
902
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
903
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
904
+ payload: {}
905
+ });
906
+ throw Object.assign(new Error("User cancelled Ledger connection"), {
907
+ _tag: ERROR_TAG.UserAborted,
908
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
909
+ cause: err
910
+ });
709
911
  }
710
912
  if (!payload?.confirmed) {
711
913
  throw Object.assign(new Error("User cancelled Ledger connection"), {
712
- _tag: "DeviceNotRecognizedError"
914
+ _tag: ERROR_TAG.UserAborted,
915
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
713
916
  });
714
917
  }
918
+ await new Promise((resolve) => {
919
+ setTimeout(resolve, 800);
920
+ });
715
921
  }
716
- async ensureConnected(connectId) {
717
- if (connectId && this._sessions.has(connectId)) {
718
- return connectId;
719
- }
720
- if (this._sessions.size > 0) {
721
- const firstKey = this._sessions.keys().next().value;
722
- return firstKey;
922
+ /**
923
+ * Decide whether a BTC pubkey call needs `showOnDevice=true` because of
924
+ * the BTC App's account-index policy, asking the user once per session.
925
+ *
926
+ * Returns the params to pass through (with `showOnDevice` possibly
927
+ * promoted), or `null` if the user declined.
928
+ */
929
+ async _gateBtcHighIndex(params) {
930
+ const accountIndex = btcAccountIndexFromPath(params.path);
931
+ if (params.showOnDevice) return params;
932
+ if (accountIndex === null || accountIndex < BTC_HIGH_INDEX_THRESHOLD) {
933
+ return params;
723
934
  }
724
- if (this._connectingPromise) {
725
- return this._connectingPromise;
935
+ if (this._btcHighIndexConfirmedThisSession) {
936
+ return { ...params, showOnDevice: true };
726
937
  }
727
- this._connectingPromise = this._doConnect();
938
+ const confirmed = await this._waitForBtcHighIndexConfirm(params.path, accountIndex);
939
+ if (!confirmed) return null;
940
+ this._btcHighIndexConfirmedThisSession = true;
941
+ return { ...params, showOnDevice: true };
942
+ }
943
+ async _waitForBtcHighIndexConfirm(path, accountIndex) {
944
+ const waitPromise = this._uiRegistry.wait(
945
+ import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
946
+ );
947
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
948
+ type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
949
+ payload: {
950
+ vendor: "ledger",
951
+ path,
952
+ accountIndex
953
+ }
954
+ });
728
955
  try {
729
- return await this._connectingPromise;
730
- } finally {
731
- this._connectingPromise = null;
956
+ const payload = await waitPromise;
957
+ return !!payload?.confirmed;
958
+ } catch (err) {
959
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
960
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
961
+ payload: {}
962
+ });
963
+ return false;
732
964
  }
733
965
  }
734
- async _doConnect() {
735
- for (let attempt = 0; attempt < _LedgerAdapter.MAX_DEVICE_RETRY; attempt++) {
736
- const devices = await this.searchDevices();
737
- if (devices.length > 0) {
738
- return this._connectFirstOrSelect(devices);
739
- }
740
- if (attempt < _LedgerAdapter.MAX_DEVICE_RETRY - 1) {
741
- await this._waitForDeviceConnect();
966
+ // Layer 1 entry. Caller signal only races the outer awaiter; the shared
967
+ // `_doConnect` runs under its own internal controller so caller A's cancel
968
+ // doesn't kill caller B's await.
969
+ async ensureConnected(connectId, signal) {
970
+ if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
971
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
972
+ throw Object.assign(new Error("Ledger BLE connectId is required."), {
973
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
974
+ });
975
+ }
976
+ if (connectId && this._sessions.has(connectId)) return connectId;
977
+ if (!connectId && this._sessions.size > 0) {
978
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
979
+ throw Object.assign(
980
+ new Error(
981
+ "Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
982
+ ),
983
+ { code: import_hwk_adapter_core2.HardwareErrorCode.DeviceOneDeviceOnly }
984
+ );
742
985
  }
986
+ return this._sessions.keys().next().value;
743
987
  }
744
- throw Object.assign(
745
- new Error(
746
- "No Ledger device found after multiple attempts. Please connect and unlock your device."
747
- ),
748
- { _tag: "DeviceNotRecognizedError" }
749
- );
988
+ if (!this._connectingPromise) {
989
+ this._doConnectAbortController = new AbortController();
990
+ const innerSignal = this._doConnectAbortController.signal;
991
+ this._connectingPromise = (async () => {
992
+ try {
993
+ return await this._doConnect(innerSignal, connectId);
994
+ } finally {
995
+ this._connectingPromise = null;
996
+ this._doConnectAbortController = null;
997
+ }
998
+ })();
999
+ }
1000
+ return this._abortable(signal, this._connectingPromise);
750
1001
  }
751
- async _connectFirstOrSelect(devices) {
752
- const chosenConnectId = devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices);
753
- const result = await this.connectDevice(chosenConnectId);
754
- if (!result.success) {
755
- throw Object.assign(new Error(result.payload.error), {
756
- _tag: "DeviceNotRecognizedError"
1002
+ // Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
1003
+ // Bounded by MAX_DOCONNECT_CONFIRMS after N Confirms with no progress,
1004
+ // throw DeviceNotFound so the user is kicked out of the loop.
1005
+ async _doConnect(internalSignal, targetConnectId) {
1006
+ if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
1007
+ try {
1008
+ return await this._connectDeviceOrThrow(targetConnectId);
1009
+ } catch (err) {
1010
+ if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
1011
+ throw err;
1012
+ }
1013
+ this._discoveredDevices.delete(targetConnectId);
1014
+ if (isDeviceDisconnectedError(err)) {
1015
+ try {
1016
+ this.connector.reset?.();
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ }
1021
+ }
1022
+ let confirms = 0;
1023
+ while (!internalSignal.aborted) {
1024
+ this.emitter.emit("ui-event", {
1025
+ type: import_hwk_adapter_core2.EConnectorInteraction.Searching,
1026
+ payload: { sessionId: "" }
757
1027
  });
1028
+ let devices = await this.searchDevices();
1029
+ if (devices.length === 0) {
1030
+ for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
1031
+ await new Promise((resolve) => {
1032
+ setTimeout(resolve, 1e3);
1033
+ });
1034
+ devices = await this.searchDevices();
1035
+ if (devices.length > 0) break;
1036
+ }
1037
+ }
1038
+ if (devices.length > 0) {
1039
+ try {
1040
+ return await this._connectFirstOrSelect(devices, targetConnectId);
1041
+ } catch (err) {
1042
+ if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
1043
+ throw err;
1044
+ }
1045
+ this._discoveredDevices.clear();
1046
+ if (isDeviceDisconnectedError(err)) {
1047
+ try {
1048
+ this.connector.reset?.();
1049
+ } catch {
1050
+ }
1051
+ }
1052
+ }
1053
+ }
1054
+ if (confirms >= _LedgerAdapter.MAX_DOCONNECT_CONFIRMS) {
1055
+ throw Object.assign(
1056
+ new Error(
1057
+ "Device not connected after multiple attempts. Please ensure your Ledger is awake, unlocked, and in range, then try again."
1058
+ ),
1059
+ { code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound }
1060
+ );
1061
+ }
1062
+ await this._waitForDeviceConnect(internalSignal);
1063
+ confirms += 1;
758
1064
  }
759
- return chosenConnectId;
1065
+ _LedgerAdapter._throwIfAborted(internalSignal);
1066
+ throw new Error("_doConnect aborted");
760
1067
  }
761
- async _chooseDeviceFromList(devices) {
762
- if (!this._handleSelectDevice) {
1068
+ async _connectFirstOrSelect(devices, targetConnectId) {
1069
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length > 1) {
763
1070
  debugLog(
764
- `[DMK] Multiple Ledger devices found (${devices.length}); handleSelectDevice=false, picking first (${devices[0].connectId}).`
1071
+ `[DMK] Multiple Ledger USB devices found (${devices.length}); refusing to auto-select by ephemeral connectId.`
765
1072
  );
766
- return devices[0].connectId;
1073
+ throw createMultipleUsbLedgerDevicesError();
767
1074
  }
768
- this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE, {
769
- type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE,
770
- payload: { devices }
771
- });
772
- const response = await this._uiRegistry.wait(
773
- import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE
774
- );
775
- const chosen = devices.find((d) => d.connectId === response?.sdkConnectId);
776
- if (!chosen) {
777
- throw Object.assign(
778
- new Error(`Selected sdkConnectId '${response?.sdkConnectId}' not in discovered list`),
779
- { _tag: "DeviceNotRecognizedError" }
1075
+ if (targetConnectId) {
1076
+ const target = devices.find(
1077
+ (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
780
1078
  );
1079
+ if (target) {
1080
+ return this._connectDeviceOrThrow(target.connectId);
1081
+ }
1082
+ if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) {
1083
+ debugLog(
1084
+ `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)`
1085
+ );
1086
+ return this._connectDeviceOrThrow(devices[0].connectId);
1087
+ }
1088
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1089
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1090
+ });
1091
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1092
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
1093
+ }
1094
+ throw err;
1095
+ }
1096
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1097
+ throw Object.assign(new Error("Ledger BLE connectId is required."), {
1098
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1099
+ });
781
1100
  }
782
- return chosen.connectId;
1101
+ if (devices.length !== 1) {
1102
+ throw Object.assign(new Error("Ledger device not found."), {
1103
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1104
+ });
1105
+ }
1106
+ return this._connectDeviceOrThrow(devices[0].connectId);
1107
+ }
1108
+ async _connectDeviceOrThrow(chosenConnectId) {
1109
+ const result = await this.connectDevice(chosenConnectId);
1110
+ if (!result.success) {
1111
+ const payload = result.payload;
1112
+ const rethrow = Object.assign(new Error(payload.error), {
1113
+ code: payload.code
1114
+ });
1115
+ if (payload._tag !== void 0) {
1116
+ rethrow._tag = payload._tag;
1117
+ }
1118
+ throw rethrow;
1119
+ }
1120
+ return chosenConnectId;
783
1121
  }
784
1122
  /**
785
1123
  * Call the connector with automatic session resolution and disconnect retry.
@@ -789,30 +1127,31 @@ var _LedgerAdapter = class _LedgerAdapter {
789
1127
  * 3. Calls connector.call()
790
1128
  * 4. On disconnect error: clears stale session, re-connects, retries once
791
1129
  */
792
- async connectorCall(connectId, method, params, fingerprint) {
1130
+ async connectorCall(connectId, method, params, fingerprint, permissionDeviceId) {
793
1131
  debugLog("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
794
1132
  const queueKey = connectId || "__ledger_default__";
795
- const interruptibility = _LedgerAdapter._getInterruptibility(method);
796
1133
  return this._jobQueue.enqueue(
797
1134
  queueKey,
798
- async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint),
799
- { interruptibility, label: method }
1135
+ async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId),
1136
+ {
1137
+ label: method,
1138
+ rejectIfBusy: true,
1139
+ busyError: _LedgerAdapter._createDeviceBusyError(method)
1140
+ }
800
1141
  );
801
1142
  }
802
1143
  /**
803
- * Race a promise against an abort signal. If the signal fires, rejects with the
804
- * signal's reason (or a generic Error). The underlying connector.call() cannot
805
- * actually be cancelled on Ledger DMK, but the caller gets the abort immediately.
1144
+ * Race a promise against an abort signal. On abort, rejects with
1145
+ * signal.reason instance _lastCancelReason → generic Error('Aborted').
806
1146
  */
807
- static _abortable(signal, promise) {
1147
+ _abortable(signal, promise) {
1148
+ const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
808
1149
  if (signal.aborted) {
809
- return Promise.reject(
810
- signal.reason ?? new Error("Aborted")
811
- );
1150
+ return Promise.reject(getAbortReason());
812
1151
  }
813
1152
  return new Promise((resolve, reject) => {
814
1153
  const onAbort = () => {
815
- reject(signal.reason ?? new Error("Aborted"));
1154
+ reject(getAbortReason());
816
1155
  };
817
1156
  signal.addEventListener("abort", onAbort, { once: true });
818
1157
  promise.then(
@@ -834,40 +1173,49 @@ var _LedgerAdapter = class _LedgerAdapter {
834
1173
  }
835
1174
  }
836
1175
  /** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
837
- async _runConnectorCall(connectId, method, params, signal, fingerprint) {
1176
+ async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId) {
838
1177
  _LedgerAdapter._throwIfAborted(signal);
839
- const resolvedConnectId = await _LedgerAdapter._abortable(
840
- signal,
841
- this.ensureConnected(connectId)
1178
+ await this._ensureDevicePermission(
1179
+ connectId,
1180
+ permissionDeviceId ?? fingerprint?.deviceId,
1181
+ signal
842
1182
  );
843
1183
  _LedgerAdapter._throwIfAborted(signal);
1184
+ let effectiveParams = params;
1185
+ if (method === "btcGetPublicKey") {
1186
+ const gatedParams = await this._gateBtcHighIndex(params);
1187
+ if (gatedParams === null) {
1188
+ throw Object.assign(new Error("User cancelled BTC high-index confirmation"), {
1189
+ _tag: ERROR_TAG.UserAborted,
1190
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
1191
+ });
1192
+ }
1193
+ effectiveParams = gatedParams;
1194
+ }
1195
+ const resolvedConnectId = await this.ensureConnected(connectId, signal);
844
1196
  const sessionId = this._sessions.get(resolvedConnectId);
845
- debugLog(
846
- "[LedgerAdapter] connectorCall resolved:",
847
- method,
848
- "resolvedConnectId:",
849
- resolvedConnectId,
850
- "sessionId:",
851
- sessionId
852
- );
853
1197
  if (!sessionId) {
854
1198
  throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
855
- _tag: "DeviceSessionNotFound"
1199
+ _tag: ERROR_TAG.DeviceSessionNotFound
856
1200
  });
857
1201
  }
858
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
859
- const fp = await _LedgerAdapter._abortable(
860
- signal,
861
- this._verifyDeviceFingerprintWithSession(sessionId, fingerprint.deviceId, fingerprint.chain)
862
- );
863
- if (!fp.success) {
864
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
865
- code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
866
- });
867
- }
868
- }
869
1202
  try {
870
- return await _LedgerAdapter._abortable(signal, this.connector.call(sessionId, method, params));
1203
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1204
+ const fp = await this._abortable(
1205
+ signal,
1206
+ this._verifyDeviceFingerprintWithSession(
1207
+ sessionId,
1208
+ fingerprint.deviceId,
1209
+ fingerprint.chain
1210
+ )
1211
+ );
1212
+ if (!fp.success) {
1213
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1214
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1215
+ });
1216
+ }
1217
+ }
1218
+ return await this._abortable(signal, this.connector.call(sessionId, method, effectiveParams));
871
1219
  } catch (err) {
872
1220
  if (signal.aborted) throw err;
873
1221
  const errObj = err;
@@ -877,37 +1225,241 @@ var _LedgerAdapter = class _LedgerAdapter {
877
1225
  errorCode: errObj?.errorCode,
878
1226
  statusCode: errObj?.statusCode,
879
1227
  isDisconnected: isDeviceDisconnectedError(err),
880
- isLocked: isDeviceLockedError(err)
1228
+ isLocked: isDeviceLockedError(err),
1229
+ isNotAdvertising: isDeviceNotAdvertisingError(err),
1230
+ isStuckApp: isStuckAppStateError(err)
881
1231
  });
882
- if (isDeviceDisconnectedError(err)) {
883
- debugLog("[LedgerAdapter] disconnected, retrying with fresh connection...");
884
- this._discoveredDevices.clear();
885
- return this._retryWithFreshConnection(resolvedConnectId, method, params, signal, err);
1232
+ if (isStuckAppStateError(err)) {
1233
+ try {
1234
+ this._sessions.delete(resolvedConnectId);
1235
+ this._discoveredDevices.delete(resolvedConnectId);
1236
+ this.connector.reset?.();
1237
+ } catch {
1238
+ }
1239
+ debugLog(
1240
+ "[LedgerAdapter] stuck-app retry: method=",
1241
+ method,
1242
+ "delayMs=",
1243
+ _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
1244
+ "_tag=",
1245
+ err?._tag
1246
+ );
1247
+ try {
1248
+ const retryResult = await this._retryAfterStuckApp(
1249
+ resolvedConnectId,
1250
+ method,
1251
+ effectiveParams,
1252
+ signal,
1253
+ err,
1254
+ fingerprint
1255
+ );
1256
+ debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
1257
+ return retryResult;
1258
+ } catch (retryErr) {
1259
+ if (signal.aborted) throw retryErr;
1260
+ if (isStuckAppStateError(retryErr)) {
1261
+ debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
1262
+ throw err;
1263
+ }
1264
+ debugLog(
1265
+ "[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
1266
+ method,
1267
+ "retryErrTag=",
1268
+ retryErr?._tag
1269
+ );
1270
+ throw retryErr;
1271
+ }
886
1272
  }
887
- if (isDeviceLockedError(err)) {
888
- await this._waitForDeviceConnect(signal);
889
- _LedgerAdapter._throwIfAborted(signal);
890
- return _LedgerAdapter._abortable(signal, this.connector.call(sessionId, method, params));
1273
+ if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
1274
+ let lastErr = err;
1275
+ for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
1276
+ if (signal.aborted) throw lastErr;
1277
+ try {
1278
+ this._sessions.delete(resolvedConnectId);
1279
+ this._discoveredDevices.delete(resolvedConnectId);
1280
+ if (isDeviceDisconnectedError(lastErr)) {
1281
+ try {
1282
+ this.connector.reset?.();
1283
+ } catch {
1284
+ }
1285
+ }
1286
+ const reConnectId = await this.ensureConnected(resolvedConnectId, signal);
1287
+ const reSessionId = this._sessions.get(reConnectId);
1288
+ if (!reSessionId) throw lastErr;
1289
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1290
+ const fp = await this._abortable(
1291
+ signal,
1292
+ this._verifyDeviceFingerprintWithSession(
1293
+ reSessionId,
1294
+ fingerprint.deviceId,
1295
+ fingerprint.chain
1296
+ )
1297
+ );
1298
+ if (!fp.success) {
1299
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1300
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1301
+ });
1302
+ }
1303
+ }
1304
+ return await this._abortable(
1305
+ signal,
1306
+ this.connector.call(reSessionId, method, effectiveParams)
1307
+ );
1308
+ } catch (retryErr) {
1309
+ if (signal.aborted) throw retryErr;
1310
+ lastErr = retryErr;
1311
+ const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
1312
+ if (!canRetry) {
1313
+ throw retryErr;
1314
+ }
1315
+ }
1316
+ }
1317
+ throw lastErr;
891
1318
  }
892
1319
  if (isTimeoutError(err)) {
893
1320
  debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
894
1321
  this._discoveredDevices.delete(resolvedConnectId);
895
- return this._retryWithFreshConnection(resolvedConnectId, method, params, signal, err);
1322
+ return this._retryWithFreshConnection(
1323
+ resolvedConnectId,
1324
+ method,
1325
+ effectiveParams,
1326
+ signal,
1327
+ err,
1328
+ fingerprint
1329
+ );
1330
+ }
1331
+ if (isConnectionLevelError(err)) {
1332
+ debugLog("[LedgerAdapter] connection-level fail-closed reset");
1333
+ this._sessions.delete(resolvedConnectId);
1334
+ this._discoveredDevices.delete(resolvedConnectId);
1335
+ this.connector.reset();
1336
+ const codeNum = err?.code;
1337
+ throw Object.assign(err, {
1338
+ code: codeNum ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceDisconnected
1339
+ });
896
1340
  }
897
1341
  throw err;
898
1342
  }
899
1343
  }
900
- /** Clear stale session, reconnect, and retry the call. */
901
- async _retryWithFreshConnection(resolvedConnectId, method, params, signal, originalErr) {
902
- this._sessions.delete(resolvedConnectId);
903
- _LedgerAdapter._throwIfAborted(signal);
904
- const retryConnectId = await this.ensureConnected();
905
- _LedgerAdapter._throwIfAborted(signal);
1344
+ /**
1345
+ * Stuck-app recovery: pause for the device's UI transition, then retry once.
1346
+ *
1347
+ * Caller has already cleared the session + reset connector. We wait so Stax
1348
+ * finishes its post-CloseApp animation, then go through ensureConnected +
1349
+ * fingerprint check + call exactly once. Caller decides what to do on a
1350
+ * second stuck-app hit.
1351
+ */
1352
+ async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
1353
+ await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
1354
+ const retryConnectId = await this.ensureConnected(resolvedConnectId, signal);
1355
+ const retrySessionId = this._sessions.get(retryConnectId);
1356
+ if (!retrySessionId) throw originalErr;
1357
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1358
+ const fp = await this._abortable(
1359
+ signal,
1360
+ this._verifyDeviceFingerprintWithSession(
1361
+ retrySessionId,
1362
+ fingerprint.deviceId,
1363
+ fingerprint.chain
1364
+ )
1365
+ );
1366
+ if (!fp.success) {
1367
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1368
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1369
+ });
1370
+ }
1371
+ }
1372
+ return this._abortable(signal, this.connector.call(retrySessionId, method, params));
1373
+ }
1374
+ _sleepAbortable(ms, signal) {
1375
+ return new Promise((resolve, reject) => {
1376
+ if (signal.aborted) {
1377
+ reject(signal.reason ?? new Error("Aborted"));
1378
+ return;
1379
+ }
1380
+ const timer = setTimeout(() => {
1381
+ signal.removeEventListener("abort", onAbort);
1382
+ resolve();
1383
+ }, ms);
1384
+ const onAbort = () => {
1385
+ clearTimeout(timer);
1386
+ reject(signal.reason ?? new Error("Aborted"));
1387
+ };
1388
+ signal.addEventListener("abort", onAbort, { once: true });
1389
+ });
1390
+ }
1391
+ /**
1392
+ * Clear stale session, reconnect, and retry the call.
1393
+ *
1394
+ * Timeout recovery starts with a full connector reset. After an APDU
1395
+ * timeout, DMK/transport state may still emit malformed responses; retrying
1396
+ * on the same DMK can poison the next chain switch.
1397
+ */
1398
+ async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
1399
+ this.connector.reset();
1400
+ this._sessions.clear();
1401
+ this._discoveredDevices.clear();
1402
+ this._connectingPromise = null;
1403
+ const retryConnectId = await this.ensureConnected(targetConnectId, signal);
906
1404
  const retrySessionId = this._sessions.get(retryConnectId);
907
1405
  if (!retrySessionId) {
908
1406
  throw originalErr;
909
1407
  }
910
- return _LedgerAdapter._abortable(signal, this.connector.call(retrySessionId, method, params));
1408
+ try {
1409
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1410
+ const fp = await this._abortable(
1411
+ signal,
1412
+ this._verifyDeviceFingerprintWithSession(
1413
+ retrySessionId,
1414
+ fingerprint.deviceId,
1415
+ fingerprint.chain
1416
+ )
1417
+ );
1418
+ if (!fp.success) {
1419
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1420
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1421
+ });
1422
+ }
1423
+ }
1424
+ return await this._abortable(signal, this.connector.call(retrySessionId, method, params));
1425
+ } catch (retryErr) {
1426
+ if (signal.aborted) throw retryErr;
1427
+ this.connector.reset();
1428
+ this._sessions.clear();
1429
+ this._discoveredDevices.clear();
1430
+ this._connectingPromise = null;
1431
+ if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
1432
+ throw retryErr;
1433
+ }
1434
+ debugLog(
1435
+ "[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
1436
+ );
1437
+ this.connector.reset();
1438
+ this._sessions.clear();
1439
+ this._discoveredDevices.clear();
1440
+ this._connectingPromise = null;
1441
+ const finalConnectId = await this.ensureConnected(targetConnectId, signal);
1442
+ const finalSessionId = this._sessions.get(finalConnectId);
1443
+ if (!finalSessionId) {
1444
+ throw originalErr;
1445
+ }
1446
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1447
+ const fp = await this._abortable(
1448
+ signal,
1449
+ this._verifyDeviceFingerprintWithSession(
1450
+ finalSessionId,
1451
+ fingerprint.deviceId,
1452
+ fingerprint.chain
1453
+ )
1454
+ );
1455
+ if (!fp.success) {
1456
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1457
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1458
+ });
1459
+ }
1460
+ }
1461
+ return this._abortable(signal, this.connector.call(finalSessionId, method, params));
1462
+ }
911
1463
  }
912
1464
  /**
913
1465
  * Ensure OS-level device permission (Bluetooth / USB) before proceeding.
@@ -921,7 +1473,10 @@ var _LedgerAdapter = class _LedgerAdapter {
921
1473
  * - No connectId (searchDevices): environment-level permission
922
1474
  * - With connectId (business methods): device-level permission
923
1475
  */
924
- async _ensureDevicePermission(connectId, deviceId) {
1476
+ async _ensureDevicePermission(connectId, deviceId, signal) {
1477
+ if (signal?.aborted) {
1478
+ _LedgerAdapter._throwIfAborted(signal);
1479
+ }
925
1480
  const transportType = this.activeTransport ?? "hid";
926
1481
  const waitPromise = this._uiRegistry.wait(
927
1482
  import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
@@ -931,10 +1486,37 @@ var _LedgerAdapter = class _LedgerAdapter {
931
1486
  type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
932
1487
  payload: { transportType, connectId, deviceId }
933
1488
  });
934
- const { granted } = await waitPromise;
1489
+ let response;
1490
+ const onAbort = () => {
1491
+ this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION);
1492
+ };
1493
+ try {
1494
+ signal?.addEventListener("abort", onAbort, { once: true });
1495
+ response = await waitPromise;
1496
+ } catch (err) {
1497
+ if (signal?.aborted) {
1498
+ _LedgerAdapter._throwIfAborted(signal);
1499
+ }
1500
+ if (err?._tag === import_hwk_adapter_core2.UI_REQUEST_PREEMPTED_TAG) {
1501
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
1502
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
1503
+ payload: {}
1504
+ });
1505
+ throw Object.assign(new Error("Device permission request superseded"), {
1506
+ _tag: ERROR_TAG.UserAborted,
1507
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
1508
+ cause: err
1509
+ });
1510
+ }
1511
+ throw err;
1512
+ } finally {
1513
+ signal?.removeEventListener("abort", onAbort);
1514
+ }
1515
+ const { granted, reason, message } = response;
935
1516
  if (!granted) {
936
- throw Object.assign(new Error("Device permission denied"), {
937
- code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied
1517
+ throw Object.assign(new Error(message ?? "Device permission denied"), {
1518
+ code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied,
1519
+ reason
938
1520
  });
939
1521
  }
940
1522
  }
@@ -944,89 +1526,61 @@ var _LedgerAdapter = class _LedgerAdapter {
944
1526
  */
945
1527
  errorToFailure(err) {
946
1528
  debugError("[LedgerAdapter] error:", err);
1529
+ const tag = err && typeof err === "object" ? err._tag : void 0;
947
1530
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
948
1531
  const e = err;
949
- return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName);
1532
+ const params = e.code === import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : void 0;
1533
+ return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
950
1534
  }
951
1535
  const mapped = mapLedgerError(err);
952
- return ledgerFailure(mapped.code, mapped.message, mapped.appName);
1536
+ return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
953
1537
  }
954
1538
  registerEventListeners() {
955
1539
  this.connector.on("device-connect", this.deviceConnectHandler);
956
1540
  this.connector.on("device-disconnect", this.deviceDisconnectHandler);
957
- this.connector.on("ui-request", this.uiRequestHandler);
958
- this.connector.on("ui-event", this.uiEventHandler);
1541
+ this.connector.on("ui-event", this.uiEventForwarder);
959
1542
  }
960
1543
  unregisterEventListeners() {
961
1544
  this.connector.off("device-connect", this.deviceConnectHandler);
962
1545
  this.connector.off("device-disconnect", this.deviceDisconnectHandler);
963
- this.connector.off("ui-request", this.uiRequestHandler);
964
- this.connector.off("ui-event", this.uiEventHandler);
965
- }
966
- handleUiEvent(event) {
967
- if (!event.type) return;
968
- const payload = event.payload;
969
- const deviceInfo = payload ? this.extractDeviceInfoFromPayload(payload) : this.unknownDevice();
970
- switch (event.type) {
971
- case "ui-request_confirmation":
972
- this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_BUTTON, {
973
- type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_BUTTON,
974
- payload: { device: deviceInfo }
975
- });
976
- break;
977
- }
1546
+ this.connector.off("ui-event", this.uiEventForwarder);
978
1547
  }
979
1548
  // ---------------------------------------------------------------------------
980
1549
  // Device info mapping
981
1550
  // ---------------------------------------------------------------------------
982
1551
  connectorDeviceToDeviceInfo(device) {
983
- const isBle = device.connectId && /^[0-9A-Fa-f]{4}$/.test(device.connectId);
984
1552
  return {
985
1553
  vendor: "ledger",
986
1554
  model: device.model ?? "unknown",
1555
+ modelName: device.modelName,
987
1556
  firmwareVersion: "",
988
1557
  deviceId: device.deviceId,
989
1558
  connectId: device.connectId,
990
1559
  label: device.name,
991
- connectionType: isBle ? "ble" : "usb",
1560
+ connectionType: this.connector.connectionType,
1561
+ rssi: device.rssi,
1562
+ isConnectable: device.isConnectable,
1563
+ serialNumber: device.serialNumber,
992
1564
  capabilities: device.capabilities
993
1565
  };
994
1566
  }
995
- extractDeviceInfoFromPayload(payload) {
996
- return {
997
- vendor: "ledger",
998
- model: payload.model ?? "unknown",
999
- firmwareVersion: "",
1000
- deviceId: payload.deviceId ?? payload.id ?? "",
1001
- connectId: payload.connectId ?? payload.path ?? "",
1002
- label: payload.label,
1003
- connectionType: "usb"
1004
- };
1005
- }
1006
- unknownDevice() {
1007
- return {
1008
- vendor: "ledger",
1009
- model: "unknown",
1010
- firmwareVersion: "",
1011
- deviceId: "",
1012
- connectId: "",
1013
- connectionType: "usb"
1014
- };
1015
- }
1016
1567
  };
1017
- // ---------------------------------------------------------------------------
1018
- // Private helpers
1019
- // ---------------------------------------------------------------------------
1020
- /**
1021
- * Ensure at least one device is connected and return a valid connectId.
1022
- *
1023
- * - If a session already exists for the given connectId, reuse it.
1024
- * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
1025
- * - Otherwise: search 1 device: auto-connect, multiple: ask user, 0: throw.
1026
- */
1027
- _LedgerAdapter.MAX_DEVICE_RETRY = 3;
1568
+ // Layer 2 retry budget after connection-class error. Each round delegates
1569
+ // to Layer 1 (which owns the unlock prompt). Layer 2 never emits UI itself.
1570
+ _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET = 3;
1571
+ // Stuck-app (APDU 0x6901) retry pause. Ledger Stax often returns 6901 when
1572
+ // OpenAppCommand lands mid-transition right after CloseApp; a short wait
1573
+ // lets the device finish its UI animation before we retry once.
1574
+ _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS = 500;
1575
+ // Layer 1 confirm budget. After N failed Confirm cycles (user keeps clicking
1576
+ // Confirm but device never shows up / never unlocks), throw DeviceNotFound
1577
+ // instead of looping forever.
1578
+ _LedgerAdapter.MAX_DOCONNECT_CONFIRMS = 3;
1028
1579
  var LedgerAdapter = _LedgerAdapter;
1029
1580
 
1581
+ // src/connector/LedgerConnectorBase.ts
1582
+ var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
1583
+
1030
1584
  // src/device/LedgerDeviceManager.ts
1031
1585
  var LedgerDeviceManager = class {
1032
1586
  constructor(dmk) {
@@ -1063,7 +1617,9 @@ var LedgerDeviceManager = class {
1063
1617
  if (resolved) return;
1064
1618
  resolved = true;
1065
1619
  this._discovered.clear();
1066
- debugLog("[DMK] enumerate raw devices:", devices.length);
1620
+ debugLog(
1621
+ `[DMK] enumerate count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
1622
+ );
1067
1623
  for (const d of devices) {
1068
1624
  this._discovered.set(d.id, d);
1069
1625
  }
@@ -1073,8 +1629,10 @@ var LedgerDeviceManager = class {
1073
1629
  devices.map((d) => ({
1074
1630
  path: d.id,
1075
1631
  type: d.deviceModel.model,
1632
+ modelName: d.deviceModel.name,
1076
1633
  name: d.name,
1077
- transport: d.transport
1634
+ transport: d.transport,
1635
+ rssi: d.rssi
1078
1636
  }))
1079
1637
  );
1080
1638
  } else {
@@ -1095,8 +1653,10 @@ var LedgerDeviceManager = class {
1095
1653
  devices.map((d) => ({
1096
1654
  path: d.id,
1097
1655
  type: d.deviceModel.model,
1656
+ modelName: d.deviceModel.name,
1098
1657
  name: d.name,
1099
- transport: d.transport
1658
+ transport: d.transport,
1659
+ rssi: d.rssi
1100
1660
  }))
1101
1661
  );
1102
1662
  }
@@ -1120,8 +1680,10 @@ var LedgerDeviceManager = class {
1120
1680
  descriptor: {
1121
1681
  path: d.id,
1122
1682
  type: d.deviceModel.model,
1683
+ modelName: d.deviceModel.name,
1123
1684
  name: d.name,
1124
- transport: d.transport
1685
+ transport: d.transport,
1686
+ rssi: d.rssi
1125
1687
  }
1126
1688
  });
1127
1689
  }
@@ -1136,10 +1698,58 @@ var LedgerDeviceManager = class {
1136
1698
  }
1137
1699
  });
1138
1700
  }
1139
- stopListening() {
1140
- this._listenSub?.unsubscribe();
1141
- this._listenSub = null;
1142
- }
1701
+ stopListening() {
1702
+ this._listenSub?.unsubscribe();
1703
+ this._listenSub = null;
1704
+ }
1705
+ /**
1706
+ * Resolve to the latest snapshot of advertising devices observed within
1707
+ * `timeoutMs`. Rewrites `_discovered` so it tracks the live list.
1708
+ *
1709
+ * `listenToAvailableDevices` is a BehaviorSubject — it emits the cached
1710
+ * list on subscribe and *only* re-emits when the list changes. A stable
1711
+ * list of currently-advertising devices therefore produces only the
1712
+ * subscription frame; treating that frame as "fossil to be discarded"
1713
+ * caused devices to flicker in/out of the UI as searches alternated
1714
+ * between "saw a change frame" and "saw only the cached frame".
1715
+ *
1716
+ * The window still gives a chance for the active scan to update the list
1717
+ * (e.g. drop a peripheral that just stopped broadcasting), but if nothing
1718
+ * changes we trust the snapshot — DMK silence on a stable list means
1719
+ * "everything's still there".
1720
+ */
1721
+ getLiveDevices(timeoutMs = 1500) {
1722
+ debugLog(`[DMK] getLiveDevices() called, timeoutMs=${timeoutMs}`);
1723
+ void this.requestDevice();
1724
+ return new Promise((resolve) => {
1725
+ let done = false;
1726
+ let lastSeen = [];
1727
+ let sub = null;
1728
+ const finish = (devices) => {
1729
+ if (done) return;
1730
+ done = true;
1731
+ sub?.unsubscribe();
1732
+ clearTimeout(timer);
1733
+ this._discovered.clear();
1734
+ for (const d of devices) this._discovered.set(d.id, d);
1735
+ debugLog(
1736
+ `[DMK] getLiveDevices() resolved count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
1737
+ );
1738
+ resolve(devices);
1739
+ };
1740
+ const timer = setTimeout(() => finish(lastSeen), timeoutMs);
1741
+ sub = this._dmk.listenToAvailableDevices({}).subscribe({
1742
+ next: (devices) => {
1743
+ lastSeen = devices;
1744
+ },
1745
+ error: (err) => {
1746
+ debugError("[DMK] getLiveDevices() error:", err);
1747
+ finish(lastSeen);
1748
+ },
1749
+ complete: () => finish(lastSeen)
1750
+ });
1751
+ });
1752
+ }
1143
1753
  requestDevice() {
1144
1754
  if (this._discoverySub) {
1145
1755
  return Promise.resolve();
@@ -1157,11 +1767,33 @@ var LedgerDeviceManager = class {
1157
1767
  });
1158
1768
  return Promise.resolve();
1159
1769
  }
1770
+ /** Lookup a previously-discovered device's display name. */
1771
+ getDeviceName(deviceId) {
1772
+ return this._discovered.get(deviceId)?.name;
1773
+ }
1774
+ /** Lookup minimal model/signal info from a previously-discovered device. */
1775
+ getDiscoveredDeviceInfo(deviceId) {
1776
+ const d = this._discovered.get(deviceId);
1777
+ if (!d) return void 0;
1778
+ return {
1779
+ model: d.deviceModel?.model,
1780
+ modelName: d.deviceModel?.name,
1781
+ name: d.name,
1782
+ rssi: d.rssi
1783
+ };
1784
+ }
1785
+ hasDiscoveredDevice(deviceId) {
1786
+ return this._discovered.has(deviceId);
1787
+ }
1160
1788
  /** Connect to a previously discovered device. Returns sessionId. */
1161
1789
  async connect(deviceId) {
1162
1790
  const device = this._discovered.get(deviceId);
1163
1791
  if (!device) {
1164
- throw new Error(`Device "${deviceId}" not found. Call enumerate() or listen() first.`);
1792
+ const err = new Error(
1793
+ `Device "${deviceId}" not found in discovery cache. Call enumerate() or listen() first.`
1794
+ );
1795
+ err._tag = ERROR_TAG.DeviceNotInDiscoveryCache;
1796
+ throw err;
1165
1797
  }
1166
1798
  const sessionId = await this._dmk.connect({ device });
1167
1799
  this._sessions.set(deviceId, sessionId);
@@ -1200,6 +1832,19 @@ var LedgerDeviceManager = class {
1200
1832
  this._sessionToDevice.clear();
1201
1833
  this._dmk.close();
1202
1834
  }
1835
+ /**
1836
+ * Tear down RxJS subscriptions and local state without closing the DMK.
1837
+ * For light-reset paths that want to drop session state but keep the
1838
+ * underlying transport alive for retry. Call this instead of orphaning
1839
+ * the manager — bare null assignment leaks _listenSub / _discoverySub.
1840
+ */
1841
+ disposeKeepingDmk() {
1842
+ this.stopListening();
1843
+ this.stopDiscovery();
1844
+ this._discovered.clear();
1845
+ this._sessions.clear();
1846
+ this._sessionToDevice.clear();
1847
+ }
1203
1848
  };
1204
1849
 
1205
1850
  // src/signer/SignerManager.ts
@@ -1211,11 +1856,12 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
1211
1856
 
1212
1857
  // src/signer/deviceActionToPromise.ts
1213
1858
  var import_device_management_kit = require("@ledgerhq/device-management-kit");
1214
- var DEFAULT_TIMEOUT_MS = 3e4;
1215
- function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOUT_MS, onRegisterCanceller) {
1859
+ var IDLE_WATCHDOG_MS = 65e3;
1860
+ function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller) {
1216
1861
  return new Promise((resolve, reject) => {
1217
1862
  let settled = false;
1218
1863
  let lastStep;
1864
+ const observedSteps = [];
1219
1865
  let sub;
1220
1866
  let timer = null;
1221
1867
  const cancelAction = () => {
@@ -1228,44 +1874,59 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1228
1874
  } catch {
1229
1875
  }
1230
1876
  };
1231
- const resetTimer = () => {
1877
+ const armIdleWatchdog = () => {
1232
1878
  if (timer) clearTimeout(timer);
1233
1879
  if (timeoutMs > 0) {
1234
1880
  timer = setTimeout(() => {
1235
1881
  if (!settled) {
1236
1882
  settled = true;
1237
1883
  cancelAction();
1238
- reject(new Error("Device action timed out \u2014 device may be locked or disconnected"));
1884
+ reject(
1885
+ new Error(
1886
+ "Ledger transport unresponsive: no DMK state change within the watchdog window. The device may have lost connection."
1887
+ )
1888
+ );
1239
1889
  }
1240
1890
  }, timeoutMs);
1241
1891
  }
1242
1892
  };
1243
- resetTimer();
1893
+ armIdleWatchdog();
1244
1894
  if (onRegisterCanceller) {
1245
- onRegisterCanceller(() => {
1246
- if (settled) return;
1895
+ onRegisterCanceller((reason) => {
1896
+ if (settled) {
1897
+ return;
1898
+ }
1247
1899
  settled = true;
1248
1900
  if (timer) clearTimeout(timer);
1249
1901
  cancelAction();
1250
- reject(new Error("Device action cancelled"));
1902
+ const err = new Error(reason?.message ?? "Device action cancelled");
1903
+ if (reason?.code !== void 0) {
1904
+ err.code = reason.code;
1905
+ }
1906
+ if (reason?.tag) {
1907
+ Object.defineProperty(err, "_tag", {
1908
+ value: reason.tag,
1909
+ configurable: true,
1910
+ enumerable: false,
1911
+ writable: true
1912
+ });
1913
+ }
1914
+ reject(err);
1251
1915
  });
1252
1916
  }
1253
- debugLog("[DMK-Observable] subscribing to action.observable...");
1254
1917
  sub = action.observable.subscribe({
1255
1918
  next: (state) => {
1256
- if (settled) return;
1257
- resetTimer();
1919
+ if (settled) {
1920
+ return;
1921
+ }
1922
+ armIdleWatchdog();
1258
1923
  const step = state?.intermediateValue?.step;
1259
- if (step) lastStep = step;
1260
- debugLog(
1261
- "[DMK-Observable] state:",
1262
- JSON.stringify({
1263
- status: state.status,
1264
- intermediateValue: state.status === import_device_management_kit.DeviceActionStatus.Pending ? state.intermediateValue : void 0,
1265
- hasOutput: state.status === import_device_management_kit.DeviceActionStatus.Completed,
1266
- hasError: state.status === import_device_management_kit.DeviceActionStatus.Error
1267
- })
1268
- );
1924
+ if (step) {
1925
+ lastStep = step;
1926
+ if (observedSteps[observedSteps.length - 1] !== step) {
1927
+ observedSteps.push(step);
1928
+ }
1929
+ }
1269
1930
  if (state.status === import_device_management_kit.DeviceActionStatus.Completed) {
1270
1931
  settled = true;
1271
1932
  if (timer) clearTimeout(timer);
@@ -1277,10 +1938,14 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1277
1938
  if (timer) clearTimeout(timer);
1278
1939
  onInteraction?.("interaction-complete");
1279
1940
  sub?.unsubscribe();
1280
- rejectWithLastStep(state.error, lastStep, reject);
1941
+ rejectWithStepContext(state.error, lastStep, observedSteps, reject);
1281
1942
  } else if (state.status === import_device_management_kit.DeviceActionStatus.Pending && onInteraction) {
1282
1943
  const interaction = state.intermediateValue?.requiredUserInteraction;
1283
1944
  if (interaction && interaction !== "none") {
1945
+ if (interaction === "unlock-device" && timer) {
1946
+ clearTimeout(timer);
1947
+ timer = null;
1948
+ }
1284
1949
  onInteraction(String(interaction));
1285
1950
  }
1286
1951
  }
@@ -1290,7 +1955,7 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1290
1955
  settled = true;
1291
1956
  if (timer) clearTimeout(timer);
1292
1957
  sub?.unsubscribe();
1293
- rejectWithLastStep(err, lastStep, reject);
1958
+ rejectWithStepContext(err, lastStep, observedSteps, reject);
1294
1959
  }
1295
1960
  },
1296
1961
  complete: () => {
@@ -1303,15 +1968,25 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1303
1968
  });
1304
1969
  });
1305
1970
  }
1306
- function rejectWithLastStep(err, lastStep, reject) {
1307
- if (err && typeof err === "object" && lastStep) {
1971
+ function rejectWithStepContext(err, lastStep, observedSteps, reject) {
1972
+ if (err && typeof err === "object" && (lastStep || observedSteps.length > 0)) {
1308
1973
  try {
1309
- Object.defineProperty(err, "_lastStep", {
1310
- value: lastStep,
1311
- configurable: true,
1312
- enumerable: false,
1313
- writable: true
1314
- });
1974
+ if (lastStep) {
1975
+ Object.defineProperty(err, "_lastStep", {
1976
+ value: lastStep,
1977
+ configurable: true,
1978
+ enumerable: false,
1979
+ writable: true
1980
+ });
1981
+ }
1982
+ if (observedSteps.length > 0) {
1983
+ Object.defineProperty(err, "_deviceActionSteps", {
1984
+ value: [...observedSteps],
1985
+ configurable: true,
1986
+ enumerable: false,
1987
+ writable: true
1988
+ });
1989
+ }
1315
1990
  } catch {
1316
1991
  }
1317
1992
  }
@@ -1377,7 +2052,8 @@ var SignerManager = class _SignerManager {
1377
2052
  async getOrCreate(sessionId) {
1378
2053
  debugLog("[DMK] SignerManager.getOrCreate:", { sessionId });
1379
2054
  const builder = await this._builderFn({ dmk: this._dmk, sessionId });
1380
- return new SignerEth(builder.build());
2055
+ const builderWithContext = builder.withContextModule ? builder.withContextModule(_SignerManager._createContextModule()) : builder;
2056
+ return new SignerEth(builderWithContext.build());
1381
2057
  }
1382
2058
  // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this
1383
2059
  invalidate(_sessionId) {
@@ -1386,10 +2062,24 @@ var SignerManager = class _SignerManager {
1386
2062
  clearAll() {
1387
2063
  }
1388
2064
  static _defaultBuilder() {
1389
- return (args) => {
1390
- const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
1391
- return new import_device_signer_kit_ethereum.SignerEthBuilder(args).withContextModule(contextModule);
2065
+ return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
2066
+ }
2067
+ static _createContextModule() {
2068
+ const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
2069
+ return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
2070
+ }
2071
+ static wrapBlindSigningReportNonBlocking(contextModule) {
2072
+ const report = contextModule.report.bind(contextModule);
2073
+ contextModule.report = async (params) => {
2074
+ try {
2075
+ void report(params).catch((err) => {
2076
+ debugLog("[DMK] ContextModule.report failed:", err);
2077
+ });
2078
+ } catch (err) {
2079
+ debugLog("[DMK] ContextModule.report failed:", err);
2080
+ }
1392
2081
  };
2082
+ return contextModule;
1393
2083
  }
1394
2084
  };
1395
2085
 
@@ -1499,6 +2189,7 @@ async function _getEthSigner(ctx, sessionId) {
1499
2189
  const signerManager = await ctx.getSignerManager();
1500
2190
  const signer = await signerManager.getOrCreate(sessionId);
1501
2191
  signer.onInteraction = (interaction) => {
2192
+ debugLog("[LedgerConnector] evm.onInteraction:", interaction);
1502
2193
  ctx.emit("ui-event", {
1503
2194
  type: collapseSignerInteraction(interaction),
1504
2195
  payload: { sessionId }
@@ -1699,6 +2390,7 @@ async function btcSignTransaction(ctx, sessionId, params) {
1699
2390
  const path = normalizePath(params.path || "84'/0'/0'");
1700
2391
  const purpose = path.split("/")[0]?.replace(/'/g, "");
1701
2392
  const template = _purposeToTemplate(purpose, DefaultDescriptorTemplate);
2393
+ debugLog("[LedgerConnector] btcSignTransaction wallet:", { path, purpose, template });
1702
2394
  const wallet = new DefaultWallet(path, template);
1703
2395
  const signedTxHex = await btcSigner.signTransaction(wallet, params.psbt);
1704
2396
  return { serializedTx: (0, import_hwk_adapter_core7.stripHex)(signedTxHex) };
@@ -1723,8 +2415,10 @@ async function btcSignPsbt(ctx, sessionId, params) {
1723
2415
  const path = normalizePath(params.path || "84'/0'/0'");
1724
2416
  const purpose = path.split("/")[0]?.replace(/'/g, "");
1725
2417
  const template = _purposeToTemplate(purpose, DefaultDescriptorTemplate);
2418
+ debugLog("[LedgerConnector] btcSignPsbt wallet:", { path, purpose, template });
1726
2419
  const wallet = new DefaultWallet(path, template);
1727
2420
  const signatures = await btcSigner.signPsbt(wallet, params.psbt);
2421
+ debugLog("[LedgerConnector] btcSignPsbt signatures received:", signatures.length);
1728
2422
  const signedPsbtHex = _applySignaturesToPsbt(params.psbt, signatures);
1729
2423
  return { signedPsbt: signedPsbtHex };
1730
2424
  } catch (err) {
@@ -1771,6 +2465,7 @@ async function _createBtcSigner(ctx, sessionId) {
1771
2465
  const sdkSigner = new SignerBtcBuilder({ dmk, sessionId }).build();
1772
2466
  const signer = new SignerBtc(sdkSigner);
1773
2467
  signer.onInteraction = (interaction) => {
2468
+ debugLog("[LedgerConnector] btc.onInteraction:", interaction);
1774
2469
  ctx.emit("ui-event", {
1775
2470
  type: collapseSignerInteraction(interaction),
1776
2471
  payload: { sessionId }
@@ -1941,6 +2636,7 @@ async function _createSolSigner(ctx, sessionId) {
1941
2636
  const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
1942
2637
  const signer = new SignerSol(sdkSigner);
1943
2638
  signer.onInteraction = (interaction) => {
2639
+ debugLog("[LedgerConnector] sol.onInteraction:", interaction);
1944
2640
  ctx.emit("ui-event", {
1945
2641
  type: collapseSignerInteraction(interaction),
1946
2642
  payload: { sessionId }
@@ -1953,14 +2649,15 @@ async function _createSolSigner(ctx, sessionId) {
1953
2649
  }
1954
2650
 
1955
2651
  // src/connector/chains/tron.ts
1956
- var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
2652
+ var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
1957
2653
  var import_hw_app_trx = __toESM(require("@ledgerhq/hw-app-trx"));
1958
2654
 
1959
- // src/connector/chains/legacyAppRetry.ts
1960
- var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
2655
+ // src/connector/chains/legacyChainCall.ts
2656
+ var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
1961
2657
 
1962
2658
  // src/app/AppManager.ts
1963
2659
  var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
2660
+ var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
1964
2661
  var APP_NAME_MAP = {
1965
2662
  ETH: "Ethereum",
1966
2663
  BTC: "Bitcoin",
@@ -1996,9 +2693,16 @@ var AppManager = class {
1996
2693
  * 5. Poll until the device confirms the target app is running.
1997
2694
  */
1998
2695
  /**
1999
- * @param onConfirmOnDevice Called after OpenAppCommand succeeds
2000
- * the device is now showing a confirmation prompt to the user.
2001
- * NOT called if the app is already open or if the app is not installed.
2696
+ * @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued the
2697
+ * device is about to display "Open <app>" on screen and wait for the
2698
+ * user's button press. UI consumers should show their "open app" prompt
2699
+ * in response. NOT called when the target app is already open (no user
2700
+ * interaction needed in that case).
2701
+ *
2702
+ * Important: OpenAppCommand is blocking. It does not resolve until the user
2703
+ * has physically confirmed on the device, so anything that runs AFTER
2704
+ * `await this._openApp(...)` lands AFTER the prompt is already gone.
2705
+ * Hence the callback must fire BEFORE that await.
2002
2706
  */
2003
2707
  async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
2004
2708
  const currentApp = await this._getCurrentApp(sessionId);
@@ -2009,8 +2713,8 @@ var AppManager = class {
2009
2713
  await this._closeCurrentApp(sessionId);
2010
2714
  await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
2011
2715
  }
2012
- await this._openApp(sessionId, targetAppName);
2013
2716
  onConfirmOnDevice?.();
2717
+ await this._openApp(sessionId, targetAppName);
2014
2718
  await this._waitForApp(sessionId, targetAppName);
2015
2719
  }
2016
2720
  // ---------------------------------------------------------------------------
@@ -2022,9 +2726,34 @@ var AppManager = class {
2022
2726
  command: new import_device_management_kit2.GetAppAndVersionCommand()
2023
2727
  });
2024
2728
  if ((0, import_device_management_kit2.isSuccessCommandResult)(result)) {
2729
+ debugLog("[AppManager] currentApp:", result.data.name);
2025
2730
  return result.data.name;
2026
2731
  }
2027
- throw new Error("Failed to get current app from device");
2732
+ const errResult = result;
2733
+ const dmkErr = errResult.error ?? {};
2734
+ const original = dmkErr.originalError;
2735
+ debugLog(
2736
+ "[AppManager] _getCurrentApp failed sessionId=",
2737
+ sessionId,
2738
+ "tag=",
2739
+ dmkErr._tag,
2740
+ "errorCode=",
2741
+ dmkErr.errorCode,
2742
+ "message=",
2743
+ dmkErr.message,
2744
+ "originalErrorMessage=",
2745
+ original?.message ?? String(original ?? "")
2746
+ );
2747
+ throw Object.assign(
2748
+ new Error(
2749
+ dmkErr.message ?? "Failed to get current app from device"
2750
+ ),
2751
+ {
2752
+ _tag: dmkErr._tag,
2753
+ errorCode: dmkErr.errorCode,
2754
+ originalError: original
2755
+ }
2756
+ );
2028
2757
  }
2029
2758
  async _openApp(sessionId, appName) {
2030
2759
  const result = await this._dmk.sendCommand({
@@ -2033,8 +2762,11 @@ var AppManager = class {
2033
2762
  });
2034
2763
  if (!(0, import_device_management_kit2.isSuccessCommandResult)(result)) {
2035
2764
  const { statusCode } = result;
2765
+ const hasStatusCode2 = statusCode != null && statusCode !== "";
2766
+ debugLog("[AppManager] openApp failed:", appName, "statusCode:", statusCode);
2036
2767
  throw Object.assign(new Error(`Failed to open "${appName}"`), {
2037
- _tag: "OpenAppCommandError",
2768
+ _tag: ERROR_TAG.OpenAppCommand,
2769
+ code: hasStatusCode2 ? void 0 : import_hwk_adapter_core9.HardwareErrorCode.AppNotInstalled,
2038
2770
  errorCode: String(statusCode ?? ""),
2039
2771
  statusCode,
2040
2772
  appName
@@ -2042,6 +2774,7 @@ var AppManager = class {
2042
2774
  }
2043
2775
  }
2044
2776
  async _closeCurrentApp(sessionId) {
2777
+ debugLog("[AppManager] closeCurrentApp");
2045
2778
  await this._dmk.sendCommand({
2046
2779
  sessionId,
2047
2780
  command: new import_device_management_kit2.CloseAppCommand()
@@ -2052,15 +2785,23 @@ var AppManager = class {
2052
2785
  * or throw after `_maxRetries` attempts.
2053
2786
  */
2054
2787
  async _waitForApp(sessionId, expectedAppName) {
2788
+ let lastSeen = "";
2055
2789
  for (let i = 0; i < this._maxRetries; i++) {
2056
2790
  await this._wait();
2057
2791
  const current = await this._getCurrentApp(sessionId);
2792
+ lastSeen = current;
2058
2793
  if (current === expectedAppName) {
2059
2794
  return;
2060
2795
  }
2061
2796
  }
2797
+ debugLog(
2798
+ "[AppManager] waitForApp exhausted: expected=",
2799
+ expectedAppName,
2800
+ "lastSeen=",
2801
+ lastSeen
2802
+ );
2062
2803
  throw new Error(
2063
- `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries`
2804
+ `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
2064
2805
  );
2065
2806
  }
2066
2807
  _isDashboard(appName) {
@@ -2071,46 +2812,92 @@ var AppManager = class {
2071
2812
  }
2072
2813
  };
2073
2814
 
2074
- // src/connector/chains/legacyAppRetry.ts
2815
+ // src/connector/chains/legacyChainCall.ts
2075
2816
  function isLegacyWrongAppError(err, _appName) {
2076
2817
  return isWrongAppError(err);
2077
2818
  }
2078
- async function withLegacyAppRetry(ctx, sessionId, appName, action) {
2079
- try {
2080
- return await action(sessionId);
2081
- } catch (err) {
2819
+ async function withLegacyChainCall(ctx, sessionId, options, action) {
2820
+ const { appName, needsConfirmation } = options;
2821
+ let openAppPromptShown = false;
2822
+ const onAppOpenPrompt = () => {
2823
+ openAppPromptShown = true;
2082
2824
  ctx.emit("ui-event", {
2083
- type: import_hwk_adapter_core9.EConnectorInteraction.InteractionComplete,
2825
+ type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOpenApp,
2084
2826
  payload: { sessionId }
2085
2827
  });
2086
- if (isLegacyWrongAppError(err, appName)) {
2087
- const dmk = await ctx.getOrCreateDmk();
2088
- const appManager = new AppManager(dmk);
2089
- try {
2090
- await appManager.ensureAppOpen(sessionId, appName, () => {
2091
- ctx.emit("ui-event", {
2092
- type: import_hwk_adapter_core9.EConnectorInteraction.ConfirmOpenApp,
2093
- payload: { sessionId }
2094
- });
2095
- });
2096
- } catch (switchErr) {
2097
- ctx.emit("ui-event", {
2098
- type: import_hwk_adapter_core9.EConnectorInteraction.InteractionComplete,
2099
- payload: { sessionId }
2100
- });
2101
- throw ctx.wrapError(switchErr);
2102
- }
2103
- ctx.clearAllSigners();
2828
+ };
2829
+ const closeOpenAppUiIfShown = () => {
2830
+ if (openAppPromptShown) {
2831
+ ctx.emit("ui-event", {
2832
+ type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
2833
+ payload: { sessionId }
2834
+ });
2835
+ openAppPromptShown = false;
2836
+ }
2837
+ };
2838
+ try {
2839
+ await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
2840
+ } catch (err) {
2841
+ debugLog(
2842
+ "[LegacyChainCall] pre-flight ensureAppOpen failed:",
2843
+ appName,
2844
+ err?.message
2845
+ );
2846
+ closeOpenAppUiIfShown();
2847
+ throw ctx.wrapError(err, { defaultAppName: appName });
2848
+ }
2849
+ const runOnce = async () => {
2850
+ let confirmEmitted = false;
2851
+ if (needsConfirmation) {
2104
2852
  ctx.emit("ui-event", {
2105
- type: import_hwk_adapter_core9.EConnectorInteraction.InteractionComplete,
2853
+ type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
2106
2854
  payload: { sessionId }
2107
2855
  });
2856
+ confirmEmitted = true;
2857
+ }
2858
+ try {
2108
2859
  return await action(sessionId);
2860
+ } finally {
2861
+ if (confirmEmitted || openAppPromptShown) {
2862
+ ctx.emit("ui-event", {
2863
+ type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
2864
+ payload: { sessionId }
2865
+ });
2866
+ openAppPromptShown = false;
2867
+ }
2109
2868
  }
2110
- ctx.invalidateSession(sessionId);
2111
- throw ctx.wrapError(err);
2869
+ };
2870
+ try {
2871
+ return await runOnce();
2872
+ } catch (err) {
2873
+ if (!isLegacyWrongAppError(err, appName)) {
2874
+ debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
2875
+ ctx.invalidateSession(sessionId);
2876
+ throw ctx.wrapError(err, { defaultAppName: appName });
2877
+ }
2878
+ debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
2879
+ try {
2880
+ await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
2881
+ } catch (switchErr) {
2882
+ debugLog(
2883
+ "[LegacyChainCall] retry ensureAppOpen failed:",
2884
+ appName,
2885
+ switchErr?.message
2886
+ );
2887
+ closeOpenAppUiIfShown();
2888
+ throw ctx.wrapError(switchErr, { defaultAppName: appName });
2889
+ }
2890
+ ctx.clearAllSigners();
2891
+ const result = await runOnce();
2892
+ debugLog("[LegacyChainCall] retry succeeded:", appName);
2893
+ return result;
2112
2894
  }
2113
2895
  }
2896
+ async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
2897
+ const dmk = await ctx.getOrCreateDmk();
2898
+ const appManager = new AppManager(dmk);
2899
+ await appManager.ensureAppOpen(sessionId, appName, onPrompt);
2900
+ }
2114
2901
 
2115
2902
  // src/transport/DmkTransport.ts
2116
2903
  var import_hw_transport = __toESM(require("@ledgerhq/hw-transport"));
@@ -2140,83 +2927,75 @@ var DmkTransport = class extends import_hw_transport.default {
2140
2927
  // src/connector/chains/tron.ts
2141
2928
  async function tronGetAddress(ctx, sessionId, params) {
2142
2929
  const path = normalizePath(params.path);
2143
- await _ensureTronAppOpen(ctx, sessionId);
2144
- return withLegacyAppRetry(ctx, sessionId, "Tron", async (sid) => {
2145
- const trx = await _createTrx(ctx, sid);
2146
- if (params.showOnDevice) {
2147
- ctx.emit("ui-event", {
2148
- type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
2149
- payload: { sessionId: sid }
2150
- });
2930
+ const showOnDevice = params.showOnDevice ?? false;
2931
+ return withLegacyChainCall(
2932
+ ctx,
2933
+ sessionId,
2934
+ {
2935
+ appName: "Tron",
2936
+ // Only show "confirm on device" UI when the device is actually going
2937
+ // to display the address for the user to verify.
2938
+ needsConfirmation: showOnDevice
2939
+ },
2940
+ async (sid) => {
2941
+ const trx = await _createTrx(ctx, sid);
2942
+ const result = await trx.getAddress(path, showOnDevice);
2943
+ return { address: result.address, publicKey: result.publicKey, path: params.path };
2151
2944
  }
2152
- const result = await trx.getAddress(path, params.showOnDevice ?? false);
2153
- ctx.emit("ui-event", {
2154
- type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
2155
- payload: { sessionId: sid }
2156
- });
2157
- return { address: result.address, publicKey: result.publicKey, path: params.path };
2158
- });
2945
+ );
2159
2946
  }
2160
2947
  async function tronSignTransaction(ctx, sessionId, params) {
2161
2948
  if (!params.rawTxHex) {
2162
2949
  throw Object.assign(
2163
2950
  new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
2164
- { code: import_hwk_adapter_core10.HardwareErrorCode.InvalidParams }
2951
+ { code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
2165
2952
  );
2166
2953
  }
2167
2954
  const path = normalizePath(params.path);
2168
- await _ensureTronAppOpen(ctx, sessionId);
2169
- return withLegacyAppRetry(ctx, sessionId, "Tron", async (sid) => {
2170
- const trx = await _createTrx(ctx, sid);
2171
- ctx.emit("ui-event", {
2172
- type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
2173
- payload: { sessionId: sid }
2174
- });
2175
- const signature = await trx.signTransaction(
2176
- path,
2177
- params.rawTxHex,
2178
- params.tokenSignatures ?? []
2179
- );
2180
- ctx.emit("ui-event", {
2181
- type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
2182
- payload: { sessionId: sid }
2183
- });
2184
- return { signature };
2185
- });
2955
+ return withLegacyChainCall(
2956
+ ctx,
2957
+ sessionId,
2958
+ { appName: "Tron", needsConfirmation: true },
2959
+ async (sid) => {
2960
+ const trx = await _createTrx(ctx, sid);
2961
+ const signature = await trx.signTransaction(
2962
+ path,
2963
+ params.rawTxHex,
2964
+ params.tokenSignatures ?? []
2965
+ );
2966
+ return { signature };
2967
+ }
2968
+ );
2186
2969
  }
2187
2970
  async function tronSignMessage(ctx, sessionId, params) {
2188
2971
  const path = normalizePath(params.path);
2189
- await _ensureTronAppOpen(ctx, sessionId);
2190
- return withLegacyAppRetry(ctx, sessionId, "Tron", async (sid) => {
2191
- const trx = await _createTrx(ctx, sid);
2192
- ctx.emit("ui-event", {
2193
- type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
2194
- payload: { sessionId: sid }
2195
- });
2196
- const signature = await trx.signPersonalMessage(path, params.messageHex);
2197
- ctx.emit("ui-event", {
2198
- type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
2199
- payload: { sessionId: sid }
2200
- });
2201
- return { signature };
2202
- });
2972
+ return withLegacyChainCall(
2973
+ ctx,
2974
+ sessionId,
2975
+ { appName: "Tron", needsConfirmation: true },
2976
+ async (sid) => {
2977
+ const trx = await _createTrx(ctx, sid);
2978
+ const signature = await trx.signPersonalMessage(path, params.messageHex);
2979
+ return { signature };
2980
+ }
2981
+ );
2203
2982
  }
2204
2983
  async function _createTrx(ctx, sessionId) {
2205
2984
  const dmk = await ctx.getOrCreateDmk();
2206
2985
  return new import_hw_app_trx.default(new DmkTransport(dmk, sessionId));
2207
2986
  }
2208
- async function _ensureTronAppOpen(ctx, sessionId) {
2209
- const dmk = await ctx.getOrCreateDmk();
2210
- const appManager = new AppManager(dmk);
2211
- await appManager.ensureAppOpen(sessionId, "Tron", () => {
2212
- ctx.emit("ui-event", {
2213
- type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOpenApp,
2214
- payload: { sessionId }
2215
- });
2216
- });
2217
- }
2218
2987
 
2219
2988
  // src/connector/LedgerConnectorBase.ts
2989
+ var METHOD_PREFIX_TO_APP_NAME = {
2990
+ evm: "Ethereum",
2991
+ btc: "Bitcoin",
2992
+ sol: "Solana",
2993
+ tron: "Tron"
2994
+ };
2995
+ var HARDWARE_ERROR_CODE_VALUES = new Set(
2996
+ Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
2997
+ );
2998
+ var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
2220
2999
  async function defaultLedgerKitImporter(pkg) {
2221
3000
  switch (pkg) {
2222
3001
  case "@ledgerhq/device-management-kit":
@@ -2240,16 +3019,6 @@ var LedgerConnectorBase = class {
2240
3019
  this._dmk = null;
2241
3020
  this._eventHandlers = /* @__PURE__ */ new Map();
2242
3021
  // ---------------------------------------------------------------------------
2243
- // ConnectId <-> DMK path mapping
2244
- //
2245
- // DMK uses internal paths (BLE MAC, USB UUID) that may change across sessions.
2246
- // _resolveConnectId() maps these to stable external IDs (BLE: "A58F", USB: same).
2247
- // This bidirectional map is the SINGLE SOURCE OF TRUTH for all connectId usage.
2248
- // ---------------------------------------------------------------------------
2249
- this._connectIdToPath = /* @__PURE__ */ new Map();
2250
- // "A58F" -> "D5:75:7D:4B:51:E8"
2251
- this._pathToConnectId = /* @__PURE__ */ new Map();
2252
- // ---------------------------------------------------------------------------
2253
3022
  // Per-session DeviceAction cancellers
2254
3023
  //
2255
3024
  // Each chain handler registers its active DeviceAction's canceller via
@@ -2258,17 +3027,30 @@ var LedgerConnectorBase = class {
2258
3027
  // unsubscribes the observable and releases DMK's IntentQueue slot.
2259
3028
  // ---------------------------------------------------------------------------
2260
3029
  this._cancellers = /* @__PURE__ */ new Map();
3030
+ // ---------------------------------------------------------------------------
3031
+ // Per-session DMK state subscriptions
3032
+ //
3033
+ // DMK only emits `device-disconnect` on the connector when `disconnect()`
3034
+ // is called explicitly. To pick up autonomous disconnects (USB unplug,
3035
+ // BLE drop, device sleep, DMK transport reset) we subscribe to
3036
+ // `_dmk.getDeviceSessionState({sessionId})` per active session and emit
3037
+ // `device-disconnect` ourselves the moment DMK reports the device went
3038
+ // to NOT_CONNECTED. The subscription is torn down on disconnect /
3039
+ // reset / observable completion to avoid leaks.
3040
+ // ---------------------------------------------------------------------------
3041
+ this._sessionStateSubs = /* @__PURE__ */ new Map();
2261
3042
  this._createTransport = createTransport;
2262
3043
  this.connectionType = options?.connectionType ?? "usb";
2263
3044
  this._providedDmk = options?.dmk;
2264
3045
  this._importLedgerKit = options?.importLedgerKit ?? defaultLedgerKitImporter;
3046
+ this._requirePreFlightScan = options?.requirePreFlightScan ?? false;
2265
3047
  if (this._providedDmk) {
2266
3048
  this._initManagers(this._providedDmk);
2267
3049
  }
2268
3050
  this._ctx = {
2269
3051
  emit: (event, data) => this._emit(event, data),
2270
3052
  invalidateSession: (sid) => this._invalidateSession(sid),
2271
- wrapError: (err) => this._wrapError(err),
3053
+ wrapError: (err, opts) => this._wrapError(err, opts),
2272
3054
  getOrCreateDmk: () => this._getOrCreateDmk(),
2273
3055
  getDeviceManager: () => this._getDeviceManager(),
2274
3056
  getSignerManager: () => this._getSignerManager(),
@@ -2279,38 +3061,23 @@ var LedgerConnectorBase = class {
2279
3061
  importLedgerKit: this._importLedgerKit
2280
3062
  };
2281
3063
  }
2282
- // "D5:75:7D:4B:51:E8" -> "A58F"
2283
- /** Register a connectId <-> path mapping from a device descriptor. */
2284
- _registerDeviceId(descriptor) {
2285
- const connectId = this._resolveConnectId(descriptor);
2286
- this._connectIdToPath.set(connectId, descriptor.path);
2287
- this._pathToConnectId.set(descriptor.path, connectId);
2288
- return connectId;
2289
- }
2290
- /** Get DMK path from external connectId. Falls back to connectId itself. */
2291
- _getPathForConnectId(connectId) {
2292
- return this._connectIdToPath.get(connectId) ?? connectId;
2293
- }
2294
- /** Get external connectId from DMK path. Falls back to path itself. */
2295
- _getConnectIdForPath(path) {
2296
- return this._pathToConnectId.get(path) ?? path;
2297
- }
2298
3064
  // ---------------------------------------------------------------------------
2299
3065
  // Protected — hooks for subclasses
2300
3066
  // ---------------------------------------------------------------------------
2301
3067
  /**
2302
3068
  * Resolve the connectId for a discovered device descriptor.
2303
3069
  * Default: use the DMK path (ephemeral UUID).
2304
- * Override in subclasses to extract stable identifiers (e.g. BLE hex ID).
3070
+ * Override in subclasses only when the public connectId differs from the transport path.
2305
3071
  */
2306
3072
  _resolveConnectId(descriptor) {
2307
3073
  return descriptor.path;
2308
3074
  }
2309
- // ---------------------------------------------------------------------------
2310
- // IConnector -- Device discovery
2311
- // ---------------------------------------------------------------------------
2312
- async searchDevices() {
2313
- const dm = await this._getDeviceManager();
3075
+ /**
3076
+ * Authoritative discovery for this transport. Default = enumerate snapshot
3077
+ * (USB/HID). BLE overrides to drive from a live scan window since DMK's
3078
+ * enumerate() cache survives peripherals going offline.
3079
+ */
3080
+ async _discoverDescriptors(dm) {
2314
3081
  let descriptors = await dm.enumerate();
2315
3082
  if (descriptors.length === 0) {
2316
3083
  try {
@@ -2319,137 +3086,331 @@ var LedgerConnectorBase = class {
2319
3086
  }
2320
3087
  descriptors = await dm.enumerate();
2321
3088
  }
2322
- const result = descriptors.map((d) => {
2323
- const connectId = this._registerDeviceId(d);
2324
- return {
2325
- connectId,
2326
- deviceId: d.path,
2327
- name: d.name || d.type || "Ledger",
2328
- model: d.type
2329
- };
2330
- });
3089
+ return descriptors;
3090
+ }
3091
+ _assertSingleUsbDescriptor(descriptors) {
3092
+ if (isLedgerBleConnectionType(this.connectionType) || descriptors.length <= 1) {
3093
+ return;
3094
+ }
3095
+ debugLog(
3096
+ `[DMK] Multiple Ledger USB devices found (${descriptors.length}); refusing to choose by ephemeral path. paths=[${descriptors.map((d) => d.path).join(",")}]`
3097
+ );
3098
+ throw createMultipleUsbLedgerDevicesError();
3099
+ }
3100
+ // ---------------------------------------------------------------------------
3101
+ // IConnector -- Device discovery
3102
+ // ---------------------------------------------------------------------------
3103
+ async searchDevices() {
3104
+ const dm = await this._getDeviceManager();
3105
+ const descriptors = await this._discoverDescriptors(dm);
3106
+ this._assertSingleUsbDescriptor(descriptors);
3107
+ const resolvedDescriptors = descriptors.map((d) => ({
3108
+ descriptor: d,
3109
+ connectId: this._resolveConnectId(d)
3110
+ }));
3111
+ const result = resolvedDescriptors.map(({ descriptor: d, connectId }) => ({
3112
+ connectId,
3113
+ deviceId: d.path,
3114
+ name: d.name || d.type || "Ledger",
3115
+ model: d.type,
3116
+ modelName: d.modelName,
3117
+ rssi: d.rssi,
3118
+ isConnectable: d.isConnectable,
3119
+ serialNumber: d.serialNumber
3120
+ }));
3121
+ debugLog(
3122
+ `[DMK] connector.searchDevices() return count=${result.length} ids=[${result.map((r) => r.connectId).join(",")}] paths=[${result.map((r) => r.deviceId).join(",")}]`
3123
+ );
2331
3124
  return result;
2332
3125
  }
2333
3126
  // ---------------------------------------------------------------------------
2334
3127
  // IConnector -- Connection
2335
3128
  // ---------------------------------------------------------------------------
2336
3129
  async connect(deviceId) {
2337
- const dm = await this._getDeviceManager();
2338
- await this.searchDevices();
2339
- const dmkPath = deviceId ? this._getPathForConnectId(deviceId) : void 0;
2340
- let targetPath = dmkPath;
3130
+ const callerSuppliedConnectId = Boolean(deviceId);
3131
+ let targetPath = deviceId;
3132
+ if (callerSuppliedConnectId && !isLedgerBleConnectionType(this.connectionType)) {
3133
+ const dm = await this._getDeviceManager();
3134
+ this._assertSingleUsbDescriptor(await this._discoverDescriptors(dm));
3135
+ }
2341
3136
  if (!targetPath) {
2342
- const descriptors = await dm.enumerate();
2343
- if (descriptors.length === 0) {
3137
+ const discovered = await this.searchDevices();
3138
+ if (discovered.length === 0) {
2344
3139
  throw new Error(
2345
- `No Ledger device found. Make sure the device is connected${this.connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
3140
+ `No Ledger device found. Make sure the device is connected${isLedgerBleConnectionType(this.connectionType) ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
2346
3141
  );
2347
3142
  }
2348
- targetPath = descriptors[0].path;
3143
+ targetPath = discovered[0].deviceId;
2349
3144
  }
2350
- const externalConnectId = this._getConnectIdForPath(targetPath);
3145
+ const externalConnectId = targetPath;
3146
+ const HANG_CEILING_MS = 5 * 6e4;
3147
+ const dmConnectWithObserve = async (dm, path) => {
3148
+ if (!isLedgerBleConnectionType(this.connectionType)) return dm.connect(path);
3149
+ let timeoutId;
3150
+ let timedOut = false;
3151
+ const connectPromise = dm.connect(path);
3152
+ const hangPromise = new Promise((_, reject) => {
3153
+ timeoutId = setTimeout(() => {
3154
+ timedOut = true;
3155
+ const err = new Error(
3156
+ `Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
3157
+ );
3158
+ err._tag = ERROR_TAG.BlePairingTimeout;
3159
+ err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
3160
+ reject(err);
3161
+ }, HANG_CEILING_MS);
3162
+ });
3163
+ try {
3164
+ return await Promise.race([connectPromise, hangPromise]);
3165
+ } catch (err) {
3166
+ const e = err;
3167
+ debugLog("[DMK] dm.connect rejected \u2014 observed:", {
3168
+ _tag: e?._tag,
3169
+ message: e?.message,
3170
+ errorCode: e?.errorCode,
3171
+ statusCode: e?.statusCode,
3172
+ originalTag: e?.originalError?._tag,
3173
+ originalMessage: e?.originalError?.message
3174
+ });
3175
+ if (timedOut) {
3176
+ void connectPromise.then(
3177
+ (sessionId) => dm.disconnect(sessionId).catch(() => void 0),
3178
+ () => void 0
3179
+ );
3180
+ }
3181
+ throw err;
3182
+ } finally {
3183
+ if (timeoutId) clearTimeout(timeoutId);
3184
+ }
3185
+ };
3186
+ const isBleDirectConnect = isLedgerBleConnectionType(this.connectionType) && callerSuppliedConnectId;
3187
+ const throwNotAdvertising = () => {
3188
+ const err = new Error(
3189
+ "Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
3190
+ );
3191
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
3192
+ err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
3193
+ throw err;
3194
+ };
2351
3195
  const doConnect = async (path) => {
2352
- const sessionId = await dm.connect(path);
3196
+ const dm = await this._getDeviceManager();
3197
+ if (isBleDirectConnect && !dm.hasDiscoveredDevice(path)) {
3198
+ const live = await dm.getLiveDevices(BLE_CONNECT_SCAN_TIMEOUT_MS);
3199
+ if (this._requirePreFlightScan && !live.some((d) => d.id === path)) {
3200
+ throwNotAdvertising();
3201
+ }
3202
+ }
3203
+ let sessionId;
3204
+ try {
3205
+ sessionId = await dmConnectWithObserve(dm, path);
3206
+ } catch (err) {
3207
+ if (isBleDirectConnect && err?._tag === ERROR_TAG.DeviceNotInDiscoveryCache) {
3208
+ throwNotAdvertising();
3209
+ }
3210
+ throw err;
3211
+ }
3212
+ try {
3213
+ this._watchSessionState(sessionId, externalConnectId);
3214
+ } catch (subErr) {
3215
+ debugLog("[DMK] state subscription failed during connect; disconnecting session:", subErr);
3216
+ try {
3217
+ await dm.disconnect(sessionId);
3218
+ } catch {
3219
+ }
3220
+ throw subErr;
3221
+ }
3222
+ const info = dm.getDiscoveredDeviceInfo(path);
2353
3223
  const session = {
2354
3224
  sessionId,
2355
3225
  deviceInfo: {
2356
3226
  vendor: "ledger",
2357
- model: "unknown",
3227
+ model: info?.model ?? "unknown",
3228
+ modelName: info?.modelName,
2358
3229
  firmwareVersion: "unknown",
2359
3230
  deviceId: path,
2360
3231
  connectId: externalConnectId,
2361
3232
  connectionType: this.connectionType,
3233
+ rssi: info?.rssi,
2362
3234
  capabilities: { persistentDeviceIdentity: false }
2363
3235
  }
2364
3236
  };
3237
+ const name = dm.getDeviceName(path) ?? "Ledger";
2365
3238
  this._emit("device-connect", {
2366
- device: {
2367
- connectId: externalConnectId,
2368
- deviceId: path,
2369
- name: "Ledger"
2370
- }
3239
+ device: { connectId: externalConnectId, deviceId: path, name }
2371
3240
  });
2372
3241
  return session;
2373
3242
  };
2374
3243
  try {
2375
3244
  return await doConnect(targetPath);
2376
- } catch {
3245
+ } catch (err) {
2377
3246
  this._resetSignersAndSessions();
2378
- const dm2 = await this._getDeviceManager();
2379
- await this.searchDevices();
2380
- const retryPath = this._getPathForConnectId(externalConnectId);
2381
- if (!retryPath || retryPath === externalConnectId) {
2382
- const descriptors = await dm2.enumerate();
2383
- if (descriptors.length === 0) {
2384
- throw new Error(
2385
- `No Ledger device found after retry. Make sure the device is connected${this.connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
2386
- );
3247
+ if (isLedgerBleConnectionType(this.connectionType)) {
3248
+ const tag = err?._tag;
3249
+ if (isKnownConnectionTag(tag)) {
3250
+ throw err;
2387
3251
  }
2388
- return doConnect(descriptors[0].path);
3252
+ const wrapped = new Error(
3253
+ "Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
3254
+ );
3255
+ wrapped._tag = ERROR_TAG.BleGattBondingFailed;
3256
+ wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
3257
+ wrapped.originalError = err;
3258
+ throw wrapped;
3259
+ }
3260
+ if (err && typeof err === "object" && err._tag) {
3261
+ const taggedError = err instanceof Error ? err : Object.assign(
3262
+ new Error(err.message ?? "Ledger device error"),
3263
+ err
3264
+ );
3265
+ throw taggedError;
2389
3266
  }
2390
- return doConnect(retryPath);
3267
+ throw new Error("Ledger device appears unplugged. Plug in the device and try again.");
2391
3268
  }
2392
3269
  }
2393
3270
  async disconnect(sessionId) {
2394
3271
  if (!this._deviceManager) return;
2395
3272
  const deviceId = this._deviceManager.getDeviceId(sessionId);
2396
3273
  this._signerManager?.invalidate(sessionId);
3274
+ this._unwatchSessionState(sessionId);
2397
3275
  await this._deviceManager.disconnect(sessionId);
2398
3276
  if (deviceId) {
2399
3277
  this._emit("device-disconnect", { connectId: deviceId });
2400
3278
  }
2401
3279
  }
3280
+ /**
3281
+ * Subscribe to DMK's per-session state observable so that any autonomous
3282
+ * disconnect (USB unplug, BLE drop, device sleep, transport reset) is
3283
+ * surfaced as a `device-disconnect` event — without this, the upstream
3284
+ * `LedgerAdapter._sessions` map would hold a dead session entry until
3285
+ * the next call hit `DeviceSessionNotFound`.
3286
+ *
3287
+ * Subscribe failure is fatal — see the inline note at the subscribe call.
3288
+ */
3289
+ _watchSessionState(sessionId, externalConnectId) {
3290
+ const dmk = this._dmk;
3291
+ if (!dmk) return;
3292
+ const previous = this._sessionStateSubs.get(sessionId);
3293
+ if (previous) {
3294
+ try {
3295
+ previous.unsubscribe();
3296
+ } catch {
3297
+ }
3298
+ }
3299
+ const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
3300
+ next: (state) => {
3301
+ if (state?.deviceStatus === "NOT CONNECTED") {
3302
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3303
+ }
3304
+ },
3305
+ error: () => {
3306
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3307
+ },
3308
+ complete: () => {
3309
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3310
+ }
3311
+ });
3312
+ this._sessionStateSubs.set(sessionId, sub);
3313
+ }
3314
+ _unwatchSessionState(sessionId) {
3315
+ const sub = this._sessionStateSubs.get(sessionId);
3316
+ if (!sub) return;
3317
+ try {
3318
+ sub.unsubscribe();
3319
+ } catch {
3320
+ }
3321
+ this._sessionStateSubs.delete(sessionId);
3322
+ }
3323
+ _handleAutonomousDisconnect(sessionId, externalConnectId) {
3324
+ if (!this._sessionStateSubs.has(sessionId)) return;
3325
+ debugLog(
3326
+ "[DMK] autonomous disconnect detected \u2014 sessionId:",
3327
+ sessionId,
3328
+ "connectId:",
3329
+ externalConnectId
3330
+ );
3331
+ this._unwatchSessionState(sessionId);
3332
+ this._signerManager?.invalidate(sessionId);
3333
+ this._cancellers.get(sessionId)?.({
3334
+ code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
3335
+ tag: "DeviceDisconnected",
3336
+ message: "Device disconnected"
3337
+ });
3338
+ this._cancellers.delete(sessionId);
3339
+ this._emit("device-disconnect", { connectId: externalConnectId });
3340
+ }
2402
3341
  // ---------------------------------------------------------------------------
2403
3342
  // IConnector -- Method dispatch
2404
3343
  // ---------------------------------------------------------------------------
2405
3344
  async call(sessionId, method, params) {
2406
3345
  debugLog("[DMK] call:", method, JSON.stringify(params));
3346
+ try {
3347
+ return await this._dispatch(sessionId, method, params);
3348
+ } catch (err) {
3349
+ if (isAppStuckByApdu(err)) {
3350
+ throw Object.assign(new Error("Ledger app is unresponsive"), {
3351
+ code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
3352
+ _tag: ERROR_TAG.DeviceAppStuck,
3353
+ originalError: err
3354
+ });
3355
+ }
3356
+ if (isTransportStuck(err)) {
3357
+ throw Object.assign(new Error("Device communication interrupted, please retry"), {
3358
+ code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
3359
+ _tag: ERROR_TAG.DeviceTransportStuck,
3360
+ originalError: err
3361
+ });
3362
+ }
3363
+ throw err;
3364
+ }
3365
+ }
3366
+ async _dispatch(sessionId, method, params) {
3367
+ const ctx = this._ctxForMethod(method);
2407
3368
  switch (method) {
2408
3369
  // EVM
2409
3370
  case "evmGetAddress":
2410
- return evmGetAddress(this._ctx, sessionId, params);
3371
+ return evmGetAddress(ctx, sessionId, params);
2411
3372
  case "evmSignTransaction":
2412
- return evmSignTransaction(this._ctx, sessionId, params);
3373
+ return evmSignTransaction(ctx, sessionId, params);
2413
3374
  case "evmSignMessage":
2414
- return evmSignMessage(this._ctx, sessionId, params);
3375
+ return evmSignMessage(ctx, sessionId, params);
2415
3376
  case "evmSignTypedData":
2416
- return evmSignTypedData(this._ctx, sessionId, params);
3377
+ return evmSignTypedData(ctx, sessionId, params);
2417
3378
  // BTC
2418
3379
  case "btcGetAddress":
2419
- return btcGetAddress(this._ctx, sessionId, params);
3380
+ return btcGetAddress(ctx, sessionId, params);
2420
3381
  case "btcGetPublicKey":
2421
- return btcGetPublicKey(this._ctx, sessionId, params);
3382
+ return btcGetPublicKey(ctx, sessionId, params);
2422
3383
  case "btcSignTransaction":
2423
- return btcSignTransaction(this._ctx, sessionId, params);
3384
+ return btcSignTransaction(ctx, sessionId, params);
2424
3385
  case "btcSignPsbt":
2425
- return btcSignPsbt(this._ctx, sessionId, params);
3386
+ return btcSignPsbt(ctx, sessionId, params);
2426
3387
  case "btcSignMessage":
2427
- return btcSignMessage(this._ctx, sessionId, params);
3388
+ return btcSignMessage(ctx, sessionId, params);
2428
3389
  case "btcGetMasterFingerprint":
2429
3390
  return btcGetMasterFingerprint(
2430
- this._ctx,
3391
+ ctx,
2431
3392
  sessionId,
2432
3393
  params
2433
3394
  );
2434
3395
  // SOL
2435
3396
  case "solGetAddress":
2436
- return solGetAddress(this._ctx, sessionId, params);
3397
+ return solGetAddress(ctx, sessionId, params);
2437
3398
  case "solSignTransaction":
2438
- return solSignTransaction(this._ctx, sessionId, params);
3399
+ return solSignTransaction(ctx, sessionId, params);
2439
3400
  case "solSignMessage":
2440
- return solSignMessage(this._ctx, sessionId, params);
3401
+ return solSignMessage(ctx, sessionId, params);
2441
3402
  // TRON
2442
3403
  case "tronGetAddress":
2443
- return tronGetAddress(this._ctx, sessionId, params);
3404
+ return tronGetAddress(ctx, sessionId, params);
2444
3405
  case "tronSignTransaction":
2445
- return tronSignTransaction(this._ctx, sessionId, params);
3406
+ return tronSignTransaction(ctx, sessionId, params);
2446
3407
  case "tronSignMessage": {
2447
3408
  const p = params;
2448
3409
  const internalParams = {
2449
3410
  path: p.path,
2450
3411
  messageHex: p.messageHex
2451
3412
  };
2452
- return tronSignMessage(this._ctx, sessionId, internalParams);
3413
+ return tronSignMessage(ctx, sessionId, internalParams);
2453
3414
  }
2454
3415
  default:
2455
3416
  throw new Error(`LedgerConnector: unknown method "${method}"`);
@@ -2542,14 +3503,13 @@ var LedgerConnectorBase = class {
2542
3503
  }
2543
3504
  /**
2544
3505
  * Replace an old session with a new one after app switch.
2545
- * Updates the connectId→path mapping so subsequent calls() use the new session,
2546
- * and emits device-connect so the adapter updates its _sessions Map.
3506
+ * Emits device-connect so the adapter updates its _sessions Map.
2547
3507
  */
2548
3508
  _replaceSession(oldSessionId, _newSessionId) {
2549
3509
  const dm = this._deviceManager;
2550
3510
  if (!dm) return;
2551
3511
  const oldDeviceId = dm.getDeviceId(oldSessionId);
2552
- const connectId = oldDeviceId ? this._pathToConnectId.get(oldDeviceId) : void 0;
3512
+ const connectId = oldDeviceId;
2553
3513
  this._signerManager?.invalidate(oldSessionId);
2554
3514
  if (connectId) {
2555
3515
  this._emit("device-connect", {
@@ -2562,13 +3522,18 @@ var LedgerConnectorBase = class {
2562
3522
  }
2563
3523
  }
2564
3524
  /**
2565
- * Light reset: clear signer/session state but keep DMK, BLE scan, and ID mapping alive.
3525
+ * Light reset: clear signer/session state but keep DMK alive.
2566
3526
  * Used by connect() retry — we want to re-discover with the same transport.
3527
+ *
3528
+ * Note: drops the device manager but tears down its RxJS subs first via
3529
+ * disposeKeepingDmk(). Bare null-assignment would leak _listenSub /
3530
+ * _discoverySub and double-scan after the next _initManagers().
2567
3531
  */
2568
3532
  _resetSignersAndSessions() {
2569
3533
  debugLog("[DMK] _resetSignersAndSessions called");
2570
3534
  this._signerManager?.clearAll();
2571
3535
  this._signerManager = null;
3536
+ this._deviceManager?.disposeKeepingDmk();
2572
3537
  this._deviceManager = null;
2573
3538
  }
2574
3539
  _resetAll() {
@@ -2580,13 +3545,18 @@ var LedgerConnectorBase = class {
2580
3545
  }
2581
3546
  }
2582
3547
  this._cancellers.clear();
3548
+ for (const sub of this._sessionStateSubs.values()) {
3549
+ try {
3550
+ sub.unsubscribe();
3551
+ } catch {
3552
+ }
3553
+ }
3554
+ this._sessionStateSubs.clear();
2583
3555
  this._signerManager?.clearAll();
2584
3556
  this._deviceManager?.dispose();
2585
3557
  this._deviceManager = null;
2586
3558
  this._signerManager = null;
2587
3559
  this._dmk = null;
2588
- this._connectIdToPath.clear();
2589
- this._pathToConnectId.clear();
2590
3560
  }
2591
3561
  // ---------------------------------------------------------------------------
2592
3562
  // Private -- Events
@@ -2605,16 +3575,36 @@ var LedgerConnectorBase = class {
2605
3575
  // ---------------------------------------------------------------------------
2606
3576
  // Private -- Error handling
2607
3577
  // ---------------------------------------------------------------------------
2608
- _wrapError(err) {
2609
- const mapped = mapLedgerError(err);
2610
- const error = new Error(mapped.message);
3578
+ /**
3579
+ * Return a per-call ctx with the chain's Ledger app name pre-bound to
3580
+ * wrapError, so chain handlers don't need to repeat `{ defaultAppName: 'X' }`
3581
+ * at every catch site. Falls through unchanged for unknown methods.
3582
+ */
3583
+ _ctxForMethod(method) {
3584
+ const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
3585
+ const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
3586
+ if (!defaultAppName) return this._ctx;
3587
+ return {
3588
+ ...this._ctx,
3589
+ wrapError: (err, opts) => this._wrapError(err, { defaultAppName, ...opts })
3590
+ };
3591
+ }
3592
+ _wrapError(err, opts) {
2611
3593
  const src = err && typeof err === "object" ? err : {};
3594
+ const hasSerializedCode = typeof src.code === "number" && HARDWARE_ERROR_CODE_VALUES.has(src.code);
3595
+ const mapped = hasSerializedCode ? {
3596
+ code: src.code,
3597
+ message: typeof src.message === "string" ? src.message : "Unknown Ledger error",
3598
+ appName: typeof src.appName === "string" ? src.appName : opts?.defaultAppName
3599
+ } : mapLedgerError(err, opts);
3600
+ const error = new Error(mapped.message);
2612
3601
  Object.assign(error, {
2613
3602
  code: mapped.code,
2614
3603
  appName: mapped.appName,
2615
3604
  _tag: src._tag,
2616
3605
  errorCode: src.errorCode,
2617
- _lastStep: src._lastStep
3606
+ _lastStep: src._lastStep,
3607
+ _deviceActionSteps: src._deviceActionSteps
2618
3608
  });
2619
3609
  Object.defineProperty(error, "originalError", {
2620
3610
  value: err,
@@ -2625,13 +3615,6 @@ var LedgerConnectorBase = class {
2625
3615
  return error;
2626
3616
  }
2627
3617
  };
2628
-
2629
- // src/utils/bleIdentity.ts
2630
- function extractBleHexId(name) {
2631
- if (!name) return void 0;
2632
- const match = name.match(/\b([0-9A-Fa-f]{4})$/);
2633
- return match ? match[1].toUpperCase() : void 0;
2634
- }
2635
3618
  // Annotate the CommonJS export names for ESM import in node:
2636
3619
  0 && (module.exports = {
2637
3620
  AppManager,
@@ -2643,12 +3626,15 @@ function extractBleHexId(name) {
2643
3626
  SignerEth,
2644
3627
  SignerManager,
2645
3628
  SignerSol,
3629
+ debugLog,
2646
3630
  deviceActionToPromise,
2647
- extractBleHexId,
2648
- isDebugEnabled,
2649
3631
  isDeviceLockedError,
3632
+ isLedgerBleConnectionType,
3633
+ isLedgerBleDescriptor,
3634
+ isLedgerDmkBleTransport,
2650
3635
  ledgerFailure,
2651
3636
  mapLedgerError,
2652
- setDebugEnabled
3637
+ offSdkEvent,
3638
+ onSdkEvent
2653
3639
  });
2654
3640
  //# sourceMappingURL=index.js.map