@onekeyfe/hwk-ledger-adapter 1.1.26-alpha.9 → 1.1.26

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,26 @@ 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
+ function ledgerFailure(code, error, appName, tag, params) {
58
61
  const payload = { error, code };
59
62
  if (appName !== void 0) payload.appName = appName;
63
+ if (tag !== void 0) payload._tag = tag;
64
+ if (params !== void 0) payload.params = params;
60
65
  return { success: false, payload };
61
66
  }
62
67
  var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
63
68
  var USER_REJECTED_CODES = /* @__PURE__ */ new Set(["6985", "27013"]);
64
69
  var WRONG_APP_CODES = /* @__PURE__ */ new Set(["6e00", "28160", "6d00", "27904", "6a83", "27267"]);
65
70
  var APP_NOT_INSTALLED_CODES = /* @__PURE__ */ new Set(["6807", "26631"]);
66
- var STEP_BLIND_SIGN_FALLBACK = "signer.eth.steps.blindSignTransactionFallback";
71
+ var STEP_BLIND_SIGN_TRANSACTION_FALLBACK = "signer.eth.steps.blindSignTransactionFallback";
67
72
  function getEthAppErrorCode(err) {
68
73
  if (!err || typeof err !== "object") return null;
69
74
  const e = err;
70
- if (e._tag === "EthAppCommandError" && typeof e.errorCode === "string") {
75
+ if (e._tag === ERROR_TAG.EthAppCommand && typeof e.errorCode === "string") {
71
76
  return e.errorCode.toLowerCase();
72
77
  }
73
78
  const orig = e.originalError;
74
- if (orig?._tag === "EthAppCommandError" && typeof orig.errorCode === "string") {
79
+ if (orig?._tag === ERROR_TAG.EthAppCommand && typeof orig.errorCode === "string") {
75
80
  return orig.errorCode.toLowerCase();
76
81
  }
77
82
  return null;
@@ -128,13 +133,8 @@ function mapBtcAppError(hex) {
128
133
  return null;
129
134
  }
130
135
  }
131
- function mapEthAppError(ethCode, lastStep) {
136
+ function mapEthAppErrorCode(ethCode) {
132
137
  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
138
  case "6984":
139
139
  return import_hwk_adapter_core.HardwareErrorCode.EvmClearSignPluginMissing;
140
140
  case "6a84":
@@ -147,16 +147,133 @@ function mapEthAppError(ethCode, lastStep) {
147
147
  return null;
148
148
  }
149
149
  }
150
+ function classifyEthAppError(err) {
151
+ const ethCode = getEthAppErrorCode(err);
152
+ if (!ethCode) return null;
153
+ if (ethCode === "6a80") {
154
+ return hasBlindSignFallbackStep(err) ? import_hwk_adapter_core.HardwareErrorCode.EvmBlindSigningRequired : null;
155
+ }
156
+ return mapEthAppErrorCode(ethCode);
157
+ }
158
+ var ERROR_TAG = {
159
+ // SDK-mint
160
+ DeviceNotAdvertising: "DeviceNotAdvertisingError",
161
+ // BLE scan miss
162
+ DeviceNotInDiscoveryCache: "DeviceNotInDiscoveryCacheError",
163
+ // dm.connect() before enumerate
164
+ BlePairingTimeout: "BlePairingTimeoutError",
165
+ // SMP 30s timeout
166
+ BleGattBondingFailed: "BleGattBondingFailedError",
167
+ // other GATT failure
168
+ UserAborted: "UserAborted",
169
+ DeviceAppStuck: "DeviceAppStuck",
170
+ // chain app wedged (APDU 0x6901)
171
+ DeviceTransportStuck: "DeviceTransportStuck",
172
+ // DMK transport queue wedged
173
+ // DMK-reuse (DMK throws same string; we synthesize too)
174
+ DeviceLocked: "DeviceLockedError",
175
+ DeviceNotRecognized: "DeviceNotRecognizedError",
176
+ DeviceSessionNotFound: "DeviceSessionNotFound",
177
+ OpenAppCommand: "OpenAppCommandError",
178
+ // DMK-only (read only)
179
+ EthAppCommand: "EthAppCommandError",
180
+ UserRefusedOnDevice: "UserRefusedOnDevice",
181
+ WrongAppOpened: "WrongAppOpenedError",
182
+ InvalidStatusWord: "InvalidStatusWordError",
183
+ AlreadySendingApdu: "AlreadySendingApduError",
184
+ UnknownDeviceExchange: "UnknownDeviceExchangeError",
185
+ NoAccessibleDevice: "NoAccessibleDeviceError",
186
+ UnknownDevice: "UnknownDeviceError",
187
+ DeviceSessionRefresher: "DeviceSessionRefresherError",
188
+ DeviceNotInitialized: "DeviceNotInitializedError",
189
+ OpeningConnection: "OpeningConnectionError",
190
+ DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
191
+ DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
192
+ Disconnect: "DisconnectError",
193
+ ReconnectionFailed: "ReconnectionFailedError",
194
+ WebHIDDisconnect: "WebHIDDisconnectError",
195
+ // ble-plx surfaces this when GATT notification setup fails — typical
196
+ // outcome when the user doesn't confirm pairing on the device, or the
197
+ // existing bond is invalid. Observed in production after ~30s.
198
+ PairingRefused: "PairingRefusedError"
199
+ };
150
200
  function isDeviceLockedError(err) {
151
201
  if (!err || typeof err !== "object") return false;
152
202
  const e = err;
153
203
  if (e.errorCode != null && LOCKED_ERROR_CODES.has(String(e.errorCode))) return true;
154
204
  if (e.statusCode != null && LOCKED_ERROR_CODES.has(String(e.statusCode))) return true;
155
- if (e._tag === "DeviceLockedError") return true;
205
+ if (e._tag === ERROR_TAG.DeviceLocked) return true;
156
206
  if (e.originalError != null && isDeviceLockedError(e.originalError)) return true;
157
207
  if (e.error != null && e._tag && isDeviceLockedError(e.error)) return true;
158
208
  return false;
159
209
  }
210
+ function isDeviceNotAdvertisingError(err) {
211
+ if (!err || typeof err !== "object") return false;
212
+ return err._tag === ERROR_TAG.DeviceNotAdvertising;
213
+ }
214
+ var PAIRING_FAILURE_TAGS = /* @__PURE__ */ new Set([
215
+ ERROR_TAG.BlePairingTimeout,
216
+ ERROR_TAG.BleGattBondingFailed,
217
+ ERROR_TAG.PairingRefused
218
+ ]);
219
+ function isBlePairingFailureError(err) {
220
+ if (!err || typeof err !== "object") return false;
221
+ const tag = err._tag;
222
+ if (tag && PAIRING_FAILURE_TAGS.has(tag)) return true;
223
+ const orig = err.originalError;
224
+ if (orig != null && isBlePairingFailureError(orig)) return true;
225
+ return false;
226
+ }
227
+ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
228
+ ERROR_TAG.DeviceLocked,
229
+ ERROR_TAG.DeviceNotAdvertising,
230
+ ERROR_TAG.BlePairingTimeout,
231
+ ERROR_TAG.BleGattBondingFailed,
232
+ ERROR_TAG.PairingRefused,
233
+ ERROR_TAG.DeviceNotRecognized,
234
+ ERROR_TAG.NoAccessibleDevice,
235
+ ERROR_TAG.UnknownDevice,
236
+ ERROR_TAG.DeviceSessionNotFound,
237
+ ERROR_TAG.DeviceSessionRefresher,
238
+ ERROR_TAG.DeviceNotInitialized,
239
+ ERROR_TAG.OpeningConnection,
240
+ ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
241
+ ERROR_TAG.DeviceDisconnectedWhileSending,
242
+ ERROR_TAG.Disconnect,
243
+ ERROR_TAG.ReconnectionFailed,
244
+ ERROR_TAG.WebHIDDisconnect
245
+ ]);
246
+ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
247
+ ERROR_TAG.NoAccessibleDevice,
248
+ ERROR_TAG.UnknownDevice,
249
+ ERROR_TAG.DeviceNotInitialized,
250
+ // SDK-internal: dm.connect() called before _discovered was populated.
251
+ // Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
252
+ ERROR_TAG.DeviceNotInDiscoveryCache
253
+ ]);
254
+ var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
255
+ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
256
+ ERROR_TAG.DeviceNotRecognized,
257
+ ERROR_TAG.DeviceSessionNotFound,
258
+ ERROR_TAG.DeviceSessionRefresher,
259
+ ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
260
+ ERROR_TAG.DeviceDisconnectedWhileSending,
261
+ ERROR_TAG.Disconnect,
262
+ ERROR_TAG.ReconnectionFailed,
263
+ ERROR_TAG.WebHIDDisconnect
264
+ ]);
265
+ function isConnectionLevelError(err) {
266
+ if (!err || typeof err !== "object") return false;
267
+ const tag = err._tag;
268
+ if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
269
+ const e = err;
270
+ if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
271
+ if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
272
+ return false;
273
+ }
274
+ function isKnownConnectionTag(tag) {
275
+ return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
276
+ }
160
277
  function hasStatusCode(err, codeSet) {
161
278
  if (!err || typeof err !== "object") return false;
162
279
  const e = err;
@@ -166,10 +283,39 @@ function hasStatusCode(err, codeSet) {
166
283
  if (e.error != null && e._tag && hasStatusCode(e.error, codeSet)) return true;
167
284
  return false;
168
285
  }
286
+ function hasBlindSignFallbackStep(err) {
287
+ if (!err || typeof err !== "object") return false;
288
+ const e = err;
289
+ if (e._lastStep === STEP_BLIND_SIGN_TRANSACTION_FALLBACK) return true;
290
+ if (Array.isArray(e._deviceActionSteps) && e._deviceActionSteps.includes(STEP_BLIND_SIGN_TRANSACTION_FALLBACK)) {
291
+ return true;
292
+ }
293
+ if (e.originalError != null && hasBlindSignFallbackStep(e.originalError)) return true;
294
+ if (e.error != null && e._tag && hasBlindSignFallbackStep(e.error)) return true;
295
+ return false;
296
+ }
297
+ function isDeviceNotFoundError(err) {
298
+ if (!err || typeof err !== "object") return false;
299
+ const tag = err._tag;
300
+ if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
301
+ const e = err;
302
+ if (e.originalError != null && isDeviceNotFoundError(e.originalError)) return true;
303
+ if (e.error != null && e._tag && isDeviceNotFoundError(e.error)) return true;
304
+ return false;
305
+ }
306
+ function isDeviceBusyError(err) {
307
+ if (!err || typeof err !== "object") return false;
308
+ const tag = err._tag;
309
+ if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
310
+ const e = err;
311
+ if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
312
+ if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
313
+ return false;
314
+ }
169
315
  function isUserRejectedError(err) {
170
316
  if (!err || typeof err !== "object") return false;
171
317
  const e = err;
172
- if (e._tag === "UserRefusedOnDevice") return true;
318
+ if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
173
319
  if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
174
320
  if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
175
321
  return false;
@@ -177,7 +323,7 @@ function isUserRejectedError(err) {
177
323
  function isWrongAppError(err) {
178
324
  if (!err || typeof err !== "object") return false;
179
325
  const e = err;
180
- if (e._tag === "WrongAppOpenedError" || e._tag === "InvalidStatusWordError") {
326
+ if (e._tag === ERROR_TAG.WrongAppOpened || e._tag === ERROR_TAG.InvalidStatusWord) {
181
327
  if (hasStatusCode(err, WRONG_APP_CODES)) return true;
182
328
  }
183
329
  if (typeof e.message === "string") {
@@ -191,7 +337,6 @@ function isWrongAppError(err) {
191
337
  function isAppNotInstalledError(err) {
192
338
  if (!err || typeof err !== "object") return false;
193
339
  const e = err;
194
- if (e._tag === "OpenAppCommandError") return true;
195
340
  if (typeof e.message === "string" && /unknown application/i.test(e.message)) return true;
196
341
  if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
197
342
  return false;
@@ -199,7 +344,8 @@ function isAppNotInstalledError(err) {
199
344
  function isDeviceDisconnectedError(err) {
200
345
  if (!err || typeof err !== "object") return false;
201
346
  const e = err;
202
- if (e._tag === "DeviceNotRecognizedError" || e._tag === "DeviceSessionNotFound") return true;
347
+ const tag = e._tag;
348
+ if (typeof tag === "string" && DEVICE_DISCONNECTED_TAGS.has(tag)) return true;
203
349
  if (typeof e.message === "string") {
204
350
  const msg = e.message.toLowerCase();
205
351
  if (msg.includes("disconnected") || msg.includes("not found") || msg.includes("no device") || msg.includes("unplugged"))
@@ -219,6 +365,21 @@ function isTimeoutError(err) {
219
365
  if (e.code === import_hwk_adapter_core.HardwareErrorCode.OperationTimeout) return true;
220
366
  return false;
221
367
  }
368
+ function isAppStuckByApdu(err) {
369
+ if (!err || typeof err !== "object") return false;
370
+ const tag = err._tag;
371
+ if (tag === ERROR_TAG.DeviceAppStuck) return true;
372
+ return extractApduHex(err) === "6901";
373
+ }
374
+ function isTransportStuck(err) {
375
+ if (!err || typeof err !== "object") return false;
376
+ const tag = err._tag;
377
+ return tag === ERROR_TAG.DeviceTransportStuck || // already wrapped form
378
+ tag === ERROR_TAG.UnknownDeviceExchange || tag === ERROR_TAG.AlreadySendingApdu;
379
+ }
380
+ function isStuckAppStateError(err) {
381
+ return isAppStuckByApdu(err) || isTransportStuck(err);
382
+ }
222
383
  function mapLedgerError(err, opts) {
223
384
  let originalMessage = "Unknown Ledger error";
224
385
  if (err instanceof Error) {
@@ -230,20 +391,24 @@ function mapLedgerError(err, opts) {
230
391
  let code;
231
392
  if (isDeviceLockedError(err)) {
232
393
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceLocked;
394
+ } else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
395
+ code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound;
396
+ } else if (isDeviceBusyError(err)) {
397
+ code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
398
+ } else if (isBlePairingFailureError(err)) {
399
+ code = import_hwk_adapter_core.HardwareErrorCode.BlePairingTimeout;
233
400
  } else if (isUserRejectedError(err)) {
234
401
  code = import_hwk_adapter_core.HardwareErrorCode.UserRejected;
235
402
  } else if (isWrongAppError(err)) {
236
403
  code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
237
404
  } else if (isAppNotInstalledError(err)) {
238
- code = import_hwk_adapter_core.HardwareErrorCode.AppNotOpen;
405
+ code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
239
406
  } else if (isDeviceDisconnectedError(err)) {
240
407
  code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
241
408
  } else if (isTimeoutError(err)) {
242
409
  code = import_hwk_adapter_core.HardwareErrorCode.OperationTimeout;
243
410
  } 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;
411
+ const ethMapped = classifyEthAppError(err);
247
412
  const apduHex = ethMapped ? null : extractApduHex(err);
248
413
  const chainMapped = apduHex ? mapSolanaAppError(apduHex) ?? mapTronAppError(apduHex) ?? mapBtcAppError(apduHex) : null;
249
414
  code = ethMapped ?? chainMapped ?? import_hwk_adapter_core.HardwareErrorCode.UnknownError;
@@ -253,38 +418,100 @@ function mapLedgerError(err, opts) {
253
418
  return { code, message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage), appName };
254
419
  }
255
420
 
256
- // src/utils/debugLog.ts
257
- var enabled = false;
258
- function setDebugEnabled(value) {
259
- enabled = value;
421
+ // src/utils/ledgerDmkTransport.ts
422
+ function isLedgerDmkBleTransport(transport) {
423
+ const normalized = transport?.toUpperCase();
424
+ return normalized === "BLE" || normalized?.endsWith("_BLE") === true;
260
425
  }
261
- function isDebugEnabled() {
262
- return enabled;
426
+ function isLedgerBleConnectionType(connectionType) {
427
+ return connectionType === "ble";
263
428
  }
264
- function debugLog(...args) {
265
- if (enabled) {
266
- console.debug(...args);
429
+ function isLedgerBleDescriptor(connectionType, descriptor) {
430
+ return isLedgerBleConnectionType(connectionType) || isLedgerDmkBleTransport(descriptor.transport);
431
+ }
432
+
433
+ // src/utils/sdkEventBus.ts
434
+ var listeners = /* @__PURE__ */ new Set();
435
+ function onSdkEvent(listener) {
436
+ listeners.add(listener);
437
+ return () => {
438
+ listeners.delete(listener);
439
+ };
440
+ }
441
+ function offSdkEvent(listener) {
442
+ listeners.delete(listener);
443
+ }
444
+ function emitSdkEvent(event) {
445
+ if (listeners.size === 0) return;
446
+ for (const listener of listeners) {
447
+ try {
448
+ listener(event);
449
+ } catch {
450
+ }
267
451
  }
268
452
  }
269
- function debugError(...args) {
270
- if (enabled) {
271
- console.error(...args);
453
+ function emitLog(level, ...args) {
454
+ if (listeners.size === 0) return;
455
+ const message = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
456
+ emitSdkEvent({ type: "log", level, message });
457
+ }
458
+ function safeStringify(value) {
459
+ if (value instanceof Error) {
460
+ return value.stack ? `${value.message}
461
+ ${value.stack}` : value.message;
462
+ }
463
+ try {
464
+ return JSON.stringify(value);
465
+ } catch {
466
+ return String(value);
272
467
  }
273
468
  }
274
469
 
470
+ // src/utils/debugLog.ts
471
+ function debugLog(...args) {
472
+ emitLog("debug", ...args);
473
+ }
474
+ function debugError(...args) {
475
+ emitLog("error", ...args);
476
+ }
477
+
275
478
  // src/adapter/LedgerAdapter.ts
276
479
  function formatDeviceMismatchError(expected, actual) {
277
480
  return `Wrong device: expected ${expected}, got ${actual}`;
278
481
  }
482
+ var BTC_HIGH_INDEX_THRESHOLD = 100;
483
+ function btcAccountIndexFromPath(path) {
484
+ const segments = path.replace(/^m\//, "").split("/");
485
+ if (segments.length < 3) return null;
486
+ const accountSeg = segments[2].replace(/['h]$/i, "");
487
+ const accountIndex = parseInt(accountSeg, 10);
488
+ return Number.isFinite(accountIndex) ? accountIndex : null;
489
+ }
279
490
  var _LedgerAdapter = class _LedgerAdapter {
280
491
  constructor(connector, options) {
281
492
  this.vendor = "ledger";
282
493
  this.emitter = new import_hwk_adapter_core2.TypedEventEmitter();
283
- // Device cache: tracks discovered devices from connector events
284
494
  this._discoveredDevices = /* @__PURE__ */ new Map();
285
- // Session tracking: maps connectId -> sessionId
286
495
  this._sessions = /* @__PURE__ */ new Map();
287
496
  this._uiRegistry = new import_hwk_adapter_core2.UiRequestRegistry();
497
+ // BTC App rejects account index >= 100 unless display=true. Cached per
498
+ // adapter instance: first 100+ path asks the user once via UI request,
499
+ // subsequent 100+ paths in the same session auto-promote silently.
500
+ // The Ledger device itself still requires a per-call confirmation — that's
501
+ // the Ledger app's safety boundary, not ours to bypass.
502
+ this._btcHighIndexConfirmedThisSession = false;
503
+ // Shared across concurrent callers — only `cancel()` aborts.
504
+ this._doConnectAbortController = null;
505
+ // ---------------------------------------------------------------------------
506
+ // Private helpers
507
+ // ---------------------------------------------------------------------------
508
+ /**
509
+ * Ensure at least one device is connected and return a valid connectId.
510
+ *
511
+ * - If a session already exists for the given connectId, reuse it.
512
+ * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
513
+ * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
514
+ */
288
515
  // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
289
516
  this._connectingPromise = null;
290
517
  // ---------------------------------------------------------------------------
@@ -315,39 +542,23 @@ var _LedgerAdapter = class _LedgerAdapter {
315
542
  };
316
543
  this.connector = connector;
317
544
  this._handleSelectDevice = options?.handleSelectDevice ?? false;
318
- this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue({
319
- emit: (event, data) => this.emitter.emit(event, data),
320
- uiRegistry: this._uiRegistry
321
- });
545
+ this._jobQueue = new import_hwk_adapter_core2.DeviceJobQueue();
322
546
  this.registerEventListeners();
323
547
  }
324
- /**
325
- * Classify a method's interruptibility.
326
- * - Signing / typed data / transaction → 'confirm' (user may decide via preemption UI)
327
- * - Read-only queries (getAddress / getPublicKey / getMasterFingerprint) → 'safe'
328
- * (auto-cancels any pending read for the same device)
329
- */
330
- static _getInterruptibility(method) {
331
- if (method.toLowerCase().includes("sign")) return "confirm";
332
- return "safe";
333
- }
334
- // ---------------------------------------------------------------------------
335
548
  // Transport
336
- // ---------------------------------------------------------------------------
337
- // Transport is decided at connector creation time. These methods
338
- // satisfy the IHardwareWallet interface with sensible defaults.
339
549
  get activeTransport() {
340
- return this.connector.connectionType === "ble" ? "ble" : "hid";
550
+ return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
341
551
  }
342
552
  getAvailableTransports() {
343
553
  return this.activeTransport ? [this.activeTransport] : [];
344
554
  }
345
- async switchTransport(_type) {
555
+ // Connector is bound at construction; switching requires a new adapter.
556
+ switchTransport(_type) {
557
+ return Promise.resolve();
346
558
  }
347
- // ---------------------------------------------------------------------------
348
559
  // Lifecycle
349
- // ---------------------------------------------------------------------------
350
- async init(_config) {
560
+ init(_config) {
561
+ return Promise.resolve();
351
562
  }
352
563
  /**
353
564
  * Clear cached device/session state without tearing down the adapter.
@@ -360,6 +571,7 @@ var _LedgerAdapter = class _LedgerAdapter {
360
571
  this._connectingPromise = null;
361
572
  this._uiRegistry.reset();
362
573
  this._jobQueue.clear();
574
+ this._btcHighIndexConfirmedThisSession = false;
363
575
  }
364
576
  async dispose() {
365
577
  this._uiRegistry.reset();
@@ -368,6 +580,7 @@ var _LedgerAdapter = class _LedgerAdapter {
368
580
  this.connector.reset();
369
581
  this._discoveredDevices.clear();
370
582
  this._sessions.clear();
583
+ this._btcHighIndexConfirmedThisSession = false;
371
584
  this.emitter.removeAllListeners();
372
585
  }
373
586
  uiResponse(response) {
@@ -376,10 +589,16 @@ var _LedgerAdapter = class _LedgerAdapter {
376
589
  // ---------------------------------------------------------------------------
377
590
  // Device management
378
591
  // ---------------------------------------------------------------------------
379
- async searchDevices() {
592
+ async searchDevices(options) {
593
+ if (options?.resetSession) {
594
+ this._doConnectAbortController?.abort();
595
+ this._sessions.clear();
596
+ this._connectingPromise = null;
597
+ this._doConnectAbortController = null;
598
+ this._btcHighIndexConfirmedThisSession = false;
599
+ }
380
600
  await this._ensureDevicePermission();
381
601
  const devices = await this.connector.searchDevices();
382
- debugLog("[DMK] adapter.searchDevices raw:", JSON.stringify(devices));
383
602
  this._discoveredDevices.clear();
384
603
  for (const d of devices) {
385
604
  if (d.connectId) {
@@ -389,11 +608,26 @@ var _LedgerAdapter = class _LedgerAdapter {
389
608
  if (this._discoveredDevices.size === 0) {
390
609
  await this._ensureDevicePermission();
391
610
  }
611
+ debugLog(
612
+ `[LedgerAdapter] searchDevices() return count=${this._discoveredDevices.size} ids=[${[
613
+ ...this._discoveredDevices.keys()
614
+ ].join(",")}]`
615
+ );
392
616
  return Array.from(this._discoveredDevices.values());
393
617
  }
618
+ static _createDeviceBusyError(method) {
619
+ return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
620
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceBusy
621
+ });
622
+ }
394
623
  async connectDevice(connectId) {
395
- await this._ensureDevicePermission(connectId);
396
624
  try {
625
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
626
+ throw Object.assign(new Error("Ledger BLE connectId is required."), {
627
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
628
+ });
629
+ }
630
+ await this._ensureDevicePermission(connectId);
397
631
  const session = await this.connector.connect(connectId);
398
632
  this._sessions.set(connectId, session.sessionId);
399
633
  if (session.deviceInfo) {
@@ -429,7 +663,6 @@ var _LedgerAdapter = class _LedgerAdapter {
429
663
  // Chain call helper
430
664
  // ---------------------------------------------------------------------------
431
665
  async callChain(connectId, deviceId, chain, method, params, skipFingerprint = false) {
432
- await this._ensureDevicePermission(connectId, deviceId);
433
666
  try {
434
667
  const result = await this.connectorCall(connectId, method, params, {
435
668
  chain,
@@ -441,45 +674,12 @@ var _LedgerAdapter = class _LedgerAdapter {
441
674
  return this.errorToFailure(err);
442
675
  }
443
676
  }
444
- /**
445
- * Batch version of callChain — checks permission once,
446
- * fingerprint is verified on the first call inside connectorCall.
447
- */
448
- async callChainBatch(connectId, deviceId, chain, method, params, onProgress, skipFingerprint = false) {
449
- await this._ensureDevicePermission(connectId, deviceId);
450
- const results = [];
451
- for (let i = 0; i < params.length; i++) {
452
- try {
453
- const result = await this.connectorCall(connectId, method, params[i], {
454
- chain,
455
- deviceId,
456
- // Only verify fingerprint on the first call in the batch
457
- skipFingerprint: skipFingerprint || i > 0
458
- });
459
- results.push(result);
460
- onProgress?.({ index: i, total: params.length });
461
- } catch (err) {
462
- return this.errorToFailure(err);
463
- }
464
- }
465
- return (0, import_hwk_adapter_core2.success)(results);
466
- }
467
677
  // ---------------------------------------------------------------------------
468
678
  // EVM chain methods
469
679
  // ---------------------------------------------------------------------------
470
680
  evmGetAddress(connectId, deviceId, params) {
471
681
  return this.callChain(connectId, deviceId, "evm", "evmGetAddress", params);
472
682
  }
473
- evmGetAddresses(connectId, deviceId, params, onProgress) {
474
- return this.callChainBatch(
475
- connectId,
476
- deviceId,
477
- "evm",
478
- "evmGetAddress",
479
- params,
480
- onProgress
481
- );
482
- }
483
683
  evmSignTransaction(connectId, deviceId, params) {
484
684
  return this.callChain(connectId, deviceId, "evm", "evmSignTransaction", params);
485
685
  }
@@ -495,16 +695,6 @@ var _LedgerAdapter = class _LedgerAdapter {
495
695
  btcGetAddress(connectId, deviceId, params) {
496
696
  return this.callChain(connectId, deviceId, "btc", "btcGetAddress", params);
497
697
  }
498
- btcGetAddresses(connectId, deviceId, params, onProgress) {
499
- return this.callChainBatch(
500
- connectId,
501
- deviceId,
502
- "btc",
503
- "btcGetAddress",
504
- params,
505
- onProgress
506
- );
507
- }
508
698
  btcGetPublicKey(connectId, deviceId, params) {
509
699
  return this.callChain(connectId, deviceId, "btc", "btcGetPublicKey", params);
510
700
  }
@@ -532,16 +722,6 @@ var _LedgerAdapter = class _LedgerAdapter {
532
722
  solGetAddress(connectId, deviceId, params) {
533
723
  return this.callChain(connectId, deviceId, "sol", "solGetAddress", params);
534
724
  }
535
- solGetAddresses(connectId, deviceId, params, onProgress) {
536
- return this.callChainBatch(
537
- connectId,
538
- deviceId,
539
- "sol",
540
- "solGetAddress",
541
- params,
542
- onProgress
543
- );
544
- }
545
725
  solSignTransaction(connectId, deviceId, params) {
546
726
  return this.callChain(connectId, deviceId, "sol", "solSignTransaction", params);
547
727
  }
@@ -552,38 +732,13 @@ var _LedgerAdapter = class _LedgerAdapter {
552
732
  // TRON chain methods
553
733
  // ---------------------------------------------------------------------------
554
734
  tronGetAddress(connectId, deviceId, params) {
555
- return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params, true);
556
- }
557
- tronGetAddresses(connectId, deviceId, params, onProgress) {
558
- return this.callChainBatch(
559
- connectId,
560
- deviceId,
561
- "tron",
562
- "tronGetAddress",
563
- params,
564
- onProgress,
565
- true
566
- );
735
+ return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params);
567
736
  }
568
737
  tronSignTransaction(connectId, deviceId, params) {
569
- return this.callChain(
570
- connectId,
571
- deviceId,
572
- "tron",
573
- "tronSignTransaction",
574
- params,
575
- true
576
- );
738
+ return this.callChain(connectId, deviceId, "tron", "tronSignTransaction", params);
577
739
  }
578
740
  tronSignMessage(connectId, deviceId, params) {
579
- return this.callChain(
580
- connectId,
581
- deviceId,
582
- "tron",
583
- "tronSignMessage",
584
- params,
585
- true
586
- );
741
+ return this.callChain(connectId, deviceId, "tron", "tronSignMessage", params);
587
742
  }
588
743
  on(event, listener) {
589
744
  this.emitter.on(event, listener);
@@ -592,30 +747,41 @@ var _LedgerAdapter = class _LedgerAdapter {
592
747
  this.emitter.off(event, listener);
593
748
  }
594
749
  cancel(connectId) {
595
- const sessionId = this._sessions.get(connectId) ?? connectId;
596
- this._jobQueue.forceCancelActive(connectId || "__ledger_default__");
597
- void this.connector.cancel(sessionId);
750
+ const userAbortReason = Object.assign(new Error("User aborted operation"), {
751
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
752
+ _tag: ERROR_TAG.UserAborted
753
+ });
754
+ this._lastCancelReason = userAbortReason;
755
+ setTimeout(() => {
756
+ if (this._lastCancelReason === userAbortReason) {
757
+ this._lastCancelReason = void 0;
758
+ }
759
+ }, 2e3);
760
+ this._uiRegistry.cancel();
761
+ if (connectId) {
762
+ this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
763
+ } else {
764
+ this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
765
+ }
766
+ if (connectId) {
767
+ const sessionId = this._sessions.get(connectId) ?? connectId;
768
+ void this.connector.cancel(sessionId);
769
+ } else {
770
+ for (const sid of this._sessions.values()) void this.connector.cancel(sid);
771
+ }
772
+ if (this._connectingPromise) {
773
+ this._doConnectAbortController?.abort(userAbortReason);
774
+ }
598
775
  }
599
776
  // ---------------------------------------------------------------------------
600
777
  // Chain fingerprint
601
778
  // ---------------------------------------------------------------------------
602
779
  async getChainFingerprint(connectId, deviceId, chain) {
603
- debugLog(
604
- "[LedgerAdapter] getChainFingerprint called, chain:",
605
- chain,
606
- "connectId:",
607
- connectId || "(empty)",
608
- "sessions:",
609
- this._sessions.size
610
- );
611
- await this._ensureDevicePermission(connectId, deviceId);
612
- debugLog("[LedgerAdapter] getChainFingerprint permission ok, computing fingerprint");
613
780
  try {
614
781
  const fingerprint = await this._computeChainFingerprint(
615
782
  chain,
616
- (method, params) => this.connectorCall(connectId, method, params)
783
+ (method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
617
784
  );
618
- debugLog("[LedgerAdapter] getChainFingerprint result:", fingerprint?.substring(0, 20));
619
785
  return (0, import_hwk_adapter_core2.success)(fingerprint);
620
786
  } catch (err) {
621
787
  debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
@@ -686,77 +852,226 @@ var _LedgerAdapter = class _LedgerAdapter {
686
852
  if (signal?.aborted) {
687
853
  _LedgerAdapter._throwIfAborted(signal);
688
854
  }
855
+ const waitPromise = this._uiRegistry.wait(
856
+ import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
857
+ );
689
858
  this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
690
859
  type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT,
691
860
  payload: {
861
+ vendor: "ledger",
862
+ reason: "device-not-found",
692
863
  message: "Please connect and unlock your Ledger device"
693
864
  }
694
865
  });
695
- const waitPromise = this._uiRegistry.wait(
696
- import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT
697
- );
698
866
  let payload;
699
- if (signal) {
700
- const onAbort = () => {
701
- this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT);
702
- };
703
- signal.addEventListener("abort", onAbort, { once: true });
704
- try {
867
+ try {
868
+ if (signal) {
869
+ const onAbort = () => {
870
+ this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_CONNECT);
871
+ };
872
+ signal.addEventListener("abort", onAbort, { once: true });
873
+ try {
874
+ payload = await waitPromise;
875
+ } finally {
876
+ signal.removeEventListener("abort", onAbort);
877
+ }
878
+ } else {
705
879
  payload = await waitPromise;
706
- } finally {
707
- signal.removeEventListener("abort", onAbort);
708
880
  }
709
- } else {
710
- payload = await waitPromise;
881
+ } catch (err) {
882
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
883
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
884
+ payload: {}
885
+ });
886
+ throw Object.assign(new Error("User cancelled Ledger connection"), {
887
+ _tag: ERROR_TAG.UserAborted,
888
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
889
+ cause: err
890
+ });
711
891
  }
712
892
  if (!payload?.confirmed) {
713
893
  throw Object.assign(new Error("User cancelled Ledger connection"), {
714
- _tag: "DeviceNotRecognizedError"
894
+ _tag: ERROR_TAG.UserAborted,
895
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
715
896
  });
716
897
  }
898
+ await new Promise((resolve) => {
899
+ setTimeout(resolve, 800);
900
+ });
717
901
  }
718
- async ensureConnected(connectId) {
719
- if (connectId && this._sessions.has(connectId)) {
720
- return connectId;
721
- }
722
- if (this._sessions.size > 0) {
723
- const firstKey = this._sessions.keys().next().value;
724
- return firstKey;
902
+ /**
903
+ * Decide whether a BTC pubkey call needs `showOnDevice=true` because of
904
+ * the BTC App's account-index policy, asking the user once per session.
905
+ *
906
+ * Returns the params to pass through (with `showOnDevice` possibly
907
+ * promoted), or `null` if the user declined.
908
+ */
909
+ async _gateBtcHighIndex(params) {
910
+ const accountIndex = btcAccountIndexFromPath(params.path);
911
+ if (params.showOnDevice) return params;
912
+ if (accountIndex === null || accountIndex < BTC_HIGH_INDEX_THRESHOLD) {
913
+ return params;
725
914
  }
726
- if (this._connectingPromise) {
727
- return this._connectingPromise;
915
+ if (this._btcHighIndexConfirmedThisSession) {
916
+ return { ...params, showOnDevice: true };
728
917
  }
729
- this._connectingPromise = this._doConnect();
918
+ const confirmed = await this._waitForBtcHighIndexConfirm(params.path, accountIndex);
919
+ if (!confirmed) return null;
920
+ this._btcHighIndexConfirmedThisSession = true;
921
+ return { ...params, showOnDevice: true };
922
+ }
923
+ async _waitForBtcHighIndexConfirm(path, accountIndex) {
924
+ const waitPromise = this._uiRegistry.wait(
925
+ import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
926
+ );
927
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
928
+ type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
929
+ payload: {
930
+ vendor: "ledger",
931
+ path,
932
+ accountIndex
933
+ }
934
+ });
730
935
  try {
731
- return await this._connectingPromise;
732
- } finally {
733
- this._connectingPromise = null;
936
+ const payload = await waitPromise;
937
+ return !!payload?.confirmed;
938
+ } catch (err) {
939
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
940
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
941
+ payload: {}
942
+ });
943
+ return false;
734
944
  }
735
945
  }
736
- async _doConnect() {
737
- for (let attempt = 0; attempt < _LedgerAdapter.MAX_DEVICE_RETRY; attempt++) {
738
- const devices = await this.searchDevices();
946
+ // Layer 1 entry. Caller signal only races the outer awaiter; the shared
947
+ // `_doConnect` runs under its own internal controller so caller A's cancel
948
+ // doesn't kill caller B's await.
949
+ async ensureConnected(connectId, signal) {
950
+ if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
951
+ if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
952
+ throw Object.assign(new Error("Ledger BLE connectId is required."), {
953
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
954
+ });
955
+ }
956
+ if (connectId && this._sessions.has(connectId)) return connectId;
957
+ if (this._sessions.size > 0) {
958
+ return this._sessions.keys().next().value;
959
+ }
960
+ if (!this._connectingPromise) {
961
+ this._doConnectAbortController = new AbortController();
962
+ const innerSignal = this._doConnectAbortController.signal;
963
+ this._connectingPromise = (async () => {
964
+ try {
965
+ return await this._doConnect(innerSignal, connectId);
966
+ } finally {
967
+ this._connectingPromise = null;
968
+ this._doConnectAbortController = null;
969
+ }
970
+ })();
971
+ }
972
+ return this._abortable(signal, this._connectingPromise);
973
+ }
974
+ // Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
975
+ // Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
976
+ // throw DeviceNotFound so the user is kicked out of the loop.
977
+ async _doConnect(internalSignal, targetConnectId) {
978
+ if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
979
+ try {
980
+ return await this._connectDeviceOrThrow(targetConnectId);
981
+ } catch (err) {
982
+ if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
983
+ throw err;
984
+ }
985
+ this._discoveredDevices.delete(targetConnectId);
986
+ if (isDeviceDisconnectedError(err)) {
987
+ try {
988
+ this.connector.reset?.();
989
+ } catch {
990
+ }
991
+ }
992
+ }
993
+ }
994
+ let confirms = 0;
995
+ while (!internalSignal.aborted) {
996
+ this.emitter.emit("ui-event", {
997
+ type: import_hwk_adapter_core2.EConnectorInteraction.Searching,
998
+ payload: { sessionId: "" }
999
+ });
1000
+ let devices = await this.searchDevices();
1001
+ if (devices.length === 0) {
1002
+ for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
1003
+ await new Promise((resolve) => {
1004
+ setTimeout(resolve, 1e3);
1005
+ });
1006
+ devices = await this.searchDevices();
1007
+ if (devices.length > 0) break;
1008
+ }
1009
+ }
739
1010
  if (devices.length > 0) {
740
- return this._connectFirstOrSelect(devices);
1011
+ try {
1012
+ return await this._connectFirstOrSelect(devices, targetConnectId);
1013
+ } catch (err) {
1014
+ if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
1015
+ throw err;
1016
+ }
1017
+ this._discoveredDevices.clear();
1018
+ if (isDeviceDisconnectedError(err)) {
1019
+ try {
1020
+ this.connector.reset?.();
1021
+ } catch {
1022
+ }
1023
+ }
1024
+ }
741
1025
  }
742
- if (attempt < _LedgerAdapter.MAX_DEVICE_RETRY - 1) {
743
- await this._waitForDeviceConnect();
1026
+ if (confirms >= _LedgerAdapter.MAX_DOCONNECT_CONFIRMS) {
1027
+ throw Object.assign(
1028
+ new Error(
1029
+ "Device not connected after multiple attempts. Please ensure your Ledger is awake, unlocked, and in range, then try again."
1030
+ ),
1031
+ { code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound }
1032
+ );
744
1033
  }
1034
+ await this._waitForDeviceConnect(internalSignal);
1035
+ confirms += 1;
745
1036
  }
746
- throw Object.assign(
747
- new Error(
748
- "No Ledger device found after multiple attempts. Please connect and unlock your device."
749
- ),
750
- { _tag: "DeviceNotRecognizedError" }
751
- );
1037
+ _LedgerAdapter._throwIfAborted(internalSignal);
1038
+ throw new Error("_doConnect aborted");
752
1039
  }
753
- async _connectFirstOrSelect(devices) {
1040
+ async _connectFirstOrSelect(devices, targetConnectId) {
1041
+ if (targetConnectId) {
1042
+ const target = devices.find(
1043
+ (d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
1044
+ );
1045
+ if (!target) {
1046
+ const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
1047
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1048
+ });
1049
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1050
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
1051
+ }
1052
+ throw err;
1053
+ }
1054
+ return this._connectDeviceOrThrow(target.connectId);
1055
+ }
1056
+ if (isLedgerBleConnectionType(this.connector.connectionType)) {
1057
+ throw Object.assign(new Error("Ledger BLE connectId is required."), {
1058
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceNotFound
1059
+ });
1060
+ }
754
1061
  const chosenConnectId = devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices);
1062
+ return this._connectDeviceOrThrow(chosenConnectId);
1063
+ }
1064
+ async _connectDeviceOrThrow(chosenConnectId) {
755
1065
  const result = await this.connectDevice(chosenConnectId);
756
1066
  if (!result.success) {
757
- throw Object.assign(new Error(result.payload.error), {
758
- _tag: "DeviceNotRecognizedError"
1067
+ const payload = result.payload;
1068
+ const rethrow = Object.assign(new Error(payload.error), {
1069
+ code: payload.code
759
1070
  });
1071
+ if (payload._tag !== void 0) {
1072
+ rethrow._tag = payload._tag;
1073
+ }
1074
+ throw rethrow;
760
1075
  }
761
1076
  return chosenConnectId;
762
1077
  }
@@ -767,18 +1082,35 @@ var _LedgerAdapter = class _LedgerAdapter {
767
1082
  );
768
1083
  return devices[0].connectId;
769
1084
  }
770
- this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE, {
771
- type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE,
772
- payload: { devices }
773
- });
774
- const response = await this._uiRegistry.wait(
775
- import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE
776
- );
1085
+ let response;
1086
+ try {
1087
+ const waitPromise = this._uiRegistry.wait(
1088
+ import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE
1089
+ );
1090
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE, {
1091
+ type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_SELECT_DEVICE,
1092
+ payload: { devices }
1093
+ });
1094
+ response = await waitPromise;
1095
+ } catch (err) {
1096
+ if (err?._tag === import_hwk_adapter_core2.UI_REQUEST_PREEMPTED_TAG) {
1097
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
1098
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
1099
+ payload: {}
1100
+ });
1101
+ throw Object.assign(new Error("Device selection superseded"), {
1102
+ _tag: ERROR_TAG.UserAborted,
1103
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
1104
+ cause: err
1105
+ });
1106
+ }
1107
+ throw err;
1108
+ }
777
1109
  const chosen = devices.find((d) => d.connectId === response?.sdkConnectId);
778
1110
  if (!chosen) {
779
1111
  throw Object.assign(
780
1112
  new Error(`Selected sdkConnectId '${response?.sdkConnectId}' not in discovered list`),
781
- { _tag: "DeviceNotRecognizedError" }
1113
+ { _tag: ERROR_TAG.DeviceNotRecognized }
782
1114
  );
783
1115
  }
784
1116
  return chosen.connectId;
@@ -791,30 +1123,31 @@ var _LedgerAdapter = class _LedgerAdapter {
791
1123
  * 3. Calls connector.call()
792
1124
  * 4. On disconnect error: clears stale session, re-connects, retries once
793
1125
  */
794
- async connectorCall(connectId, method, params, fingerprint) {
1126
+ async connectorCall(connectId, method, params, fingerprint, permissionDeviceId) {
795
1127
  debugLog("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
796
1128
  const queueKey = connectId || "__ledger_default__";
797
- const interruptibility = _LedgerAdapter._getInterruptibility(method);
798
1129
  return this._jobQueue.enqueue(
799
1130
  queueKey,
800
- async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint),
801
- { interruptibility, label: method }
1131
+ async (signal) => this._runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId),
1132
+ {
1133
+ label: method,
1134
+ rejectIfBusy: true,
1135
+ busyError: _LedgerAdapter._createDeviceBusyError(method)
1136
+ }
802
1137
  );
803
1138
  }
804
1139
  /**
805
- * Race a promise against an abort signal. If the signal fires, rejects with the
806
- * signal's reason (or a generic Error). The underlying connector.call() cannot
807
- * actually be cancelled on Ledger DMK, but the caller gets the abort immediately.
1140
+ * Race a promise against an abort signal. On abort, rejects with
1141
+ * signal.reason instance _lastCancelReason → generic Error('Aborted').
808
1142
  */
809
- static _abortable(signal, promise) {
1143
+ _abortable(signal, promise) {
1144
+ const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
810
1145
  if (signal.aborted) {
811
- return Promise.reject(
812
- signal.reason ?? new Error("Aborted")
813
- );
1146
+ return Promise.reject(getAbortReason());
814
1147
  }
815
1148
  return new Promise((resolve, reject) => {
816
1149
  const onAbort = () => {
817
- reject(signal.reason ?? new Error("Aborted"));
1150
+ reject(getAbortReason());
818
1151
  };
819
1152
  signal.addEventListener("abort", onAbort, { once: true });
820
1153
  promise.then(
@@ -836,40 +1169,49 @@ var _LedgerAdapter = class _LedgerAdapter {
836
1169
  }
837
1170
  }
838
1171
  /** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
839
- async _runConnectorCall(connectId, method, params, signal, fingerprint) {
1172
+ async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId) {
840
1173
  _LedgerAdapter._throwIfAborted(signal);
841
- const resolvedConnectId = await _LedgerAdapter._abortable(
842
- signal,
843
- this.ensureConnected(connectId)
1174
+ await this._ensureDevicePermission(
1175
+ connectId,
1176
+ permissionDeviceId ?? fingerprint?.deviceId,
1177
+ signal
844
1178
  );
845
1179
  _LedgerAdapter._throwIfAborted(signal);
1180
+ let effectiveParams = params;
1181
+ if (method === "btcGetPublicKey") {
1182
+ const gatedParams = await this._gateBtcHighIndex(params);
1183
+ if (gatedParams === null) {
1184
+ throw Object.assign(new Error("User cancelled BTC high-index confirmation"), {
1185
+ _tag: ERROR_TAG.UserAborted,
1186
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted
1187
+ });
1188
+ }
1189
+ effectiveParams = gatedParams;
1190
+ }
1191
+ const resolvedConnectId = await this.ensureConnected(connectId, signal);
846
1192
  const sessionId = this._sessions.get(resolvedConnectId);
847
- debugLog(
848
- "[LedgerAdapter] connectorCall resolved:",
849
- method,
850
- "resolvedConnectId:",
851
- resolvedConnectId,
852
- "sessionId:",
853
- sessionId
854
- );
855
1193
  if (!sessionId) {
856
1194
  throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
857
- _tag: "DeviceSessionNotFound"
1195
+ _tag: ERROR_TAG.DeviceSessionNotFound
858
1196
  });
859
1197
  }
860
- if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
861
- const fp = await _LedgerAdapter._abortable(
862
- signal,
863
- this._verifyDeviceFingerprintWithSession(sessionId, fingerprint.deviceId, fingerprint.chain)
864
- );
865
- if (!fp.success) {
866
- throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
867
- code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
868
- });
869
- }
870
- }
871
1198
  try {
872
- return await _LedgerAdapter._abortable(signal, this.connector.call(sessionId, method, params));
1199
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1200
+ const fp = await this._abortable(
1201
+ signal,
1202
+ this._verifyDeviceFingerprintWithSession(
1203
+ sessionId,
1204
+ fingerprint.deviceId,
1205
+ fingerprint.chain
1206
+ )
1207
+ );
1208
+ if (!fp.success) {
1209
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1210
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1211
+ });
1212
+ }
1213
+ }
1214
+ return await this._abortable(signal, this.connector.call(sessionId, method, effectiveParams));
873
1215
  } catch (err) {
874
1216
  if (signal.aborted) throw err;
875
1217
  const errObj = err;
@@ -879,37 +1221,243 @@ var _LedgerAdapter = class _LedgerAdapter {
879
1221
  errorCode: errObj?.errorCode,
880
1222
  statusCode: errObj?.statusCode,
881
1223
  isDisconnected: isDeviceDisconnectedError(err),
882
- isLocked: isDeviceLockedError(err)
1224
+ isLocked: isDeviceLockedError(err),
1225
+ isNotAdvertising: isDeviceNotAdvertisingError(err),
1226
+ isStuckApp: isStuckAppStateError(err)
883
1227
  });
884
- if (isDeviceDisconnectedError(err)) {
885
- debugLog("[LedgerAdapter] disconnected, retrying with fresh connection...");
886
- this._discoveredDevices.clear();
887
- return this._retryWithFreshConnection(resolvedConnectId, method, params, signal, err);
1228
+ if (isStuckAppStateError(err)) {
1229
+ try {
1230
+ this._sessions.delete(resolvedConnectId);
1231
+ this._discoveredDevices.delete(resolvedConnectId);
1232
+ this.connector.reset?.();
1233
+ } catch {
1234
+ }
1235
+ debugLog(
1236
+ "[LedgerAdapter] stuck-app retry: method=",
1237
+ method,
1238
+ "delayMs=",
1239
+ _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
1240
+ "_tag=",
1241
+ err?._tag
1242
+ );
1243
+ try {
1244
+ const retryResult = await this._retryAfterStuckApp(
1245
+ resolvedConnectId,
1246
+ method,
1247
+ effectiveParams,
1248
+ signal,
1249
+ err,
1250
+ fingerprint
1251
+ );
1252
+ debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
1253
+ return retryResult;
1254
+ } catch (retryErr) {
1255
+ if (signal.aborted) throw retryErr;
1256
+ if (isStuckAppStateError(retryErr)) {
1257
+ debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
1258
+ throw err;
1259
+ }
1260
+ debugLog(
1261
+ "[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
1262
+ method,
1263
+ "retryErrTag=",
1264
+ retryErr?._tag
1265
+ );
1266
+ throw retryErr;
1267
+ }
888
1268
  }
889
- if (isDeviceLockedError(err)) {
890
- await this._waitForDeviceConnect(signal);
891
- _LedgerAdapter._throwIfAborted(signal);
892
- return _LedgerAdapter._abortable(signal, this.connector.call(sessionId, method, params));
1269
+ if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
1270
+ let lastErr = err;
1271
+ for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
1272
+ if (signal.aborted) throw lastErr;
1273
+ try {
1274
+ this._sessions.delete(resolvedConnectId);
1275
+ this._discoveredDevices.delete(resolvedConnectId);
1276
+ if (isDeviceDisconnectedError(lastErr)) {
1277
+ try {
1278
+ this.connector.reset?.();
1279
+ } catch {
1280
+ }
1281
+ }
1282
+ const retryConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1283
+ const reConnectId = await this.ensureConnected(retryConnectId, signal);
1284
+ const reSessionId = this._sessions.get(reConnectId);
1285
+ if (!reSessionId) throw lastErr;
1286
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1287
+ const fp = await this._abortable(
1288
+ signal,
1289
+ this._verifyDeviceFingerprintWithSession(
1290
+ reSessionId,
1291
+ fingerprint.deviceId,
1292
+ fingerprint.chain
1293
+ )
1294
+ );
1295
+ if (!fp.success) {
1296
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1297
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1298
+ });
1299
+ }
1300
+ }
1301
+ return await this._abortable(
1302
+ signal,
1303
+ this.connector.call(reSessionId, method, effectiveParams)
1304
+ );
1305
+ } catch (retryErr) {
1306
+ if (signal.aborted) throw retryErr;
1307
+ lastErr = retryErr;
1308
+ const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
1309
+ if (!canRetry) {
1310
+ throw retryErr;
1311
+ }
1312
+ }
1313
+ }
1314
+ throw lastErr;
893
1315
  }
894
1316
  if (isTimeoutError(err)) {
895
1317
  debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
896
1318
  this._discoveredDevices.delete(resolvedConnectId);
897
- return this._retryWithFreshConnection(resolvedConnectId, method, params, signal, err);
1319
+ return this._retryWithFreshConnection(
1320
+ resolvedConnectId,
1321
+ method,
1322
+ effectiveParams,
1323
+ signal,
1324
+ err,
1325
+ fingerprint
1326
+ );
1327
+ }
1328
+ if (isConnectionLevelError(err)) {
1329
+ debugLog("[LedgerAdapter] connection-level fail-closed reset");
1330
+ this._sessions.delete(resolvedConnectId);
1331
+ this._discoveredDevices.delete(resolvedConnectId);
1332
+ this.connector.reset();
1333
+ const codeNum = err?.code;
1334
+ throw Object.assign(err, {
1335
+ code: codeNum ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceDisconnected
1336
+ });
898
1337
  }
899
1338
  throw err;
900
1339
  }
901
1340
  }
902
- /** Clear stale session, reconnect, and retry the call. */
903
- async _retryWithFreshConnection(resolvedConnectId, method, params, signal, originalErr) {
904
- this._sessions.delete(resolvedConnectId);
905
- _LedgerAdapter._throwIfAborted(signal);
906
- const retryConnectId = await this.ensureConnected();
907
- _LedgerAdapter._throwIfAborted(signal);
1341
+ /**
1342
+ * Stuck-app recovery: pause for the device's UI transition, then retry once.
1343
+ *
1344
+ * Caller has already cleared the session + reset connector. We wait so Stax
1345
+ * finishes its post-CloseApp animation, then go through ensureConnected +
1346
+ * fingerprint check + call exactly once. Caller decides what to do on a
1347
+ * second stuck-app hit.
1348
+ */
1349
+ async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
1350
+ await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
1351
+ const retryTargetConnectId = isLedgerBleConnectionType(this.connector.connectionType) ? resolvedConnectId : void 0;
1352
+ const retryConnectId = await this.ensureConnected(retryTargetConnectId, signal);
1353
+ const retrySessionId = this._sessions.get(retryConnectId);
1354
+ if (!retrySessionId) throw originalErr;
1355
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1356
+ const fp = await this._abortable(
1357
+ signal,
1358
+ this._verifyDeviceFingerprintWithSession(
1359
+ retrySessionId,
1360
+ fingerprint.deviceId,
1361
+ fingerprint.chain
1362
+ )
1363
+ );
1364
+ if (!fp.success) {
1365
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1366
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1367
+ });
1368
+ }
1369
+ }
1370
+ return this._abortable(signal, this.connector.call(retrySessionId, method, params));
1371
+ }
1372
+ _sleepAbortable(ms, signal) {
1373
+ return new Promise((resolve, reject) => {
1374
+ if (signal.aborted) {
1375
+ reject(signal.reason ?? new Error("Aborted"));
1376
+ return;
1377
+ }
1378
+ const timer = setTimeout(() => {
1379
+ signal.removeEventListener("abort", onAbort);
1380
+ resolve();
1381
+ }, ms);
1382
+ const onAbort = () => {
1383
+ clearTimeout(timer);
1384
+ reject(signal.reason ?? new Error("Aborted"));
1385
+ };
1386
+ signal.addEventListener("abort", onAbort, { once: true });
1387
+ });
1388
+ }
1389
+ /**
1390
+ * Clear stale session, reconnect, and retry the call.
1391
+ *
1392
+ * Timeout recovery starts with a full connector reset. After an APDU
1393
+ * timeout, DMK/transport state may still emit malformed responses; retrying
1394
+ * on the same DMK can poison the next chain switch.
1395
+ */
1396
+ async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
1397
+ this.connector.reset();
1398
+ this._sessions.clear();
1399
+ this._discoveredDevices.clear();
1400
+ this._connectingPromise = null;
1401
+ const retryConnectId = await this.ensureConnected(targetConnectId, signal);
908
1402
  const retrySessionId = this._sessions.get(retryConnectId);
909
1403
  if (!retrySessionId) {
910
1404
  throw originalErr;
911
1405
  }
912
- return _LedgerAdapter._abortable(signal, this.connector.call(retrySessionId, method, params));
1406
+ try {
1407
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1408
+ const fp = await this._abortable(
1409
+ signal,
1410
+ this._verifyDeviceFingerprintWithSession(
1411
+ retrySessionId,
1412
+ fingerprint.deviceId,
1413
+ fingerprint.chain
1414
+ )
1415
+ );
1416
+ if (!fp.success) {
1417
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1418
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1419
+ });
1420
+ }
1421
+ }
1422
+ return await this._abortable(signal, this.connector.call(retrySessionId, method, params));
1423
+ } catch (retryErr) {
1424
+ if (signal.aborted) throw retryErr;
1425
+ this.connector.reset();
1426
+ this._sessions.clear();
1427
+ this._discoveredDevices.clear();
1428
+ this._connectingPromise = null;
1429
+ if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
1430
+ throw retryErr;
1431
+ }
1432
+ debugLog(
1433
+ "[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
1434
+ );
1435
+ this.connector.reset();
1436
+ this._sessions.clear();
1437
+ this._discoveredDevices.clear();
1438
+ this._connectingPromise = null;
1439
+ const finalConnectId = await this.ensureConnected(targetConnectId, signal);
1440
+ const finalSessionId = this._sessions.get(finalConnectId);
1441
+ if (!finalSessionId) {
1442
+ throw originalErr;
1443
+ }
1444
+ if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
1445
+ const fp = await this._abortable(
1446
+ signal,
1447
+ this._verifyDeviceFingerprintWithSession(
1448
+ finalSessionId,
1449
+ fingerprint.deviceId,
1450
+ fingerprint.chain
1451
+ )
1452
+ );
1453
+ if (!fp.success) {
1454
+ throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
1455
+ code: import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch
1456
+ });
1457
+ }
1458
+ }
1459
+ return this._abortable(signal, this.connector.call(finalSessionId, method, params));
1460
+ }
913
1461
  }
914
1462
  /**
915
1463
  * Ensure OS-level device permission (Bluetooth / USB) before proceeding.
@@ -923,7 +1471,10 @@ var _LedgerAdapter = class _LedgerAdapter {
923
1471
  * - No connectId (searchDevices): environment-level permission
924
1472
  * - With connectId (business methods): device-level permission
925
1473
  */
926
- async _ensureDevicePermission(connectId, deviceId) {
1474
+ async _ensureDevicePermission(connectId, deviceId, signal) {
1475
+ if (signal?.aborted) {
1476
+ _LedgerAdapter._throwIfAborted(signal);
1477
+ }
927
1478
  const transportType = this.activeTransport ?? "hid";
928
1479
  const waitPromise = this._uiRegistry.wait(
929
1480
  import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
@@ -933,10 +1484,37 @@ var _LedgerAdapter = class _LedgerAdapter {
933
1484
  type: import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
934
1485
  payload: { transportType, connectId, deviceId }
935
1486
  });
936
- const { granted } = await waitPromise;
1487
+ let response;
1488
+ const onAbort = () => {
1489
+ this._uiRegistry.cancel(import_hwk_adapter_core2.UI_REQUEST.REQUEST_DEVICE_PERMISSION);
1490
+ };
1491
+ try {
1492
+ signal?.addEventListener("abort", onAbort, { once: true });
1493
+ response = await waitPromise;
1494
+ } catch (err) {
1495
+ if (signal?.aborted) {
1496
+ _LedgerAdapter._throwIfAborted(signal);
1497
+ }
1498
+ if (err?._tag === import_hwk_adapter_core2.UI_REQUEST_PREEMPTED_TAG) {
1499
+ this.emitter.emit(import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW, {
1500
+ type: import_hwk_adapter_core2.UI_REQUEST.CLOSE_UI_WINDOW,
1501
+ payload: {}
1502
+ });
1503
+ throw Object.assign(new Error("Device permission request superseded"), {
1504
+ _tag: ERROR_TAG.UserAborted,
1505
+ code: import_hwk_adapter_core2.HardwareErrorCode.UserAborted,
1506
+ cause: err
1507
+ });
1508
+ }
1509
+ throw err;
1510
+ } finally {
1511
+ signal?.removeEventListener("abort", onAbort);
1512
+ }
1513
+ const { granted, reason, message } = response;
937
1514
  if (!granted) {
938
- throw Object.assign(new Error("Device permission denied"), {
939
- code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied
1515
+ throw Object.assign(new Error(message ?? "Device permission denied"), {
1516
+ code: import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied,
1517
+ reason
940
1518
  });
941
1519
  }
942
1520
  }
@@ -946,12 +1524,14 @@ var _LedgerAdapter = class _LedgerAdapter {
946
1524
  */
947
1525
  errorToFailure(err) {
948
1526
  debugError("[LedgerAdapter] error:", err);
1527
+ const tag = err && typeof err === "object" ? err._tag : void 0;
949
1528
  if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
950
1529
  const e = err;
951
- return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName);
1530
+ const params = e.code === import_hwk_adapter_core2.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : void 0;
1531
+ return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
952
1532
  }
953
1533
  const mapped = mapLedgerError(err);
954
- return ledgerFailure(mapped.code, mapped.message, mapped.appName);
1534
+ return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
955
1535
  }
956
1536
  registerEventListeners() {
957
1537
  this.connector.on("device-connect", this.deviceConnectHandler);
@@ -967,32 +1547,38 @@ var _LedgerAdapter = class _LedgerAdapter {
967
1547
  // Device info mapping
968
1548
  // ---------------------------------------------------------------------------
969
1549
  connectorDeviceToDeviceInfo(device) {
970
- const isBle = device.connectId && /^[0-9A-Fa-f]{4}$/.test(device.connectId);
971
1550
  return {
972
1551
  vendor: "ledger",
973
1552
  model: device.model ?? "unknown",
1553
+ modelName: device.modelName,
974
1554
  firmwareVersion: "",
975
1555
  deviceId: device.deviceId,
976
1556
  connectId: device.connectId,
977
1557
  label: device.name,
978
- connectionType: isBle ? "ble" : "usb",
1558
+ connectionType: this.connector.connectionType,
1559
+ rssi: device.rssi,
1560
+ isConnectable: device.isConnectable,
1561
+ serialNumber: device.serialNumber,
979
1562
  capabilities: device.capabilities
980
1563
  };
981
1564
  }
982
1565
  };
983
- // ---------------------------------------------------------------------------
984
- // Private helpers
985
- // ---------------------------------------------------------------------------
986
- /**
987
- * Ensure at least one device is connected and return a valid connectId.
988
- *
989
- * - If a session already exists for the given connectId, reuse it.
990
- * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
991
- * - Otherwise: search 1 device: auto-connect, multiple: ask user, 0: throw.
992
- */
993
- _LedgerAdapter.MAX_DEVICE_RETRY = 3;
1566
+ // Layer 2 retry budget after connection-class error. Each round delegates
1567
+ // to Layer 1 (which owns the unlock prompt). Layer 2 never emits UI itself.
1568
+ _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET = 3;
1569
+ // Stuck-app (APDU 0x6901) retry pause. Ledger Stax often returns 6901 when
1570
+ // OpenAppCommand lands mid-transition right after CloseApp; a short wait
1571
+ // lets the device finish its UI animation before we retry once.
1572
+ _LedgerAdapter.STUCK_APP_RETRY_DELAY_MS = 500;
1573
+ // Layer 1 confirm budget. After N failed Confirm cycles (user keeps clicking
1574
+ // Confirm but device never shows up / never unlocks), throw DeviceNotFound
1575
+ // instead of looping forever.
1576
+ _LedgerAdapter.MAX_DOCONNECT_CONFIRMS = 3;
994
1577
  var LedgerAdapter = _LedgerAdapter;
995
1578
 
1579
+ // src/connector/LedgerConnectorBase.ts
1580
+ var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
1581
+
996
1582
  // src/device/LedgerDeviceManager.ts
997
1583
  var LedgerDeviceManager = class {
998
1584
  constructor(dmk) {
@@ -1029,7 +1615,9 @@ var LedgerDeviceManager = class {
1029
1615
  if (resolved) return;
1030
1616
  resolved = true;
1031
1617
  this._discovered.clear();
1032
- debugLog("[DMK] enumerate raw devices:", devices.length);
1618
+ debugLog(
1619
+ `[DMK] enumerate count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
1620
+ );
1033
1621
  for (const d of devices) {
1034
1622
  this._discovered.set(d.id, d);
1035
1623
  }
@@ -1039,8 +1627,10 @@ var LedgerDeviceManager = class {
1039
1627
  devices.map((d) => ({
1040
1628
  path: d.id,
1041
1629
  type: d.deviceModel.model,
1630
+ modelName: d.deviceModel.name,
1042
1631
  name: d.name,
1043
- transport: d.transport
1632
+ transport: d.transport,
1633
+ rssi: d.rssi
1044
1634
  }))
1045
1635
  );
1046
1636
  } else {
@@ -1061,8 +1651,10 @@ var LedgerDeviceManager = class {
1061
1651
  devices.map((d) => ({
1062
1652
  path: d.id,
1063
1653
  type: d.deviceModel.model,
1654
+ modelName: d.deviceModel.name,
1064
1655
  name: d.name,
1065
- transport: d.transport
1656
+ transport: d.transport,
1657
+ rssi: d.rssi
1066
1658
  }))
1067
1659
  );
1068
1660
  }
@@ -1086,8 +1678,10 @@ var LedgerDeviceManager = class {
1086
1678
  descriptor: {
1087
1679
  path: d.id,
1088
1680
  type: d.deviceModel.model,
1681
+ modelName: d.deviceModel.name,
1089
1682
  name: d.name,
1090
- transport: d.transport
1683
+ transport: d.transport,
1684
+ rssi: d.rssi
1091
1685
  }
1092
1686
  });
1093
1687
  }
@@ -1106,6 +1700,54 @@ var LedgerDeviceManager = class {
1106
1700
  this._listenSub?.unsubscribe();
1107
1701
  this._listenSub = null;
1108
1702
  }
1703
+ /**
1704
+ * Resolve to the latest snapshot of advertising devices observed within
1705
+ * `timeoutMs`. Rewrites `_discovered` so it tracks the live list.
1706
+ *
1707
+ * `listenToAvailableDevices` is a BehaviorSubject — it emits the cached
1708
+ * list on subscribe and *only* re-emits when the list changes. A stable
1709
+ * list of currently-advertising devices therefore produces only the
1710
+ * subscription frame; treating that frame as "fossil to be discarded"
1711
+ * caused devices to flicker in/out of the UI as searches alternated
1712
+ * between "saw a change frame" and "saw only the cached frame".
1713
+ *
1714
+ * The window still gives a chance for the active scan to update the list
1715
+ * (e.g. drop a peripheral that just stopped broadcasting), but if nothing
1716
+ * changes we trust the snapshot — DMK silence on a stable list means
1717
+ * "everything's still there".
1718
+ */
1719
+ getLiveDevices(timeoutMs = 1500) {
1720
+ debugLog(`[DMK] getLiveDevices() called, timeoutMs=${timeoutMs}`);
1721
+ void this.requestDevice();
1722
+ return new Promise((resolve) => {
1723
+ let done = false;
1724
+ let lastSeen = [];
1725
+ let sub = null;
1726
+ const finish = (devices) => {
1727
+ if (done) return;
1728
+ done = true;
1729
+ sub?.unsubscribe();
1730
+ clearTimeout(timer);
1731
+ this._discovered.clear();
1732
+ for (const d of devices) this._discovered.set(d.id, d);
1733
+ debugLog(
1734
+ `[DMK] getLiveDevices() resolved count=${devices.length} ids=[${devices.map((d) => d.id).join(",")}]`
1735
+ );
1736
+ resolve(devices);
1737
+ };
1738
+ const timer = setTimeout(() => finish(lastSeen), timeoutMs);
1739
+ sub = this._dmk.listenToAvailableDevices({}).subscribe({
1740
+ next: (devices) => {
1741
+ lastSeen = devices;
1742
+ },
1743
+ error: (err) => {
1744
+ debugError("[DMK] getLiveDevices() error:", err);
1745
+ finish(lastSeen);
1746
+ },
1747
+ complete: () => finish(lastSeen)
1748
+ });
1749
+ });
1750
+ }
1109
1751
  requestDevice() {
1110
1752
  if (this._discoverySub) {
1111
1753
  return Promise.resolve();
@@ -1123,11 +1765,33 @@ var LedgerDeviceManager = class {
1123
1765
  });
1124
1766
  return Promise.resolve();
1125
1767
  }
1768
+ /** Lookup a previously-discovered device's display name. */
1769
+ getDeviceName(deviceId) {
1770
+ return this._discovered.get(deviceId)?.name;
1771
+ }
1772
+ /** Lookup minimal model/signal info from a previously-discovered device. */
1773
+ getDiscoveredDeviceInfo(deviceId) {
1774
+ const d = this._discovered.get(deviceId);
1775
+ if (!d) return void 0;
1776
+ return {
1777
+ model: d.deviceModel?.model,
1778
+ modelName: d.deviceModel?.name,
1779
+ name: d.name,
1780
+ rssi: d.rssi
1781
+ };
1782
+ }
1783
+ hasDiscoveredDevice(deviceId) {
1784
+ return this._discovered.has(deviceId);
1785
+ }
1126
1786
  /** Connect to a previously discovered device. Returns sessionId. */
1127
1787
  async connect(deviceId) {
1128
1788
  const device = this._discovered.get(deviceId);
1129
1789
  if (!device) {
1130
- throw new Error(`Device "${deviceId}" not found. Call enumerate() or listen() first.`);
1790
+ const err = new Error(
1791
+ `Device "${deviceId}" not found in discovery cache. Call enumerate() or listen() first.`
1792
+ );
1793
+ err._tag = ERROR_TAG.DeviceNotInDiscoveryCache;
1794
+ throw err;
1131
1795
  }
1132
1796
  const sessionId = await this._dmk.connect({ device });
1133
1797
  this._sessions.set(deviceId, sessionId);
@@ -1166,6 +1830,19 @@ var LedgerDeviceManager = class {
1166
1830
  this._sessionToDevice.clear();
1167
1831
  this._dmk.close();
1168
1832
  }
1833
+ /**
1834
+ * Tear down RxJS subscriptions and local state without closing the DMK.
1835
+ * For light-reset paths that want to drop session state but keep the
1836
+ * underlying transport alive for retry. Call this instead of orphaning
1837
+ * the manager — bare null assignment leaks _listenSub / _discoverySub.
1838
+ */
1839
+ disposeKeepingDmk() {
1840
+ this.stopListening();
1841
+ this.stopDiscovery();
1842
+ this._discovered.clear();
1843
+ this._sessions.clear();
1844
+ this._sessionToDevice.clear();
1845
+ }
1169
1846
  };
1170
1847
 
1171
1848
  // src/signer/SignerManager.ts
@@ -1177,11 +1854,12 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
1177
1854
 
1178
1855
  // src/signer/deviceActionToPromise.ts
1179
1856
  var import_device_management_kit = require("@ledgerhq/device-management-kit");
1180
- var DEFAULT_TIMEOUT_MS = 3e4;
1181
- function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOUT_MS, onRegisterCanceller) {
1857
+ var IDLE_WATCHDOG_MS = 65e3;
1858
+ function deviceActionToPromise(action, onInteraction, timeoutMs = IDLE_WATCHDOG_MS, onRegisterCanceller) {
1182
1859
  return new Promise((resolve, reject) => {
1183
1860
  let settled = false;
1184
1861
  let lastStep;
1862
+ const observedSteps = [];
1185
1863
  let sub;
1186
1864
  let timer = null;
1187
1865
  const cancelAction = () => {
@@ -1194,44 +1872,59 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1194
1872
  } catch {
1195
1873
  }
1196
1874
  };
1197
- const resetTimer = () => {
1875
+ const armIdleWatchdog = () => {
1198
1876
  if (timer) clearTimeout(timer);
1199
1877
  if (timeoutMs > 0) {
1200
1878
  timer = setTimeout(() => {
1201
1879
  if (!settled) {
1202
1880
  settled = true;
1203
1881
  cancelAction();
1204
- reject(new Error("Device action timed out \u2014 device may be locked or disconnected"));
1882
+ reject(
1883
+ new Error(
1884
+ "Ledger transport unresponsive: no DMK state change within the watchdog window. The device may have lost connection."
1885
+ )
1886
+ );
1205
1887
  }
1206
1888
  }, timeoutMs);
1207
1889
  }
1208
1890
  };
1209
- resetTimer();
1891
+ armIdleWatchdog();
1210
1892
  if (onRegisterCanceller) {
1211
- onRegisterCanceller(() => {
1212
- if (settled) return;
1893
+ onRegisterCanceller((reason) => {
1894
+ if (settled) {
1895
+ return;
1896
+ }
1213
1897
  settled = true;
1214
1898
  if (timer) clearTimeout(timer);
1215
1899
  cancelAction();
1216
- reject(new Error("Device action cancelled"));
1900
+ const err = new Error(reason?.message ?? "Device action cancelled");
1901
+ if (reason?.code !== void 0) {
1902
+ err.code = reason.code;
1903
+ }
1904
+ if (reason?.tag) {
1905
+ Object.defineProperty(err, "_tag", {
1906
+ value: reason.tag,
1907
+ configurable: true,
1908
+ enumerable: false,
1909
+ writable: true
1910
+ });
1911
+ }
1912
+ reject(err);
1217
1913
  });
1218
1914
  }
1219
- debugLog("[DMK-Observable] subscribing to action.observable...");
1220
1915
  sub = action.observable.subscribe({
1221
1916
  next: (state) => {
1222
- if (settled) return;
1223
- resetTimer();
1917
+ if (settled) {
1918
+ return;
1919
+ }
1920
+ armIdleWatchdog();
1224
1921
  const step = state?.intermediateValue?.step;
1225
- if (step) lastStep = step;
1226
- debugLog(
1227
- "[DMK-Observable] state:",
1228
- JSON.stringify({
1229
- status: state.status,
1230
- intermediateValue: state.status === import_device_management_kit.DeviceActionStatus.Pending ? state.intermediateValue : void 0,
1231
- hasOutput: state.status === import_device_management_kit.DeviceActionStatus.Completed,
1232
- hasError: state.status === import_device_management_kit.DeviceActionStatus.Error
1233
- })
1234
- );
1922
+ if (step) {
1923
+ lastStep = step;
1924
+ if (observedSteps[observedSteps.length - 1] !== step) {
1925
+ observedSteps.push(step);
1926
+ }
1927
+ }
1235
1928
  if (state.status === import_device_management_kit.DeviceActionStatus.Completed) {
1236
1929
  settled = true;
1237
1930
  if (timer) clearTimeout(timer);
@@ -1243,10 +1936,14 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1243
1936
  if (timer) clearTimeout(timer);
1244
1937
  onInteraction?.("interaction-complete");
1245
1938
  sub?.unsubscribe();
1246
- rejectWithLastStep(state.error, lastStep, reject);
1939
+ rejectWithStepContext(state.error, lastStep, observedSteps, reject);
1247
1940
  } else if (state.status === import_device_management_kit.DeviceActionStatus.Pending && onInteraction) {
1248
1941
  const interaction = state.intermediateValue?.requiredUserInteraction;
1249
1942
  if (interaction && interaction !== "none") {
1943
+ if (interaction === "unlock-device" && timer) {
1944
+ clearTimeout(timer);
1945
+ timer = null;
1946
+ }
1250
1947
  onInteraction(String(interaction));
1251
1948
  }
1252
1949
  }
@@ -1256,7 +1953,7 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1256
1953
  settled = true;
1257
1954
  if (timer) clearTimeout(timer);
1258
1955
  sub?.unsubscribe();
1259
- rejectWithLastStep(err, lastStep, reject);
1956
+ rejectWithStepContext(err, lastStep, observedSteps, reject);
1260
1957
  }
1261
1958
  },
1262
1959
  complete: () => {
@@ -1269,15 +1966,25 @@ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOU
1269
1966
  });
1270
1967
  });
1271
1968
  }
1272
- function rejectWithLastStep(err, lastStep, reject) {
1273
- if (err && typeof err === "object" && lastStep) {
1969
+ function rejectWithStepContext(err, lastStep, observedSteps, reject) {
1970
+ if (err && typeof err === "object" && (lastStep || observedSteps.length > 0)) {
1274
1971
  try {
1275
- Object.defineProperty(err, "_lastStep", {
1276
- value: lastStep,
1277
- configurable: true,
1278
- enumerable: false,
1279
- writable: true
1280
- });
1972
+ if (lastStep) {
1973
+ Object.defineProperty(err, "_lastStep", {
1974
+ value: lastStep,
1975
+ configurable: true,
1976
+ enumerable: false,
1977
+ writable: true
1978
+ });
1979
+ }
1980
+ if (observedSteps.length > 0) {
1981
+ Object.defineProperty(err, "_deviceActionSteps", {
1982
+ value: [...observedSteps],
1983
+ configurable: true,
1984
+ enumerable: false,
1985
+ writable: true
1986
+ });
1987
+ }
1281
1988
  } catch {
1282
1989
  }
1283
1990
  }
@@ -1343,7 +2050,8 @@ var SignerManager = class _SignerManager {
1343
2050
  async getOrCreate(sessionId) {
1344
2051
  debugLog("[DMK] SignerManager.getOrCreate:", { sessionId });
1345
2052
  const builder = await this._builderFn({ dmk: this._dmk, sessionId });
1346
- return new SignerEth(builder.build());
2053
+ const builderWithContext = builder.withContextModule ? builder.withContextModule(_SignerManager._createContextModule()) : builder;
2054
+ return new SignerEth(builderWithContext.build());
1347
2055
  }
1348
2056
  // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this
1349
2057
  invalidate(_sessionId) {
@@ -1352,10 +2060,24 @@ var SignerManager = class _SignerManager {
1352
2060
  clearAll() {
1353
2061
  }
1354
2062
  static _defaultBuilder() {
1355
- return (args) => {
1356
- const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
1357
- return new import_device_signer_kit_ethereum.SignerEthBuilder(args).withContextModule(contextModule);
2063
+ return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
2064
+ }
2065
+ static _createContextModule() {
2066
+ const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
2067
+ return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
2068
+ }
2069
+ static wrapBlindSigningReportNonBlocking(contextModule) {
2070
+ const report = contextModule.report.bind(contextModule);
2071
+ contextModule.report = async (params) => {
2072
+ try {
2073
+ void report(params).catch((err) => {
2074
+ debugLog("[DMK] ContextModule.report failed:", err);
2075
+ });
2076
+ } catch (err) {
2077
+ debugLog("[DMK] ContextModule.report failed:", err);
2078
+ }
1358
2079
  };
2080
+ return contextModule;
1359
2081
  }
1360
2082
  };
1361
2083
 
@@ -1465,6 +2187,7 @@ async function _getEthSigner(ctx, sessionId) {
1465
2187
  const signerManager = await ctx.getSignerManager();
1466
2188
  const signer = await signerManager.getOrCreate(sessionId);
1467
2189
  signer.onInteraction = (interaction) => {
2190
+ debugLog("[LedgerConnector] evm.onInteraction:", interaction);
1468
2191
  ctx.emit("ui-event", {
1469
2192
  type: collapseSignerInteraction(interaction),
1470
2193
  payload: { sessionId }
@@ -1740,6 +2463,7 @@ async function _createBtcSigner(ctx, sessionId) {
1740
2463
  const sdkSigner = new SignerBtcBuilder({ dmk, sessionId }).build();
1741
2464
  const signer = new SignerBtc(sdkSigner);
1742
2465
  signer.onInteraction = (interaction) => {
2466
+ debugLog("[LedgerConnector] btc.onInteraction:", interaction);
1743
2467
  ctx.emit("ui-event", {
1744
2468
  type: collapseSignerInteraction(interaction),
1745
2469
  payload: { sessionId }
@@ -1910,6 +2634,7 @@ async function _createSolSigner(ctx, sessionId) {
1910
2634
  const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
1911
2635
  const signer = new SignerSol(sdkSigner);
1912
2636
  signer.onInteraction = (interaction) => {
2637
+ debugLog("[LedgerConnector] sol.onInteraction:", interaction);
1913
2638
  ctx.emit("ui-event", {
1914
2639
  type: collapseSignerInteraction(interaction),
1915
2640
  payload: { sessionId }
@@ -1922,14 +2647,15 @@ async function _createSolSigner(ctx, sessionId) {
1922
2647
  }
1923
2648
 
1924
2649
  // src/connector/chains/tron.ts
1925
- var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
2650
+ var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
1926
2651
  var import_hw_app_trx = __toESM(require("@ledgerhq/hw-app-trx"));
1927
2652
 
1928
2653
  // src/connector/chains/legacyChainCall.ts
1929
- var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
2654
+ var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
1930
2655
 
1931
2656
  // src/app/AppManager.ts
1932
2657
  var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
2658
+ var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
1933
2659
  var APP_NAME_MAP = {
1934
2660
  ETH: "Ethereum",
1935
2661
  BTC: "Bitcoin",
@@ -2001,7 +2727,31 @@ var AppManager = class {
2001
2727
  debugLog("[AppManager] currentApp:", result.data.name);
2002
2728
  return result.data.name;
2003
2729
  }
2004
- throw new Error("Failed to get current app from device");
2730
+ const errResult = result;
2731
+ const dmkErr = errResult.error ?? {};
2732
+ const original = dmkErr.originalError;
2733
+ debugLog(
2734
+ "[AppManager] _getCurrentApp failed sessionId=",
2735
+ sessionId,
2736
+ "tag=",
2737
+ dmkErr._tag,
2738
+ "errorCode=",
2739
+ dmkErr.errorCode,
2740
+ "message=",
2741
+ dmkErr.message,
2742
+ "originalErrorMessage=",
2743
+ original?.message ?? String(original ?? "")
2744
+ );
2745
+ throw Object.assign(
2746
+ new Error(
2747
+ dmkErr.message ?? "Failed to get current app from device"
2748
+ ),
2749
+ {
2750
+ _tag: dmkErr._tag,
2751
+ errorCode: dmkErr.errorCode,
2752
+ originalError: original
2753
+ }
2754
+ );
2005
2755
  }
2006
2756
  async _openApp(sessionId, appName) {
2007
2757
  const result = await this._dmk.sendCommand({
@@ -2010,9 +2760,11 @@ var AppManager = class {
2010
2760
  });
2011
2761
  if (!(0, import_device_management_kit2.isSuccessCommandResult)(result)) {
2012
2762
  const { statusCode } = result;
2763
+ const hasStatusCode2 = statusCode != null && statusCode !== "";
2013
2764
  debugLog("[AppManager] openApp failed:", appName, "statusCode:", statusCode);
2014
2765
  throw Object.assign(new Error(`Failed to open "${appName}"`), {
2015
- _tag: "OpenAppCommandError",
2766
+ _tag: ERROR_TAG.OpenAppCommand,
2767
+ code: hasStatusCode2 ? void 0 : import_hwk_adapter_core9.HardwareErrorCode.AppNotInstalled,
2016
2768
  errorCode: String(statusCode ?? ""),
2017
2769
  statusCode,
2018
2770
  appName
@@ -2068,14 +2820,14 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
2068
2820
  const onAppOpenPrompt = () => {
2069
2821
  openAppPromptShown = true;
2070
2822
  ctx.emit("ui-event", {
2071
- type: import_hwk_adapter_core9.EConnectorInteraction.ConfirmOpenApp,
2823
+ type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOpenApp,
2072
2824
  payload: { sessionId }
2073
2825
  });
2074
2826
  };
2075
2827
  const closeOpenAppUiIfShown = () => {
2076
2828
  if (openAppPromptShown) {
2077
2829
  ctx.emit("ui-event", {
2078
- type: import_hwk_adapter_core9.EConnectorInteraction.InteractionComplete,
2830
+ type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
2079
2831
  payload: { sessionId }
2080
2832
  });
2081
2833
  openAppPromptShown = false;
@@ -2096,7 +2848,7 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
2096
2848
  let confirmEmitted = false;
2097
2849
  if (needsConfirmation) {
2098
2850
  ctx.emit("ui-event", {
2099
- type: import_hwk_adapter_core9.EConnectorInteraction.ConfirmOnDevice,
2851
+ type: import_hwk_adapter_core10.EConnectorInteraction.ConfirmOnDevice,
2100
2852
  payload: { sessionId }
2101
2853
  });
2102
2854
  confirmEmitted = true;
@@ -2106,7 +2858,7 @@ async function withLegacyChainCall(ctx, sessionId, options, action) {
2106
2858
  } finally {
2107
2859
  if (confirmEmitted || openAppPromptShown) {
2108
2860
  ctx.emit("ui-event", {
2109
- type: import_hwk_adapter_core9.EConnectorInteraction.InteractionComplete,
2861
+ type: import_hwk_adapter_core10.EConnectorInteraction.InteractionComplete,
2110
2862
  payload: { sessionId }
2111
2863
  });
2112
2864
  openAppPromptShown = false;
@@ -2194,7 +2946,7 @@ async function tronSignTransaction(ctx, sessionId, params) {
2194
2946
  if (!params.rawTxHex) {
2195
2947
  throw Object.assign(
2196
2948
  new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
2197
- { code: import_hwk_adapter_core10.HardwareErrorCode.InvalidParams }
2949
+ { code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
2198
2950
  );
2199
2951
  }
2200
2952
  const path = normalizePath(params.path);
@@ -2238,6 +2990,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
2238
2990
  sol: "Solana",
2239
2991
  tron: "Tron"
2240
2992
  };
2993
+ var HARDWARE_ERROR_CODE_VALUES = new Set(
2994
+ Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
2995
+ );
2996
+ var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
2241
2997
  async function defaultLedgerKitImporter(pkg) {
2242
2998
  switch (pkg) {
2243
2999
  case "@ledgerhq/device-management-kit":
@@ -2261,16 +3017,6 @@ var LedgerConnectorBase = class {
2261
3017
  this._dmk = null;
2262
3018
  this._eventHandlers = /* @__PURE__ */ new Map();
2263
3019
  // ---------------------------------------------------------------------------
2264
- // ConnectId <-> DMK path mapping
2265
- //
2266
- // DMK uses internal paths (BLE MAC, USB UUID) that may change across sessions.
2267
- // _resolveConnectId() maps these to stable external IDs (BLE: "A58F", USB: same).
2268
- // This bidirectional map is the SINGLE SOURCE OF TRUTH for all connectId usage.
2269
- // ---------------------------------------------------------------------------
2270
- this._connectIdToPath = /* @__PURE__ */ new Map();
2271
- // "A58F" -> "D5:75:7D:4B:51:E8"
2272
- this._pathToConnectId = /* @__PURE__ */ new Map();
2273
- // ---------------------------------------------------------------------------
2274
3020
  // Per-session DeviceAction cancellers
2275
3021
  //
2276
3022
  // Each chain handler registers its active DeviceAction's canceller via
@@ -2279,10 +3025,23 @@ var LedgerConnectorBase = class {
2279
3025
  // unsubscribes the observable and releases DMK's IntentQueue slot.
2280
3026
  // ---------------------------------------------------------------------------
2281
3027
  this._cancellers = /* @__PURE__ */ new Map();
3028
+ // ---------------------------------------------------------------------------
3029
+ // Per-session DMK state subscriptions
3030
+ //
3031
+ // DMK only emits `device-disconnect` on the connector when `disconnect()`
3032
+ // is called explicitly. To pick up autonomous disconnects (USB unplug,
3033
+ // BLE drop, device sleep, DMK transport reset) we subscribe to
3034
+ // `_dmk.getDeviceSessionState({sessionId})` per active session and emit
3035
+ // `device-disconnect` ourselves the moment DMK reports the device went
3036
+ // to NOT_CONNECTED. The subscription is torn down on disconnect /
3037
+ // reset / observable completion to avoid leaks.
3038
+ // ---------------------------------------------------------------------------
3039
+ this._sessionStateSubs = /* @__PURE__ */ new Map();
2282
3040
  this._createTransport = createTransport;
2283
3041
  this.connectionType = options?.connectionType ?? "usb";
2284
3042
  this._providedDmk = options?.dmk;
2285
3043
  this._importLedgerKit = options?.importLedgerKit ?? defaultLedgerKitImporter;
3044
+ this._requirePreFlightScan = options?.requirePreFlightScan ?? false;
2286
3045
  if (this._providedDmk) {
2287
3046
  this._initManagers(this._providedDmk);
2288
3047
  }
@@ -2300,38 +3059,23 @@ var LedgerConnectorBase = class {
2300
3059
  importLedgerKit: this._importLedgerKit
2301
3060
  };
2302
3061
  }
2303
- // "D5:75:7D:4B:51:E8" -> "A58F"
2304
- /** Register a connectId <-> path mapping from a device descriptor. */
2305
- _registerDeviceId(descriptor) {
2306
- const connectId = this._resolveConnectId(descriptor);
2307
- this._connectIdToPath.set(connectId, descriptor.path);
2308
- this._pathToConnectId.set(descriptor.path, connectId);
2309
- return connectId;
2310
- }
2311
- /** Get DMK path from external connectId. Falls back to connectId itself. */
2312
- _getPathForConnectId(connectId) {
2313
- return this._connectIdToPath.get(connectId) ?? connectId;
2314
- }
2315
- /** Get external connectId from DMK path. Falls back to path itself. */
2316
- _getConnectIdForPath(path) {
2317
- return this._pathToConnectId.get(path) ?? path;
2318
- }
2319
3062
  // ---------------------------------------------------------------------------
2320
3063
  // Protected — hooks for subclasses
2321
3064
  // ---------------------------------------------------------------------------
2322
3065
  /**
2323
3066
  * Resolve the connectId for a discovered device descriptor.
2324
3067
  * Default: use the DMK path (ephemeral UUID).
2325
- * Override in subclasses to extract stable identifiers (e.g. BLE hex ID).
3068
+ * Override in subclasses only when the public connectId differs from the transport path.
2326
3069
  */
2327
3070
  _resolveConnectId(descriptor) {
2328
3071
  return descriptor.path;
2329
3072
  }
2330
- // ---------------------------------------------------------------------------
2331
- // IConnector -- Device discovery
2332
- // ---------------------------------------------------------------------------
2333
- async searchDevices() {
2334
- const dm = await this._getDeviceManager();
3073
+ /**
3074
+ * Authoritative discovery for this transport. Default = enumerate snapshot
3075
+ * (USB/HID). BLE overrides to drive from a live scan window since DMK's
3076
+ * enumerate() cache survives peripherals going offline.
3077
+ */
3078
+ async _discoverDescriptors(dm) {
2335
3079
  let descriptors = await dm.enumerate();
2336
3080
  if (descriptors.length === 0) {
2337
3081
  try {
@@ -2340,92 +3084,266 @@ var LedgerConnectorBase = class {
2340
3084
  }
2341
3085
  descriptors = await dm.enumerate();
2342
3086
  }
2343
- const result = descriptors.map((d) => {
2344
- const connectId = this._registerDeviceId(d);
2345
- return {
2346
- connectId,
2347
- deviceId: d.path,
2348
- name: d.name || d.type || "Ledger",
2349
- model: d.type
2350
- };
2351
- });
3087
+ return descriptors;
3088
+ }
3089
+ // ---------------------------------------------------------------------------
3090
+ // IConnector -- Device discovery
3091
+ // ---------------------------------------------------------------------------
3092
+ async searchDevices() {
3093
+ const dm = await this._getDeviceManager();
3094
+ const descriptors = await this._discoverDescriptors(dm);
3095
+ const resolvedDescriptors = descriptors.map((d) => ({
3096
+ descriptor: d,
3097
+ connectId: this._resolveConnectId(d)
3098
+ }));
3099
+ const result = resolvedDescriptors.map(({ descriptor: d, connectId }) => ({
3100
+ connectId,
3101
+ deviceId: d.path,
3102
+ name: d.name || d.type || "Ledger",
3103
+ model: d.type,
3104
+ modelName: d.modelName,
3105
+ rssi: d.rssi,
3106
+ isConnectable: d.isConnectable,
3107
+ serialNumber: d.serialNumber
3108
+ }));
3109
+ debugLog(
3110
+ `[DMK] connector.searchDevices() return count=${result.length} ids=[${result.map((r) => r.connectId).join(",")}] paths=[${result.map((r) => r.deviceId).join(",")}]`
3111
+ );
2352
3112
  return result;
2353
3113
  }
2354
3114
  // ---------------------------------------------------------------------------
2355
3115
  // IConnector -- Connection
2356
3116
  // ---------------------------------------------------------------------------
2357
3117
  async connect(deviceId) {
2358
- const dm = await this._getDeviceManager();
2359
- let discovered = await this.searchDevices();
2360
- const dmkPath = deviceId ? this._getPathForConnectId(deviceId) : void 0;
2361
- let targetPath = dmkPath;
3118
+ const callerSuppliedConnectId = Boolean(deviceId);
3119
+ let targetPath = deviceId;
2362
3120
  if (!targetPath) {
2363
- const descriptors = await dm.enumerate();
2364
- if (descriptors.length === 0) {
3121
+ const discovered = await this.searchDevices();
3122
+ if (discovered.length === 0) {
2365
3123
  throw new Error(
2366
- `No Ledger device found. Make sure the device is connected${this.connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
3124
+ `No Ledger device found. Make sure the device is connected${isLedgerBleConnectionType(this.connectionType) ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
2367
3125
  );
2368
3126
  }
2369
- targetPath = descriptors[0].path;
2370
- }
2371
- const externalConnectId = this._getConnectIdForPath(targetPath);
3127
+ targetPath = discovered[0].deviceId;
3128
+ }
3129
+ const externalConnectId = targetPath;
3130
+ const HANG_CEILING_MS = 5 * 6e4;
3131
+ const dmConnectWithObserve = async (dm, path) => {
3132
+ if (!isLedgerBleConnectionType(this.connectionType)) return dm.connect(path);
3133
+ let timeoutId;
3134
+ let timedOut = false;
3135
+ const connectPromise = dm.connect(path);
3136
+ const hangPromise = new Promise((_, reject) => {
3137
+ timeoutId = setTimeout(() => {
3138
+ timedOut = true;
3139
+ const err = new Error(
3140
+ `Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
3141
+ );
3142
+ err._tag = ERROR_TAG.BlePairingTimeout;
3143
+ err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
3144
+ reject(err);
3145
+ }, HANG_CEILING_MS);
3146
+ });
3147
+ try {
3148
+ return await Promise.race([connectPromise, hangPromise]);
3149
+ } catch (err) {
3150
+ const e = err;
3151
+ debugLog("[DMK] dm.connect rejected \u2014 observed:", {
3152
+ _tag: e?._tag,
3153
+ message: e?.message,
3154
+ errorCode: e?.errorCode,
3155
+ statusCode: e?.statusCode,
3156
+ originalTag: e?.originalError?._tag,
3157
+ originalMessage: e?.originalError?.message
3158
+ });
3159
+ if (timedOut) {
3160
+ void connectPromise.then(
3161
+ (sessionId) => dm.disconnect(sessionId).catch(() => void 0),
3162
+ () => void 0
3163
+ );
3164
+ }
3165
+ throw err;
3166
+ } finally {
3167
+ if (timeoutId) clearTimeout(timeoutId);
3168
+ }
3169
+ };
3170
+ const isBleDirectConnect = isLedgerBleConnectionType(this.connectionType) && callerSuppliedConnectId;
3171
+ const throwNotAdvertising = () => {
3172
+ const err = new Error(
3173
+ "Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
3174
+ );
3175
+ err._tag = ERROR_TAG.DeviceNotAdvertising;
3176
+ err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
3177
+ throw err;
3178
+ };
2372
3179
  const doConnect = async (path) => {
2373
- const sessionId = await dm.connect(path);
3180
+ const dm = await this._getDeviceManager();
3181
+ if (isBleDirectConnect && !dm.hasDiscoveredDevice(path)) {
3182
+ const live = await dm.getLiveDevices(BLE_CONNECT_SCAN_TIMEOUT_MS);
3183
+ if (this._requirePreFlightScan && !live.some((d) => d.id === path)) {
3184
+ throwNotAdvertising();
3185
+ }
3186
+ }
3187
+ let sessionId;
3188
+ try {
3189
+ sessionId = await dmConnectWithObserve(dm, path);
3190
+ } catch (err) {
3191
+ if (isBleDirectConnect && err?._tag === ERROR_TAG.DeviceNotInDiscoveryCache) {
3192
+ throwNotAdvertising();
3193
+ }
3194
+ throw err;
3195
+ }
3196
+ this._watchSessionState(sessionId, externalConnectId);
3197
+ const info = dm.getDiscoveredDeviceInfo(path);
2374
3198
  const session = {
2375
3199
  sessionId,
2376
3200
  deviceInfo: {
2377
3201
  vendor: "ledger",
2378
- model: "unknown",
3202
+ model: info?.model ?? "unknown",
3203
+ modelName: info?.modelName,
2379
3204
  firmwareVersion: "unknown",
2380
3205
  deviceId: path,
2381
3206
  connectId: externalConnectId,
2382
3207
  connectionType: this.connectionType,
3208
+ rssi: info?.rssi,
2383
3209
  capabilities: { persistentDeviceIdentity: false }
2384
3210
  }
2385
3211
  };
2386
- const realName = discovered.find((d) => d.connectId === externalConnectId)?.name ?? "Ledger";
3212
+ const name = dm.getDeviceName(path) ?? "Ledger";
2387
3213
  this._emit("device-connect", {
2388
- device: {
2389
- connectId: externalConnectId,
2390
- deviceId: path,
2391
- name: realName
2392
- }
3214
+ device: { connectId: externalConnectId, deviceId: path, name }
2393
3215
  });
2394
3216
  return session;
2395
3217
  };
2396
3218
  try {
2397
3219
  return await doConnect(targetPath);
2398
- } catch {
3220
+ } catch (err) {
2399
3221
  this._resetSignersAndSessions();
2400
- const dm2 = await this._getDeviceManager();
2401
- discovered = await this.searchDevices();
2402
- const retryPath = this._getPathForConnectId(externalConnectId);
2403
- if (!retryPath || retryPath === externalConnectId) {
2404
- const descriptors = await dm2.enumerate();
2405
- if (descriptors.length === 0) {
2406
- throw new Error(
2407
- `No Ledger device found after retry. Make sure the device is connected${this.connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
2408
- );
3222
+ if (isLedgerBleConnectionType(this.connectionType)) {
3223
+ const tag = err?._tag;
3224
+ if (isKnownConnectionTag(tag)) {
3225
+ throw err;
2409
3226
  }
2410
- return doConnect(descriptors[0].path);
3227
+ const wrapped = new Error(
3228
+ "Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
3229
+ );
3230
+ wrapped._tag = ERROR_TAG.BleGattBondingFailed;
3231
+ wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
3232
+ wrapped.originalError = err;
3233
+ throw wrapped;
2411
3234
  }
2412
- return doConnect(retryPath);
3235
+ if (err && typeof err === "object" && err._tag) {
3236
+ const taggedError = err instanceof Error ? err : Object.assign(
3237
+ new Error(err.message ?? "Ledger device error"),
3238
+ err
3239
+ );
3240
+ throw taggedError;
3241
+ }
3242
+ throw new Error("Ledger device appears unplugged. Plug in the device and try again.");
2413
3243
  }
2414
3244
  }
2415
3245
  async disconnect(sessionId) {
2416
3246
  if (!this._deviceManager) return;
2417
3247
  const deviceId = this._deviceManager.getDeviceId(sessionId);
2418
3248
  this._signerManager?.invalidate(sessionId);
3249
+ this._unwatchSessionState(sessionId);
2419
3250
  await this._deviceManager.disconnect(sessionId);
2420
3251
  if (deviceId) {
2421
3252
  this._emit("device-disconnect", { connectId: deviceId });
2422
3253
  }
2423
3254
  }
3255
+ /**
3256
+ * Subscribe to DMK's per-session state observable so that any autonomous
3257
+ * disconnect (USB unplug, BLE drop, device sleep, transport reset) is
3258
+ * surfaced as a `device-disconnect` event — without this, the upstream
3259
+ * `LedgerAdapter._sessions` map would hold a dead session entry until
3260
+ * the next call hit `DeviceSessionNotFound`.
3261
+ *
3262
+ * Best-effort: any error subscribing is swallowed so a flaky DMK
3263
+ * doesn't break the connect path.
3264
+ */
3265
+ _watchSessionState(sessionId, externalConnectId) {
3266
+ const dmk = this._dmk;
3267
+ if (!dmk) return;
3268
+ const previous = this._sessionStateSubs.get(sessionId);
3269
+ if (previous) {
3270
+ try {
3271
+ previous.unsubscribe();
3272
+ } catch {
3273
+ }
3274
+ }
3275
+ try {
3276
+ const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({
3277
+ next: (state) => {
3278
+ if (state?.deviceStatus === "NOT CONNECTED") {
3279
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3280
+ }
3281
+ },
3282
+ error: () => {
3283
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3284
+ },
3285
+ complete: () => {
3286
+ this._handleAutonomousDisconnect(sessionId, externalConnectId);
3287
+ }
3288
+ });
3289
+ this._sessionStateSubs.set(sessionId, sub);
3290
+ } catch (err) {
3291
+ debugLog("[DMK] _watchSessionState subscribe failed:", err);
3292
+ }
3293
+ }
3294
+ _unwatchSessionState(sessionId) {
3295
+ const sub = this._sessionStateSubs.get(sessionId);
3296
+ if (!sub) return;
3297
+ try {
3298
+ sub.unsubscribe();
3299
+ } catch {
3300
+ }
3301
+ this._sessionStateSubs.delete(sessionId);
3302
+ }
3303
+ _handleAutonomousDisconnect(sessionId, externalConnectId) {
3304
+ if (!this._sessionStateSubs.has(sessionId)) return;
3305
+ debugLog(
3306
+ "[DMK] autonomous disconnect detected \u2014 sessionId:",
3307
+ sessionId,
3308
+ "connectId:",
3309
+ externalConnectId
3310
+ );
3311
+ this._unwatchSessionState(sessionId);
3312
+ this._signerManager?.invalidate(sessionId);
3313
+ this._cancellers.get(sessionId)?.({
3314
+ code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
3315
+ tag: "DeviceDisconnected",
3316
+ message: "Device disconnected"
3317
+ });
3318
+ this._cancellers.delete(sessionId);
3319
+ this._emit("device-disconnect", { connectId: externalConnectId });
3320
+ }
2424
3321
  // ---------------------------------------------------------------------------
2425
3322
  // IConnector -- Method dispatch
2426
3323
  // ---------------------------------------------------------------------------
2427
3324
  async call(sessionId, method, params) {
2428
3325
  debugLog("[DMK] call:", method, JSON.stringify(params));
3326
+ try {
3327
+ return await this._dispatch(sessionId, method, params);
3328
+ } catch (err) {
3329
+ if (isAppStuckByApdu(err)) {
3330
+ throw Object.assign(new Error("Ledger app is unresponsive"), {
3331
+ code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
3332
+ _tag: ERROR_TAG.DeviceAppStuck,
3333
+ originalError: err
3334
+ });
3335
+ }
3336
+ if (isTransportStuck(err)) {
3337
+ throw Object.assign(new Error("Device communication interrupted, please retry"), {
3338
+ code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
3339
+ _tag: ERROR_TAG.DeviceTransportStuck,
3340
+ originalError: err
3341
+ });
3342
+ }
3343
+ throw err;
3344
+ }
3345
+ }
3346
+ async _dispatch(sessionId, method, params) {
2429
3347
  const ctx = this._ctxForMethod(method);
2430
3348
  switch (method) {
2431
3349
  // EVM
@@ -2565,14 +3483,13 @@ var LedgerConnectorBase = class {
2565
3483
  }
2566
3484
  /**
2567
3485
  * Replace an old session with a new one after app switch.
2568
- * Updates the connectId→path mapping so subsequent calls() use the new session,
2569
- * and emits device-connect so the adapter updates its _sessions Map.
3486
+ * Emits device-connect so the adapter updates its _sessions Map.
2570
3487
  */
2571
3488
  _replaceSession(oldSessionId, _newSessionId) {
2572
3489
  const dm = this._deviceManager;
2573
3490
  if (!dm) return;
2574
3491
  const oldDeviceId = dm.getDeviceId(oldSessionId);
2575
- const connectId = oldDeviceId ? this._pathToConnectId.get(oldDeviceId) : void 0;
3492
+ const connectId = oldDeviceId;
2576
3493
  this._signerManager?.invalidate(oldSessionId);
2577
3494
  if (connectId) {
2578
3495
  this._emit("device-connect", {
@@ -2585,13 +3502,18 @@ var LedgerConnectorBase = class {
2585
3502
  }
2586
3503
  }
2587
3504
  /**
2588
- * Light reset: clear signer/session state but keep DMK, BLE scan, and ID mapping alive.
3505
+ * Light reset: clear signer/session state but keep DMK alive.
2589
3506
  * Used by connect() retry — we want to re-discover with the same transport.
3507
+ *
3508
+ * Note: drops the device manager but tears down its RxJS subs first via
3509
+ * disposeKeepingDmk(). Bare null-assignment would leak _listenSub /
3510
+ * _discoverySub and double-scan after the next _initManagers().
2590
3511
  */
2591
3512
  _resetSignersAndSessions() {
2592
3513
  debugLog("[DMK] _resetSignersAndSessions called");
2593
3514
  this._signerManager?.clearAll();
2594
3515
  this._signerManager = null;
3516
+ this._deviceManager?.disposeKeepingDmk();
2595
3517
  this._deviceManager = null;
2596
3518
  }
2597
3519
  _resetAll() {
@@ -2603,13 +3525,18 @@ var LedgerConnectorBase = class {
2603
3525
  }
2604
3526
  }
2605
3527
  this._cancellers.clear();
3528
+ for (const sub of this._sessionStateSubs.values()) {
3529
+ try {
3530
+ sub.unsubscribe();
3531
+ } catch {
3532
+ }
3533
+ }
3534
+ this._sessionStateSubs.clear();
2606
3535
  this._signerManager?.clearAll();
2607
3536
  this._deviceManager?.dispose();
2608
3537
  this._deviceManager = null;
2609
3538
  this._signerManager = null;
2610
3539
  this._dmk = null;
2611
- this._connectIdToPath.clear();
2612
- this._pathToConnectId.clear();
2613
3540
  }
2614
3541
  // ---------------------------------------------------------------------------
2615
3542
  // Private -- Events
@@ -2643,15 +3570,21 @@ var LedgerConnectorBase = class {
2643
3570
  };
2644
3571
  }
2645
3572
  _wrapError(err, opts) {
2646
- const mapped = mapLedgerError(err, opts);
2647
- const error = new Error(mapped.message);
2648
3573
  const src = err && typeof err === "object" ? err : {};
3574
+ const hasSerializedCode = typeof src.code === "number" && HARDWARE_ERROR_CODE_VALUES.has(src.code);
3575
+ const mapped = hasSerializedCode ? {
3576
+ code: src.code,
3577
+ message: typeof src.message === "string" ? src.message : "Unknown Ledger error",
3578
+ appName: typeof src.appName === "string" ? src.appName : opts?.defaultAppName
3579
+ } : mapLedgerError(err, opts);
3580
+ const error = new Error(mapped.message);
2649
3581
  Object.assign(error, {
2650
3582
  code: mapped.code,
2651
3583
  appName: mapped.appName,
2652
3584
  _tag: src._tag,
2653
3585
  errorCode: src.errorCode,
2654
- _lastStep: src._lastStep
3586
+ _lastStep: src._lastStep,
3587
+ _deviceActionSteps: src._deviceActionSteps
2655
3588
  });
2656
3589
  Object.defineProperty(error, "originalError", {
2657
3590
  value: err,
@@ -2662,13 +3595,6 @@ var LedgerConnectorBase = class {
2662
3595
  return error;
2663
3596
  }
2664
3597
  };
2665
-
2666
- // src/utils/bleIdentity.ts
2667
- function extractBleHexId(name) {
2668
- if (!name) return void 0;
2669
- const match = name.match(/\b([0-9A-Fa-f]{4})$/);
2670
- return match ? match[1].toUpperCase() : void 0;
2671
- }
2672
3598
  // Annotate the CommonJS export names for ESM import in node:
2673
3599
  0 && (module.exports = {
2674
3600
  AppManager,
@@ -2680,12 +3606,15 @@ function extractBleHexId(name) {
2680
3606
  SignerEth,
2681
3607
  SignerManager,
2682
3608
  SignerSol,
3609
+ debugLog,
2683
3610
  deviceActionToPromise,
2684
- extractBleHexId,
2685
- isDebugEnabled,
2686
3611
  isDeviceLockedError,
3612
+ isLedgerBleConnectionType,
3613
+ isLedgerBleDescriptor,
3614
+ isLedgerDmkBleTransport,
2687
3615
  ledgerFailure,
2688
3616
  mapLedgerError,
2689
- setDebugEnabled
3617
+ offSdkEvent,
3618
+ onSdkEvent
2690
3619
  });
2691
3620
  //# sourceMappingURL=index.js.map