@onekeyfe/hwk-ledger-adapter 1.1.26-alpha.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2309 @@
1
+ // src/adapter/LedgerAdapter.ts
2
+ import {
3
+ success,
4
+ failure,
5
+ HardwareErrorCode as HardwareErrorCode2,
6
+ TypedEventEmitter,
7
+ DEVICE,
8
+ UI_REQUEST,
9
+ CHAIN_FINGERPRINT_PATHS,
10
+ deriveDeviceFingerprint
11
+ } from "@onekeyfe/hwk-adapter-core";
12
+
13
+ // src/errors.ts
14
+ import { HardwareErrorCode, enrichErrorMessage } from "@onekeyfe/hwk-adapter-core";
15
+ var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
16
+ var USER_REJECTED_CODES = /* @__PURE__ */ new Set(["6985", "27013"]);
17
+ var WRONG_APP_CODES = /* @__PURE__ */ new Set(["6e00", "28160", "6d00", "27904", "6a83", "27267"]);
18
+ var APP_NOT_INSTALLED_CODES = /* @__PURE__ */ new Set(["6807", "26631"]);
19
+ function isDeviceLockedError(err) {
20
+ if (!err || typeof err !== "object") return false;
21
+ const e = err;
22
+ if (e.errorCode != null && LOCKED_ERROR_CODES.has(String(e.errorCode))) return true;
23
+ if (e.statusCode != null && LOCKED_ERROR_CODES.has(String(e.statusCode))) return true;
24
+ if (e._tag === "DeviceLockedError") return true;
25
+ if (typeof e.message === "string" && /locked|device exchange error/i.test(e.message)) return true;
26
+ if (e.originalError != null && isDeviceLockedError(e.originalError)) return true;
27
+ if (e.error != null && e._tag && isDeviceLockedError(e.error)) return true;
28
+ return false;
29
+ }
30
+ function hasStatusCode(err, codeSet) {
31
+ if (!err || typeof err !== "object") return false;
32
+ const e = err;
33
+ if (e.errorCode != null && codeSet.has(String(e.errorCode))) return true;
34
+ if (e.statusCode != null && codeSet.has(String(e.statusCode))) return true;
35
+ if (e.originalError != null && hasStatusCode(e.originalError, codeSet)) return true;
36
+ if (e.error != null && e._tag && hasStatusCode(e.error, codeSet)) return true;
37
+ return false;
38
+ }
39
+ function isUserRejectedError(err) {
40
+ if (!err || typeof err !== "object") return false;
41
+ const e = err;
42
+ if (e._tag === "UserRefusedOnDevice") return true;
43
+ if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
44
+ if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
45
+ return false;
46
+ }
47
+ function isWrongAppError(err) {
48
+ if (!err || typeof err !== "object") return false;
49
+ const e = err;
50
+ if (e._tag === "WrongAppOpenedError" || e._tag === "InvalidStatusWordError") {
51
+ if (hasStatusCode(err, WRONG_APP_CODES)) return true;
52
+ }
53
+ if (typeof e.message === "string" && /wrong app|open the .* app|CLA not supported/i.test(e.message))
54
+ return true;
55
+ if (hasStatusCode(err, WRONG_APP_CODES)) return true;
56
+ return false;
57
+ }
58
+ function isAppNotInstalledError(err) {
59
+ if (!err || typeof err !== "object") return false;
60
+ const e = err;
61
+ if (e._tag === "OpenAppCommandError") return true;
62
+ if (typeof e.message === "string" && /unknown application/i.test(e.message)) return true;
63
+ if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
64
+ return false;
65
+ }
66
+ function isDeviceDisconnectedError(err) {
67
+ if (!err || typeof err !== "object") return false;
68
+ const e = err;
69
+ if (e._tag === "DeviceNotRecognizedError" || e._tag === "DeviceSessionNotFound") return true;
70
+ if (typeof e.message === "string" && /disconnected|not found|no device|unplugged|session.*not.*found|timed out.*locked/i.test(
71
+ e.message
72
+ ))
73
+ return true;
74
+ return false;
75
+ }
76
+ var TIMEOUT_TAGS = /* @__PURE__ */ new Set([
77
+ "DeviceExchangeTimeoutError",
78
+ "SendApduTimeoutError",
79
+ "SendCommandTimeoutError"
80
+ ]);
81
+ function isTimeoutError(err) {
82
+ if (!err || typeof err !== "object") return false;
83
+ const e = err;
84
+ if (typeof e._tag === "string" && TIMEOUT_TAGS.has(e._tag)) return true;
85
+ if (e.code === HardwareErrorCode.OperationTimeout) return true;
86
+ return false;
87
+ }
88
+ function mapLedgerError(err) {
89
+ let originalMessage = "Unknown Ledger error";
90
+ if (err instanceof Error) {
91
+ originalMessage = err.message;
92
+ } else if (err && typeof err === "object") {
93
+ const e = err;
94
+ originalMessage = String(e.message ?? e._tag ?? e.type ?? JSON.stringify(err));
95
+ }
96
+ let code;
97
+ if (isDeviceLockedError(err)) {
98
+ code = HardwareErrorCode.DeviceLocked;
99
+ } else if (isUserRejectedError(err)) {
100
+ code = HardwareErrorCode.UserRejected;
101
+ } else if (isWrongAppError(err)) {
102
+ code = HardwareErrorCode.WrongApp;
103
+ } else if (isAppNotInstalledError(err)) {
104
+ code = HardwareErrorCode.AppNotOpen;
105
+ } else if (isDeviceDisconnectedError(err)) {
106
+ code = HardwareErrorCode.DeviceDisconnected;
107
+ } else if (isTimeoutError(err)) {
108
+ code = HardwareErrorCode.OperationTimeout;
109
+ } else {
110
+ code = HardwareErrorCode.UnknownError;
111
+ }
112
+ return { code, message: enrichErrorMessage(code, originalMessage) };
113
+ }
114
+
115
+ // src/adapter/LedgerAdapter.ts
116
+ var _LedgerAdapter = class _LedgerAdapter {
117
+ constructor(connector) {
118
+ this.vendor = "ledger";
119
+ this.emitter = new TypedEventEmitter();
120
+ this._uiHandler = null;
121
+ // Device cache: tracks discovered devices from connector events
122
+ this._discoveredDevices = /* @__PURE__ */ new Map();
123
+ // Session tracking: maps connectId -> sessionId
124
+ this._sessions = /* @__PURE__ */ new Map();
125
+ // Pending device-connect resolve — set by _waitForDeviceConnect, resolved by uiResponse
126
+ this._deviceConnectResolve = null;
127
+ // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
128
+ this._connectingPromise = null;
129
+ // ---------------------------------------------------------------------------
130
+ // Event translation
131
+ // ---------------------------------------------------------------------------
132
+ this.deviceConnectHandler = (data) => {
133
+ const deviceInfo = this.connectorDeviceToDeviceInfo(data.device);
134
+ this._discoveredDevices.set(deviceInfo.connectId, deviceInfo);
135
+ this._sessions.delete(deviceInfo.connectId);
136
+ this.emitter.emit(DEVICE.CONNECT, {
137
+ type: DEVICE.CONNECT,
138
+ payload: deviceInfo
139
+ });
140
+ };
141
+ this.deviceDisconnectHandler = (data) => {
142
+ this._discoveredDevices.delete(data.connectId);
143
+ this._sessions.delete(data.connectId);
144
+ this.emitter.emit(DEVICE.DISCONNECT, {
145
+ type: DEVICE.DISCONNECT,
146
+ payload: { connectId: data.connectId }
147
+ });
148
+ };
149
+ this.uiRequestHandler = (data) => {
150
+ this.handleUiEvent(data);
151
+ };
152
+ this.uiEventHandler = (data) => {
153
+ this.handleUiEvent(data);
154
+ };
155
+ this.connector = connector;
156
+ this.registerEventListeners();
157
+ }
158
+ // ---------------------------------------------------------------------------
159
+ // Transport
160
+ // ---------------------------------------------------------------------------
161
+ // Transport is decided at connector creation time. These methods
162
+ // satisfy the IHardwareWallet interface with sensible defaults.
163
+ get activeTransport() {
164
+ return "hid";
165
+ }
166
+ getAvailableTransports() {
167
+ return ["hid"];
168
+ }
169
+ async switchTransport(_type) {
170
+ }
171
+ // ---------------------------------------------------------------------------
172
+ // UI handler
173
+ // ---------------------------------------------------------------------------
174
+ setUiHandler(handler) {
175
+ this._uiHandler = handler;
176
+ }
177
+ // ---------------------------------------------------------------------------
178
+ // Lifecycle
179
+ // ---------------------------------------------------------------------------
180
+ async init(_config) {
181
+ }
182
+ /**
183
+ * Clear cached device/session state without tearing down the adapter.
184
+ * Call before retrying after errors or when the device state may be stale.
185
+ * The next operation will re-discover and re-connect automatically.
186
+ */
187
+ resetState() {
188
+ this._discoveredDevices.clear();
189
+ this._sessions.clear();
190
+ this._connectingPromise = null;
191
+ }
192
+ async dispose() {
193
+ this._deviceConnectResolve?.(true);
194
+ this._deviceConnectResolve = null;
195
+ this.unregisterEventListeners();
196
+ this.connector.reset();
197
+ this._uiHandler = null;
198
+ this._discoveredDevices.clear();
199
+ this._sessions.clear();
200
+ this.emitter.removeAllListeners();
201
+ }
202
+ // ---------------------------------------------------------------------------
203
+ // Device management
204
+ // ---------------------------------------------------------------------------
205
+ async searchDevices() {
206
+ await this._ensureDevicePermission();
207
+ const devices = await this.connector.searchDevices();
208
+ console.log("[DMK] adapter.searchDevices raw:", JSON.stringify(devices));
209
+ for (const d of devices) {
210
+ if (d.connectId) {
211
+ this._discoveredDevices.set(d.connectId, this.connectorDeviceToDeviceInfo(d));
212
+ }
213
+ }
214
+ if (this._discoveredDevices.size === 0) {
215
+ await this._ensureDevicePermission();
216
+ }
217
+ return Array.from(this._discoveredDevices.values());
218
+ }
219
+ async connectDevice(connectId) {
220
+ await this._ensureDevicePermission(connectId);
221
+ try {
222
+ const session = await this.connector.connect(connectId);
223
+ this._sessions.set(connectId, session.sessionId);
224
+ if (session.deviceInfo) {
225
+ this._discoveredDevices.set(connectId, session.deviceInfo);
226
+ }
227
+ return success(connectId);
228
+ } catch (err) {
229
+ return this.errorToFailure(err);
230
+ }
231
+ }
232
+ async disconnectDevice(connectId) {
233
+ const sessionId = this._sessions.get(connectId);
234
+ if (sessionId) {
235
+ await this.connector.disconnect(sessionId);
236
+ this._sessions.delete(connectId);
237
+ }
238
+ }
239
+ async getDeviceInfo(connectId, deviceId) {
240
+ await this._ensureDevicePermission(connectId, deviceId);
241
+ const cached = this._discoveredDevices.get(connectId) ?? Array.from(this._discoveredDevices.values()).find((d) => d.deviceId === deviceId);
242
+ if (cached) {
243
+ return success(cached);
244
+ }
245
+ return failure(
246
+ HardwareErrorCode2.DeviceNotFound,
247
+ "Device not found in cache. Call searchDevices() or wait for a device-connected event first."
248
+ );
249
+ }
250
+ getSupportedChains() {
251
+ return ["evm", "btc", "sol", "tron"];
252
+ }
253
+ // ---------------------------------------------------------------------------
254
+ // Chain call helper
255
+ // ---------------------------------------------------------------------------
256
+ async callChain(connectId, deviceId, chain, method, params, skipFingerprint = false) {
257
+ await this._ensureDevicePermission(connectId, deviceId);
258
+ if (!skipFingerprint && !await this._verifyDeviceFingerprint(connectId, deviceId, chain)) {
259
+ return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
260
+ }
261
+ try {
262
+ const result = await this.connectorCall(connectId, method, params);
263
+ return success(result);
264
+ } catch (err) {
265
+ return this.errorToFailure(err);
266
+ }
267
+ }
268
+ /**
269
+ * Batch version of callChain — checks permission and fingerprint once,
270
+ * then calls the connector for each param sequentially.
271
+ */
272
+ async callChainBatch(connectId, deviceId, chain, method, params, onProgress, skipFingerprint = false) {
273
+ await this._ensureDevicePermission(connectId, deviceId);
274
+ if (!skipFingerprint && !await this._verifyDeviceFingerprint(connectId, deviceId, chain)) {
275
+ return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
276
+ }
277
+ const results = [];
278
+ for (let i = 0; i < params.length; i++) {
279
+ try {
280
+ const result = await this.connectorCall(connectId, method, params[i]);
281
+ results.push(result);
282
+ onProgress?.({ index: i, total: params.length });
283
+ } catch (err) {
284
+ return this.errorToFailure(err);
285
+ }
286
+ }
287
+ return success(results);
288
+ }
289
+ // ---------------------------------------------------------------------------
290
+ // EVM chain methods
291
+ // ---------------------------------------------------------------------------
292
+ evmGetAddress(connectId, deviceId, params) {
293
+ return this.callChain(connectId, deviceId, "evm", "evmGetAddress", params);
294
+ }
295
+ evmGetAddresses(connectId, deviceId, params, onProgress) {
296
+ return this.callChainBatch(
297
+ connectId,
298
+ deviceId,
299
+ "evm",
300
+ "evmGetAddress",
301
+ params,
302
+ onProgress
303
+ );
304
+ }
305
+ evmGetPublicKey(connectId, deviceId, params) {
306
+ return this.callChain(connectId, deviceId, "evm", "evmGetAddress", params);
307
+ }
308
+ evmSignTransaction(connectId, deviceId, params) {
309
+ return this.callChain(connectId, deviceId, "evm", "evmSignTransaction", params);
310
+ }
311
+ evmSignMessage(connectId, deviceId, params) {
312
+ return this.callChain(connectId, deviceId, "evm", "evmSignMessage", params);
313
+ }
314
+ evmSignTypedData(connectId, deviceId, params) {
315
+ return this.callChain(connectId, deviceId, "evm", "evmSignTypedData", params);
316
+ }
317
+ // ---------------------------------------------------------------------------
318
+ // BTC chain methods
319
+ // ---------------------------------------------------------------------------
320
+ btcGetAddress(connectId, deviceId, params) {
321
+ return this.callChain(connectId, deviceId, "btc", "btcGetAddress", params);
322
+ }
323
+ btcGetAddresses(connectId, deviceId, params, onProgress) {
324
+ return this.callChainBatch(
325
+ connectId,
326
+ deviceId,
327
+ "btc",
328
+ "btcGetAddress",
329
+ params,
330
+ onProgress
331
+ );
332
+ }
333
+ btcGetPublicKey(connectId, deviceId, params) {
334
+ return this.callChain(connectId, deviceId, "btc", "btcGetPublicKey", params);
335
+ }
336
+ btcSignTransaction(connectId, deviceId, params) {
337
+ return this.callChain(connectId, deviceId, "btc", "btcSignTransaction", params);
338
+ }
339
+ btcSignMessage(connectId, deviceId, params) {
340
+ return this.callChain(connectId, deviceId, "btc", "btcSignMessage", params);
341
+ }
342
+ btcGetMasterFingerprint(connectId, deviceId) {
343
+ return this.callChain(
344
+ connectId,
345
+ deviceId,
346
+ "btc",
347
+ "btcGetMasterFingerprint",
348
+ {}
349
+ );
350
+ }
351
+ // ---------------------------------------------------------------------------
352
+ // SOL chain methods
353
+ // ---------------------------------------------------------------------------
354
+ solGetAddress(connectId, deviceId, params) {
355
+ return this.callChain(connectId, deviceId, "sol", "solGetAddress", params);
356
+ }
357
+ solGetAddresses(connectId, deviceId, params, onProgress) {
358
+ return this.callChainBatch(
359
+ connectId,
360
+ deviceId,
361
+ "sol",
362
+ "solGetAddress",
363
+ params,
364
+ onProgress
365
+ );
366
+ }
367
+ solGetPublicKey(connectId, deviceId, params) {
368
+ return this.callChain(connectId, deviceId, "sol", "solGetAddress", params);
369
+ }
370
+ solSignTransaction(connectId, deviceId, params) {
371
+ return this.callChain(connectId, deviceId, "sol", "solSignTransaction", params);
372
+ }
373
+ solSignMessage(connectId, deviceId, params) {
374
+ return this.callChain(connectId, deviceId, "sol", "solSignMessage", params);
375
+ }
376
+ // ---------------------------------------------------------------------------
377
+ // TRON chain methods
378
+ // ---------------------------------------------------------------------------
379
+ tronGetAddress(connectId, deviceId, params) {
380
+ return this.callChain(connectId, deviceId, "tron", "tronGetAddress", params, true);
381
+ }
382
+ tronGetAddresses(connectId, deviceId, params, onProgress) {
383
+ return this.callChainBatch(
384
+ connectId,
385
+ deviceId,
386
+ "tron",
387
+ "tronGetAddress",
388
+ params,
389
+ onProgress,
390
+ true
391
+ );
392
+ }
393
+ tronSignTransaction(connectId, deviceId, params) {
394
+ return this.callChain(
395
+ connectId,
396
+ deviceId,
397
+ "tron",
398
+ "tronSignTransaction",
399
+ params,
400
+ true
401
+ );
402
+ }
403
+ tronSignMessage(connectId, deviceId, params) {
404
+ return this.callChain(
405
+ connectId,
406
+ deviceId,
407
+ "tron",
408
+ "tronSignMessage",
409
+ params,
410
+ true
411
+ );
412
+ }
413
+ on(event, listener) {
414
+ this.emitter.on(event, listener);
415
+ }
416
+ off(event, listener) {
417
+ this.emitter.off(event, listener);
418
+ }
419
+ cancel(connectId) {
420
+ const sessionId = this._sessions.get(connectId) ?? connectId;
421
+ void this.connector.cancel(sessionId);
422
+ }
423
+ // ---------------------------------------------------------------------------
424
+ // Chain fingerprint
425
+ // ---------------------------------------------------------------------------
426
+ async getChainFingerprint(connectId, deviceId, chain) {
427
+ console.log(
428
+ "[LedgerAdapter] getChainFingerprint called, chain:",
429
+ chain,
430
+ "connectId:",
431
+ connectId || "(empty)",
432
+ "sessions:",
433
+ this._sessions.size
434
+ );
435
+ await this._ensureDevicePermission(connectId, deviceId);
436
+ console.log(
437
+ "[LedgerAdapter] getChainFingerprint permission ok, calling _deriveAddressForFingerprint"
438
+ );
439
+ try {
440
+ const address = await this._deriveAddressForFingerprint(connectId, chain);
441
+ console.log("[LedgerAdapter] getChainFingerprint address:", address?.substring(0, 20));
442
+ return success(deriveDeviceFingerprint(address));
443
+ } catch (err) {
444
+ console.error(
445
+ "[LedgerAdapter] getChainFingerprint error in _deriveAddressForFingerprint:",
446
+ chain,
447
+ err
448
+ );
449
+ return this.errorToFailure(err);
450
+ }
451
+ }
452
+ /**
453
+ * Verify that the connected device matches the expected fingerprint.
454
+ *
455
+ * - If deviceId is empty, verification is skipped (returns true).
456
+ * - deviceId is used here as the stored fingerprint to compare against.
457
+ */
458
+ async _verifyDeviceFingerprint(connectId, deviceId, chain) {
459
+ if (!deviceId) return true;
460
+ try {
461
+ const address = await this._deriveAddressForFingerprint(connectId, chain);
462
+ const fingerprint = deriveDeviceFingerprint(address);
463
+ return fingerprint === deviceId;
464
+ } catch (err) {
465
+ const mapped = mapLedgerError(err);
466
+ if (mapped.code === HardwareErrorCode2.WrongApp || mapped.code === HardwareErrorCode2.DeviceLocked) {
467
+ return true;
468
+ }
469
+ throw err;
470
+ }
471
+ }
472
+ /**
473
+ * Derive an address at the fixed testnet path for fingerprint generation.
474
+ */
475
+ async _deriveAddressForFingerprint(connectId, chain) {
476
+ const path = CHAIN_FINGERPRINT_PATHS[chain];
477
+ if (chain === "evm") {
478
+ const result = await this.connectorCall(connectId, "evmGetAddress", {
479
+ path,
480
+ showOnDevice: false
481
+ });
482
+ return result.address;
483
+ }
484
+ if (chain === "btc") {
485
+ const result = await this.connectorCall(connectId, "btcGetPublicKey", {
486
+ path,
487
+ showOnDevice: false
488
+ });
489
+ return result.xpub;
490
+ }
491
+ if (chain === "sol") {
492
+ const result = await this.connectorCall(connectId, "solGetAddress", {
493
+ path,
494
+ showOnDevice: false
495
+ });
496
+ return result.address;
497
+ }
498
+ if (chain === "tron") {
499
+ const result = await this.connectorCall(connectId, "tronGetAddress", {
500
+ path,
501
+ showOnDevice: false
502
+ });
503
+ return result.address;
504
+ }
505
+ throw new Error(`Unsupported chain for fingerprint: ${chain}`);
506
+ }
507
+ /**
508
+ * Wait for user to connect and unlock device.
509
+ * Emits 'ui-request' event via the adapter's own emitter.
510
+ * The consumer (monorepo adapter wrapper) listens for this and shows UI.
511
+ * When user confirms, they call adapter.deviceConnectResponse() which resolves this promise.
512
+ * Times out after 60 seconds if no response is received.
513
+ */
514
+ _waitForDeviceConnect(attempt) {
515
+ return new Promise((resolve, reject) => {
516
+ let settled = false;
517
+ const timer = setTimeout(() => {
518
+ if (!settled) {
519
+ settled = true;
520
+ this._deviceConnectResolve = null;
521
+ reject(new Error("Ledger device connect timed out after 60 seconds"));
522
+ }
523
+ }, _LedgerAdapter.DEVICE_CONNECT_TIMEOUT_MS);
524
+ this._deviceConnectResolve = (cancelled) => {
525
+ if (settled) return;
526
+ settled = true;
527
+ clearTimeout(timer);
528
+ this._deviceConnectResolve = null;
529
+ if (cancelled) {
530
+ reject(
531
+ Object.assign(new Error("User cancelled Ledger connection"), {
532
+ _tag: "DeviceNotRecognizedError"
533
+ })
534
+ );
535
+ } else {
536
+ resolve();
537
+ }
538
+ };
539
+ this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_CONNECT, {
540
+ type: UI_REQUEST.REQUEST_DEVICE_CONNECT,
541
+ payload: {
542
+ message: "Please connect and unlock your Ledger device",
543
+ retryCount: attempt,
544
+ maxRetries: _LedgerAdapter.MAX_DEVICE_RETRY
545
+ }
546
+ });
547
+ });
548
+ }
549
+ /**
550
+ * Called by consumer to respond to ui-request-device-connect.
551
+ * type='confirm' → retry search, type='cancel' → abort.
552
+ */
553
+ deviceConnectResponse(type) {
554
+ if (this._deviceConnectResolve) {
555
+ this._deviceConnectResolve(type === "cancel");
556
+ }
557
+ }
558
+ async ensureConnected(connectId) {
559
+ if (connectId && this._sessions.has(connectId)) {
560
+ return connectId;
561
+ }
562
+ if (this._sessions.size > 0) {
563
+ return this._sessions.keys().next().value;
564
+ }
565
+ if (this._connectingPromise) {
566
+ return this._connectingPromise;
567
+ }
568
+ this._connectingPromise = this._doConnect();
569
+ try {
570
+ return await this._connectingPromise;
571
+ } finally {
572
+ this._connectingPromise = null;
573
+ }
574
+ }
575
+ async _doConnect() {
576
+ for (let attempt = 0; attempt < _LedgerAdapter.MAX_DEVICE_RETRY; attempt++) {
577
+ const devices = await this.searchDevices();
578
+ if (devices.length > 0) {
579
+ return this._connectFirstOrSelect(devices);
580
+ }
581
+ if (attempt < _LedgerAdapter.MAX_DEVICE_RETRY - 1) {
582
+ await this._waitForDeviceConnect(attempt + 1);
583
+ }
584
+ }
585
+ throw Object.assign(
586
+ new Error(
587
+ "No Ledger device found after multiple attempts. Please connect and unlock your device."
588
+ ),
589
+ { _tag: "DeviceNotRecognizedError" }
590
+ );
591
+ }
592
+ async _connectFirstOrSelect(devices) {
593
+ if (devices.length === 1) {
594
+ const result2 = await this.connectDevice(devices[0].connectId);
595
+ if (!result2.success) {
596
+ throw Object.assign(new Error(result2.payload.error), { _tag: "DeviceNotRecognizedError" });
597
+ }
598
+ return devices[0].connectId;
599
+ }
600
+ if (this._uiHandler?.onSelectDevice) {
601
+ const selectedConnectId = await this._uiHandler.onSelectDevice(devices);
602
+ const result2 = await this.connectDevice(selectedConnectId);
603
+ if (!result2.success) {
604
+ throw Object.assign(new Error(result2.payload.error), { _tag: "DeviceNotRecognizedError" });
605
+ }
606
+ return selectedConnectId;
607
+ }
608
+ const result = await this.connectDevice(devices[0].connectId);
609
+ if (!result.success) {
610
+ throw Object.assign(new Error(result.payload.error), { _tag: "DeviceNotRecognizedError" });
611
+ }
612
+ return devices[0].connectId;
613
+ }
614
+ /**
615
+ * Call the connector with automatic session resolution and disconnect retry.
616
+ *
617
+ * 1. Resolves a valid connectId via ensureConnected()
618
+ * 2. Looks up sessionId from _sessions
619
+ * 3. Calls connector.call()
620
+ * 4. On disconnect error: clears stale session, re-connects, retries once
621
+ */
622
+ async connectorCall(connectId, method, params) {
623
+ console.log("[LedgerAdapter] connectorCall:", method, "connectId:", connectId || "(empty)");
624
+ const resolvedConnectId = await this.ensureConnected(connectId);
625
+ const sessionId = this._sessions.get(resolvedConnectId);
626
+ console.log(
627
+ "[LedgerAdapter] connectorCall resolved:",
628
+ method,
629
+ "resolvedConnectId:",
630
+ resolvedConnectId,
631
+ "sessionId:",
632
+ sessionId
633
+ );
634
+ if (!sessionId) {
635
+ throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
636
+ _tag: "DeviceSessionNotFound"
637
+ });
638
+ }
639
+ try {
640
+ return await this.connector.call(sessionId, method, params);
641
+ } catch (err) {
642
+ const errObj = err;
643
+ console.log("[LedgerAdapter] connectorCall error:", method, {
644
+ message: errObj?.message,
645
+ _tag: errObj?._tag,
646
+ errorCode: errObj?.errorCode,
647
+ statusCode: errObj?.statusCode,
648
+ isDisconnected: isDeviceDisconnectedError(err),
649
+ isLocked: isDeviceLockedError(err)
650
+ });
651
+ if (isDeviceDisconnectedError(err)) {
652
+ console.log("[LedgerAdapter] disconnected, retrying with fresh connection...");
653
+ this._discoveredDevices.clear();
654
+ return this._retryWithFreshConnection(resolvedConnectId, method, params, err);
655
+ }
656
+ if (isDeviceLockedError(err)) {
657
+ await this._waitForDeviceConnect(0);
658
+ return this.connector.call(sessionId, method, params);
659
+ }
660
+ if (isTimeoutError(err)) {
661
+ console.log("[LedgerAdapter] timeout, retrying with fresh connection...");
662
+ this._discoveredDevices.delete(resolvedConnectId);
663
+ return this._retryWithFreshConnection(resolvedConnectId, method, params, err);
664
+ }
665
+ throw err;
666
+ }
667
+ }
668
+ /** Clear stale session, reconnect, and retry the call. */
669
+ async _retryWithFreshConnection(resolvedConnectId, method, params, originalErr) {
670
+ this._sessions.delete(resolvedConnectId);
671
+ const retryConnectId = await this.ensureConnected();
672
+ const retrySessionId = this._sessions.get(retryConnectId);
673
+ if (!retrySessionId) {
674
+ throw originalErr;
675
+ }
676
+ return this.connector.call(retrySessionId, method, params);
677
+ }
678
+ /**
679
+ * Ensure device permission before proceeding.
680
+ * - No connectId (searchDevices): check environment-level permission
681
+ * - With connectId (business methods): check device-level permission
682
+ * If not granted, calls onDevicePermission so the consumer can request access.
683
+ */
684
+ async _ensureDevicePermission(connectId, deviceId) {
685
+ const transportType = "hid";
686
+ let granted = false;
687
+ let context;
688
+ if (this._uiHandler?.checkDevicePermission) {
689
+ try {
690
+ const result = await this._uiHandler.checkDevicePermission({
691
+ transportType,
692
+ connectId,
693
+ deviceId
694
+ });
695
+ granted = result.granted;
696
+ context = result.context;
697
+ } catch {
698
+ granted = false;
699
+ }
700
+ }
701
+ if (!granted) {
702
+ try {
703
+ await this._uiHandler?.onDevicePermission?.({ transportType, context });
704
+ } catch {
705
+ }
706
+ }
707
+ }
708
+ /**
709
+ * Convert a thrown error to a Response failure.
710
+ * Uses mapLedgerError to parse Ledger DMK error codes into HardwareErrorCode values.
711
+ */
712
+ errorToFailure(err) {
713
+ console.error("[LedgerAdapter] error:", err);
714
+ if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
715
+ const e = err;
716
+ return failure(e.code, e.message ?? "Unknown error");
717
+ }
718
+ const mapped = mapLedgerError(err);
719
+ return failure(mapped.code, mapped.message);
720
+ }
721
+ registerEventListeners() {
722
+ this.connector.on("device-connect", this.deviceConnectHandler);
723
+ this.connector.on("device-disconnect", this.deviceDisconnectHandler);
724
+ this.connector.on("ui-request", this.uiRequestHandler);
725
+ this.connector.on("ui-event", this.uiEventHandler);
726
+ }
727
+ unregisterEventListeners() {
728
+ this.connector.off("device-connect", this.deviceConnectHandler);
729
+ this.connector.off("device-disconnect", this.deviceDisconnectHandler);
730
+ this.connector.off("ui-request", this.uiRequestHandler);
731
+ this.connector.off("ui-event", this.uiEventHandler);
732
+ }
733
+ handleUiEvent(event) {
734
+ if (!event.type) return;
735
+ const payload = event.payload;
736
+ const deviceInfo = payload ? this.extractDeviceInfoFromPayload(payload) : this.unknownDevice();
737
+ switch (event.type) {
738
+ case "ui-request_confirmation":
739
+ this.emitter.emit(UI_REQUEST.REQUEST_BUTTON, {
740
+ type: UI_REQUEST.REQUEST_BUTTON,
741
+ payload: { device: deviceInfo }
742
+ });
743
+ break;
744
+ }
745
+ }
746
+ // ---------------------------------------------------------------------------
747
+ // Device info mapping
748
+ // ---------------------------------------------------------------------------
749
+ connectorDeviceToDeviceInfo(device) {
750
+ const isBle = device.connectId && /^[0-9A-Fa-f]{4}$/.test(device.connectId);
751
+ return {
752
+ vendor: "ledger",
753
+ model: device.model ?? "unknown",
754
+ firmwareVersion: "",
755
+ deviceId: device.deviceId,
756
+ connectId: device.connectId,
757
+ label: device.name,
758
+ connectionType: isBle ? "ble" : "usb",
759
+ capabilities: device.capabilities
760
+ };
761
+ }
762
+ extractDeviceInfoFromPayload(payload) {
763
+ return {
764
+ vendor: "ledger",
765
+ model: payload["model"] ?? "unknown",
766
+ firmwareVersion: "",
767
+ deviceId: payload["deviceId"] ?? payload["id"] ?? "",
768
+ connectId: payload["connectId"] ?? payload["path"] ?? "",
769
+ label: payload["label"],
770
+ connectionType: "usb"
771
+ };
772
+ }
773
+ unknownDevice() {
774
+ return {
775
+ vendor: "ledger",
776
+ model: "unknown",
777
+ firmwareVersion: "",
778
+ deviceId: "",
779
+ connectId: "",
780
+ connectionType: "usb"
781
+ };
782
+ }
783
+ };
784
+ // ---------------------------------------------------------------------------
785
+ // Private helpers
786
+ // ---------------------------------------------------------------------------
787
+ /**
788
+ * Ensure at least one device is connected and return a valid connectId.
789
+ *
790
+ * - If a session already exists for the given connectId, reuse it.
791
+ * - If ANY session exists (Ledger IDs are ephemeral), reuse it.
792
+ * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
793
+ */
794
+ _LedgerAdapter.MAX_DEVICE_RETRY = 3;
795
+ _LedgerAdapter.DEVICE_CONNECT_TIMEOUT_MS = 6e4;
796
+ var LedgerAdapter = _LedgerAdapter;
797
+
798
+ // src/device/LedgerDeviceManager.ts
799
+ var LedgerDeviceManager = class {
800
+ constructor(dmk) {
801
+ this._discovered = /* @__PURE__ */ new Map();
802
+ this._sessions = /* @__PURE__ */ new Map();
803
+ // deviceId → sessionId
804
+ this._sessionToDevice = /* @__PURE__ */ new Map();
805
+ // sessionId → deviceId
806
+ this._listenSub = null;
807
+ /**
808
+ * Trigger browser device selection (WebHID requestDevice).
809
+ * Starts discovery for a short period, then stops.
810
+ */
811
+ /**
812
+ * Start BLE discovery if not already running.
813
+ * Does NOT stop — DMK keeps scanning in the background so that
814
+ * listenToAvailableDevices() always has fresh data.
815
+ */
816
+ this._discoverySub = null;
817
+ this._dmk = dmk;
818
+ }
819
+ /**
820
+ * One-shot enumeration: subscribe to listenToAvailableDevices,
821
+ * take the first emission, unsubscribe, return DeviceDescriptors.
822
+ */
823
+ enumerate() {
824
+ console.log(
825
+ "[DMK] enumerate() called, dmk exists:",
826
+ !!this._dmk,
827
+ "listenToAvailableDevices exists:",
828
+ typeof this._dmk?.listenToAvailableDevices
829
+ );
830
+ return new Promise((resolve) => {
831
+ let resolved = false;
832
+ let syncResult = null;
833
+ let sub = null;
834
+ sub = this._dmk.listenToAvailableDevices({}).subscribe({
835
+ next: (devices) => {
836
+ if (resolved) return;
837
+ resolved = true;
838
+ this._discovered.clear();
839
+ console.log(
840
+ "[DMK] enumerate raw devices:",
841
+ JSON.stringify(
842
+ devices.map((d) => ({
843
+ id: d.id,
844
+ deviceModel: d.deviceModel,
845
+ transport: d.transport,
846
+ name: d.name,
847
+ rssi: d.rssi,
848
+ // Dump all keys to see what else is available
849
+ _keys: Object.keys(d)
850
+ }))
851
+ )
852
+ );
853
+ for (const d of devices) {
854
+ this._discovered.set(d.id, d);
855
+ }
856
+ if (sub) {
857
+ sub.unsubscribe();
858
+ resolve(
859
+ devices.map((d) => ({
860
+ path: d.id,
861
+ type: d.deviceModel.model,
862
+ name: d.name,
863
+ transport: d.transport
864
+ }))
865
+ );
866
+ } else {
867
+ syncResult = devices;
868
+ }
869
+ },
870
+ error: () => {
871
+ if (!resolved) {
872
+ resolved = true;
873
+ resolve([]);
874
+ }
875
+ }
876
+ });
877
+ if (syncResult !== null) {
878
+ sub.unsubscribe();
879
+ const devices = syncResult;
880
+ resolve(
881
+ devices.map((d) => ({
882
+ path: d.id,
883
+ type: d.deviceModel.model,
884
+ name: d.name,
885
+ transport: d.transport
886
+ }))
887
+ );
888
+ }
889
+ });
890
+ }
891
+ /**
892
+ * Continuous listening: tracks device connect/disconnect via diffing.
893
+ */
894
+ listen(onChange) {
895
+ this.stopListening();
896
+ let previousIds = /* @__PURE__ */ new Set();
897
+ this._listenSub = this._dmk.listenToAvailableDevices({}).subscribe({
898
+ next: (devices) => {
899
+ const currentIds = new Set(devices.map((d) => d.id));
900
+ for (const d of devices) {
901
+ this._discovered.set(d.id, d);
902
+ console.log(
903
+ "[DMK] listen device:",
904
+ JSON.stringify({
905
+ id: d.id,
906
+ deviceModel: d.deviceModel,
907
+ name: d.name
908
+ })
909
+ );
910
+ if (!previousIds.has(d.id)) {
911
+ onChange({
912
+ type: "device-connected",
913
+ descriptor: {
914
+ path: d.id,
915
+ type: d.deviceModel.model,
916
+ name: d.name,
917
+ transport: d.transport
918
+ }
919
+ });
920
+ }
921
+ }
922
+ for (const id of previousIds) {
923
+ if (!currentIds.has(id)) {
924
+ this._discovered.delete(id);
925
+ onChange({ type: "device-disconnected", descriptor: { path: id } });
926
+ }
927
+ }
928
+ previousIds = currentIds;
929
+ }
930
+ });
931
+ }
932
+ stopListening() {
933
+ this._listenSub?.unsubscribe();
934
+ this._listenSub = null;
935
+ }
936
+ requestDevice() {
937
+ if (this._discoverySub) {
938
+ return Promise.resolve();
939
+ }
940
+ console.log("[DMK] requestDevice() starting persistent BLE scan");
941
+ this._discoverySub = this._dmk.startDiscovering({}).subscribe({
942
+ next: (d) => {
943
+ console.log("[DMK] BLE discovered:", d.name || d.id);
944
+ this._discovered.set(d.id, d);
945
+ },
946
+ error: (err) => {
947
+ console.error("[DMK] BLE scan error:", err);
948
+ this._discoverySub = null;
949
+ }
950
+ });
951
+ return Promise.resolve();
952
+ }
953
+ /** Connect to a previously discovered device. Returns sessionId. */
954
+ async connect(deviceId) {
955
+ const device = this._discovered.get(deviceId);
956
+ if (!device) {
957
+ throw new Error(`Device "${deviceId}" not found. Call enumerate() or listen() first.`);
958
+ }
959
+ const sessionId = await this._dmk.connect({ device });
960
+ this._sessions.set(deviceId, sessionId);
961
+ this._sessionToDevice.set(sessionId, deviceId);
962
+ return sessionId;
963
+ }
964
+ /** Disconnect a session. */
965
+ async disconnect(sessionId) {
966
+ await this._dmk.disconnect({ sessionId });
967
+ const deviceId = this._sessionToDevice.get(sessionId);
968
+ if (deviceId) this._sessions.delete(deviceId);
969
+ this._sessionToDevice.delete(sessionId);
970
+ }
971
+ getSessionId(deviceId) {
972
+ return this._sessions.get(deviceId);
973
+ }
974
+ getDeviceId(sessionId) {
975
+ return this._sessionToDevice.get(sessionId);
976
+ }
977
+ /** Get the underlying DMK instance (needed by SignerManager). */
978
+ getDmk() {
979
+ return this._dmk;
980
+ }
981
+ stopDiscovery() {
982
+ if (this._discoverySub) {
983
+ this._discoverySub.unsubscribe();
984
+ this._discoverySub = null;
985
+ this._dmk.stopDiscovering();
986
+ }
987
+ }
988
+ dispose() {
989
+ this.stopListening();
990
+ this.stopDiscovery();
991
+ this._discovered.clear();
992
+ this._sessions.clear();
993
+ this._sessionToDevice.clear();
994
+ this._dmk.close();
995
+ }
996
+ };
997
+
998
+ // src/signer/deviceActionToPromise.ts
999
+ import { DeviceActionStatus } from "@ledgerhq/device-management-kit";
1000
+ var DEFAULT_TIMEOUT_MS = 3e4;
1001
+ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOUT_MS) {
1002
+ return new Promise((resolve, reject) => {
1003
+ let settled = false;
1004
+ let sub;
1005
+ let timer = null;
1006
+ const resetTimer = () => {
1007
+ if (timer) clearTimeout(timer);
1008
+ if (timeoutMs > 0) {
1009
+ timer = setTimeout(() => {
1010
+ if (!settled) {
1011
+ settled = true;
1012
+ sub?.unsubscribe();
1013
+ reject(new Error("Device action timed out \u2014 device may be locked or disconnected"));
1014
+ }
1015
+ }, timeoutMs);
1016
+ }
1017
+ };
1018
+ resetTimer();
1019
+ console.log("[DMK-Observable] subscribing to action.observable...");
1020
+ sub = action.observable.subscribe({
1021
+ next: (state) => {
1022
+ console.log("[DMK-Observable] next received");
1023
+ resetTimer();
1024
+ console.log(
1025
+ "[DMK-Observable] state:",
1026
+ JSON.stringify({
1027
+ status: state.status,
1028
+ intermediateValue: state.status === DeviceActionStatus.Pending ? state.intermediateValue : void 0,
1029
+ hasOutput: state.status === DeviceActionStatus.Completed,
1030
+ hasError: state.status === DeviceActionStatus.Error
1031
+ })
1032
+ );
1033
+ if (settled) return;
1034
+ if (state.status === DeviceActionStatus.Completed) {
1035
+ settled = true;
1036
+ if (timer) clearTimeout(timer);
1037
+ onInteraction?.("interaction-complete");
1038
+ sub?.unsubscribe();
1039
+ resolve(state.output);
1040
+ } else if (state.status === DeviceActionStatus.Error) {
1041
+ settled = true;
1042
+ if (timer) clearTimeout(timer);
1043
+ onInteraction?.("interaction-complete");
1044
+ sub?.unsubscribe();
1045
+ reject(state.error);
1046
+ } else if (state.status === DeviceActionStatus.Pending && onInteraction) {
1047
+ const interaction = state.intermediateValue?.requiredUserInteraction;
1048
+ if (interaction && interaction !== "none") {
1049
+ onInteraction(String(interaction));
1050
+ }
1051
+ }
1052
+ },
1053
+ error: (err) => {
1054
+ if (!settled) {
1055
+ settled = true;
1056
+ if (timer) clearTimeout(timer);
1057
+ sub?.unsubscribe();
1058
+ reject(err);
1059
+ }
1060
+ },
1061
+ complete: () => {
1062
+ if (!settled) {
1063
+ settled = true;
1064
+ if (timer) clearTimeout(timer);
1065
+ reject(new Error("Device action completed without result"));
1066
+ }
1067
+ }
1068
+ });
1069
+ });
1070
+ }
1071
+
1072
+ // src/signer/SignerEth.ts
1073
+ function hexToBytes(hex) {
1074
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
1075
+ const bytes = new Uint8Array(h.length / 2);
1076
+ for (let i = 0; i < bytes.length; i++) {
1077
+ bytes[i] = parseInt(h.substring(i * 2, i * 2 + 2), 16);
1078
+ }
1079
+ return bytes;
1080
+ }
1081
+ var INTERACTIVE_TIMEOUT_MS = 5 * 6e4;
1082
+ var SignerEth = class {
1083
+ constructor(_sdk) {
1084
+ this._sdk = _sdk;
1085
+ }
1086
+ async getAddress(derivationPath, options) {
1087
+ const checkOnDevice = options?.checkOnDevice ?? false;
1088
+ console.log("[DMK] getAddress \u2192 DMK:", { derivationPath, checkOnDevice });
1089
+ const action = this._sdk.getAddress(derivationPath, {
1090
+ checkOnDevice
1091
+ });
1092
+ const timeout = checkOnDevice ? INTERACTIVE_TIMEOUT_MS : void 0;
1093
+ return deviceActionToPromise(action, this.onInteraction, timeout);
1094
+ }
1095
+ async signTransaction(derivationPath, serializedTxHex) {
1096
+ const action = this._sdk.signTransaction(derivationPath, hexToBytes(serializedTxHex));
1097
+ return deviceActionToPromise(
1098
+ action,
1099
+ this.onInteraction,
1100
+ INTERACTIVE_TIMEOUT_MS
1101
+ );
1102
+ }
1103
+ async signMessage(derivationPath, message) {
1104
+ const action = this._sdk.signMessage(derivationPath, hexToBytes(message));
1105
+ return deviceActionToPromise(
1106
+ action,
1107
+ this.onInteraction,
1108
+ INTERACTIVE_TIMEOUT_MS
1109
+ );
1110
+ }
1111
+ async signTypedData(derivationPath, data) {
1112
+ const action = this._sdk.signTypedData(derivationPath, data);
1113
+ return deviceActionToPromise(
1114
+ action,
1115
+ this.onInteraction,
1116
+ INTERACTIVE_TIMEOUT_MS
1117
+ );
1118
+ }
1119
+ };
1120
+
1121
+ // src/signer/SignerManager.ts
1122
+ var SignerManager = class _SignerManager {
1123
+ constructor(dmk, builderFn) {
1124
+ this._cache = /* @__PURE__ */ new Map();
1125
+ this._dmk = dmk;
1126
+ this._builderFn = builderFn ?? _SignerManager._defaultBuilder();
1127
+ }
1128
+ async getOrCreate(sessionId) {
1129
+ const hadCached = this._cache.has(sessionId);
1130
+ this._cache.delete(sessionId);
1131
+ console.log("[DMK] SignerManager.getOrCreate:", { sessionId, hadCached, creating: true });
1132
+ const builder = await this._builderFn({ dmk: this._dmk, sessionId });
1133
+ const sdkSigner = builder.build();
1134
+ console.log("[DMK] SignerManager: new signer built");
1135
+ const signer = new SignerEth(sdkSigner);
1136
+ this._cache.set(sessionId, signer);
1137
+ return signer;
1138
+ }
1139
+ invalidate(sessionId) {
1140
+ this._cache.delete(sessionId);
1141
+ }
1142
+ clearAll() {
1143
+ this._cache.clear();
1144
+ }
1145
+ static _defaultBuilder() {
1146
+ let BuilderClass = null;
1147
+ return async (args) => {
1148
+ if (!BuilderClass) {
1149
+ const mod = await import("@ledgerhq/device-signer-kit-ethereum");
1150
+ BuilderClass = mod.SignerEthBuilder;
1151
+ }
1152
+ return new BuilderClass(args);
1153
+ };
1154
+ }
1155
+ };
1156
+
1157
+ // src/connector/chains/evm.ts
1158
+ import { stripHex, padHex64, HardwareErrorCode as HardwareErrorCode3 } from "@onekeyfe/hwk-adapter-core";
1159
+
1160
+ // src/connector/chains/utils.ts
1161
+ function normalizePath(path) {
1162
+ return path.startsWith("m/") ? path.slice(2) : path;
1163
+ }
1164
+
1165
+ // src/connector/chains/evm.ts
1166
+ async function evmGetAddress(ctx, sessionId, params) {
1167
+ const signer = await _getEthSigner(ctx, sessionId);
1168
+ const path = normalizePath(params.path);
1169
+ const checkOnDevice = params.showOnDevice ?? false;
1170
+ console.log("[DMK] evmGetAddress -> signer.getAddress:", { path, checkOnDevice });
1171
+ try {
1172
+ const result = await signer.getAddress(path, { checkOnDevice });
1173
+ return { address: result.address, publicKey: result.publicKey };
1174
+ } catch (err) {
1175
+ ctx.invalidateSession(sessionId);
1176
+ throw ctx.wrapError(err);
1177
+ }
1178
+ }
1179
+ async function evmSignTransaction(ctx, sessionId, params) {
1180
+ if (!params.serializedTx) {
1181
+ throw Object.assign(
1182
+ new Error(
1183
+ "Ledger requires a pre-serialized transaction (serializedTx). Provide an RLP-encoded hex string."
1184
+ ),
1185
+ { code: HardwareErrorCode3.InvalidParams }
1186
+ );
1187
+ }
1188
+ const signer = await _getEthSigner(ctx, sessionId);
1189
+ const path = normalizePath(params.path);
1190
+ try {
1191
+ const result = await signer.signTransaction(path, params.serializedTx);
1192
+ return {
1193
+ v: `0x${result.v.toString(16)}`,
1194
+ r: padHex64(result.r),
1195
+ s: padHex64(result.s)
1196
+ };
1197
+ } catch (err) {
1198
+ ctx.invalidateSession(sessionId);
1199
+ throw ctx.wrapError(err);
1200
+ }
1201
+ }
1202
+ async function evmSignMessage(ctx, sessionId, params) {
1203
+ const signer = await _getEthSigner(ctx, sessionId);
1204
+ const path = normalizePath(params.path);
1205
+ try {
1206
+ const result = await signer.signMessage(path, params.message);
1207
+ const rHex = stripHex(result.r).padStart(64, "0");
1208
+ const sHex = stripHex(result.s).padStart(64, "0");
1209
+ const vHex = result.v.toString(16).padStart(2, "0");
1210
+ return { signature: `0x${rHex}${sHex}${vHex}` };
1211
+ } catch (err) {
1212
+ ctx.invalidateSession(sessionId);
1213
+ throw ctx.wrapError(err);
1214
+ }
1215
+ }
1216
+ async function evmSignTypedData(ctx, sessionId, params) {
1217
+ if (params.mode === "hash") {
1218
+ throw Object.assign(
1219
+ new Error(
1220
+ 'Ledger does not support hash-only EIP-712 signing. Use mode "full" with the complete typed data structure.'
1221
+ ),
1222
+ { code: HardwareErrorCode3.MethodNotSupported }
1223
+ );
1224
+ }
1225
+ const signer = await _getEthSigner(ctx, sessionId);
1226
+ const path = normalizePath(params.path);
1227
+ try {
1228
+ const result = await signer.signTypedData(path, params.data);
1229
+ const rHex = stripHex(result.r).padStart(64, "0");
1230
+ const sHex = stripHex(result.s).padStart(64, "0");
1231
+ const vHex = result.v.toString(16).padStart(2, "0");
1232
+ return { signature: `0x${rHex}${sHex}${vHex}` };
1233
+ } catch (err) {
1234
+ ctx.invalidateSession(sessionId);
1235
+ throw ctx.wrapError(err);
1236
+ }
1237
+ }
1238
+ async function _getEthSigner(ctx, sessionId) {
1239
+ const signerManager = await ctx.getSignerManager();
1240
+ const signer = await signerManager.getOrCreate(sessionId);
1241
+ signer.onInteraction = (interaction) => {
1242
+ ctx.emit("ui-event", {
1243
+ type: interaction,
1244
+ payload: { sessionId }
1245
+ });
1246
+ };
1247
+ return signer;
1248
+ }
1249
+
1250
+ // src/connector/chains/btc.ts
1251
+ import {
1252
+ stripHex as stripHex2,
1253
+ hexToBytes as hexToBytes2,
1254
+ bytesToHex,
1255
+ HardwareErrorCode as HardwareErrorCode4
1256
+ } from "@onekeyfe/hwk-adapter-core";
1257
+
1258
+ // src/signer/SignerBtc.ts
1259
+ function hexToUtf8(hex) {
1260
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
1261
+ const bytes = new Uint8Array(h.length / 2);
1262
+ for (let i = 0; i < bytes.length; i++) {
1263
+ bytes[i] = parseInt(h.substring(i * 2, i * 2 + 2), 16);
1264
+ }
1265
+ return new TextDecoder().decode(bytes);
1266
+ }
1267
+ var INTERACTIVE_TIMEOUT_MS2 = 5 * 6e4;
1268
+ var SignerBtc = class {
1269
+ constructor(_sdk) {
1270
+ this._sdk = _sdk;
1271
+ }
1272
+ async getWalletAddress(wallet, addressIndex, options) {
1273
+ const action = this._sdk.getWalletAddress(wallet, addressIndex, {
1274
+ checkOnDevice: options?.checkOnDevice ?? false,
1275
+ change: options?.change ?? false
1276
+ });
1277
+ return deviceActionToPromise(action, this.onInteraction);
1278
+ }
1279
+ async getExtendedPublicKey(derivationPath, options) {
1280
+ console.log(
1281
+ "[SignerBtc] getExtendedPublicKey called, path:",
1282
+ derivationPath,
1283
+ "options:",
1284
+ JSON.stringify(options)
1285
+ );
1286
+ const action = this._sdk.getExtendedPublicKey(derivationPath, {
1287
+ checkOnDevice: options?.checkOnDevice ?? false
1288
+ });
1289
+ try {
1290
+ const result = await deviceActionToPromise(
1291
+ action,
1292
+ this.onInteraction
1293
+ );
1294
+ console.log(
1295
+ "[SignerBtc] getExtendedPublicKey result type:",
1296
+ typeof result,
1297
+ "value:",
1298
+ typeof result === "string" ? result.substring(0, 20) + "..." : JSON.stringify(result).substring(0, 50)
1299
+ );
1300
+ if (typeof result === "string") return result;
1301
+ return result.extendedPublicKey;
1302
+ } catch (err) {
1303
+ console.error("[SignerBtc] getExtendedPublicKey error:", err);
1304
+ throw err;
1305
+ }
1306
+ }
1307
+ async getMasterFingerprint(options) {
1308
+ const action = this._sdk.getMasterFingerprint(options);
1309
+ const result = await deviceActionToPromise(
1310
+ action,
1311
+ this.onInteraction
1312
+ );
1313
+ return result.masterFingerprint;
1314
+ }
1315
+ /**
1316
+ * Sign a PSBT and return the array of partial signatures.
1317
+ * The `wallet` param is a DefaultWallet or WalletPolicy instance.
1318
+ * The `psbt` param can be a hex string, base64 string, or Uint8Array.
1319
+ */
1320
+ async signPsbt(wallet, psbt, options) {
1321
+ const action = this._sdk.signPsbt(wallet, psbt, options);
1322
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
1323
+ }
1324
+ /**
1325
+ * Sign a PSBT and return the fully extracted raw transaction as a hex string.
1326
+ * Like signPsbt, but also finalises the PSBT and extracts the transaction.
1327
+ */
1328
+ async signTransaction(wallet, psbt, options) {
1329
+ const action = this._sdk.signTransaction(wallet, psbt, options);
1330
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
1331
+ }
1332
+ /**
1333
+ * Sign a message with the BTC app (BIP-137 / "Bitcoin Signed Message").
1334
+ * Returns `{ r, s, v }` signature object.
1335
+ */
1336
+ async signMessage(derivationPath, message, options) {
1337
+ const action = this._sdk.signMessage(derivationPath, hexToUtf8(message), options);
1338
+ return deviceActionToPromise(
1339
+ action,
1340
+ this.onInteraction,
1341
+ INTERACTIVE_TIMEOUT_MS2
1342
+ );
1343
+ }
1344
+ };
1345
+
1346
+ // src/connector/chains/btc.ts
1347
+ function readCompactSize(data, pos) {
1348
+ const first = data[pos];
1349
+ if (first < 253) return [first, 1];
1350
+ if (first === 253) return [data[pos + 1] | data[pos + 2] << 8, 3];
1351
+ return [0, 1];
1352
+ }
1353
+ function writeCompactSize(out, val) {
1354
+ if (val < 253) {
1355
+ out.push(val);
1356
+ } else {
1357
+ out.push(253, val & 255, val >>> 8 & 255);
1358
+ }
1359
+ }
1360
+ function readKeyValue(data, pos) {
1361
+ const start = pos;
1362
+ const [keyLen, keyLenSize] = readCompactSize(data, pos);
1363
+ pos += keyLenSize;
1364
+ const keyType = data[pos];
1365
+ pos += keyLen;
1366
+ const [valLen, valLenSize] = readCompactSize(data, pos);
1367
+ pos += valLenSize;
1368
+ const value = data.slice(pos, pos + valLen);
1369
+ pos += valLen;
1370
+ return { bytes: data.slice(start, pos), end: pos, keyType, value };
1371
+ }
1372
+ function writeKv(out, key, value) {
1373
+ writeCompactSize(out, key.length);
1374
+ key.forEach((b) => out.push(b));
1375
+ writeCompactSize(out, value.length);
1376
+ value.forEach((b) => out.push(b));
1377
+ }
1378
+ async function btcGetAddress(ctx, sessionId, params) {
1379
+ const btcSigner = await _createBtcSigner(ctx, sessionId);
1380
+ const path = normalizePath(params.path);
1381
+ try {
1382
+ const { DefaultWallet, DefaultDescriptorTemplate } = await ctx.importLedgerKit(
1383
+ "@ledgerhq/device-signer-kit-bitcoin"
1384
+ );
1385
+ const purpose = path.split("/")[0]?.replace("'", "");
1386
+ let template = DefaultDescriptorTemplate.NATIVE_SEGWIT;
1387
+ if (purpose === "44") template = DefaultDescriptorTemplate.LEGACY;
1388
+ else if (purpose === "49") template = DefaultDescriptorTemplate.NESTED_SEGWIT;
1389
+ else if (purpose === "86") template = DefaultDescriptorTemplate.TAPROOT;
1390
+ const wallet = new DefaultWallet(path, template);
1391
+ console.log("[LedgerConnector] btcGetAddress params:", {
1392
+ path,
1393
+ purpose,
1394
+ template,
1395
+ addressIndex: params.addressIndex,
1396
+ change: params.change,
1397
+ showOnDevice: params.showOnDevice,
1398
+ rawParams: JSON.stringify(params)
1399
+ });
1400
+ const result = await btcSigner.getWalletAddress(wallet, params.addressIndex ?? 0, {
1401
+ checkOnDevice: params.showOnDevice ?? false,
1402
+ change: params.change ?? false
1403
+ });
1404
+ return { address: result.address, path: params.path };
1405
+ } catch (err) {
1406
+ ctx.invalidateSession(sessionId);
1407
+ throw ctx.wrapError(err);
1408
+ }
1409
+ }
1410
+ async function btcGetPublicKey(ctx, sessionId, params) {
1411
+ const btcSigner = await _createBtcSigner(ctx, sessionId);
1412
+ const path = normalizePath(params.path);
1413
+ console.log("[LedgerConnector] btcGetPublicKey called, path:", path, "sessionId:", sessionId);
1414
+ try {
1415
+ const xpub = await btcSigner.getExtendedPublicKey(path, {
1416
+ checkOnDevice: params.showOnDevice ?? false
1417
+ });
1418
+ console.log("[LedgerConnector] btcGetPublicKey success, xpub:", xpub?.substring(0, 20) + "...");
1419
+ return { xpub, path: params.path };
1420
+ } catch (err) {
1421
+ console.error("[LedgerConnector] btcGetPublicKey error, path:", path, "err:", err);
1422
+ ctx.invalidateSession(sessionId);
1423
+ throw ctx.wrapError(err);
1424
+ }
1425
+ }
1426
+ async function btcSignTransaction(ctx, sessionId, params) {
1427
+ if (!params.psbt) {
1428
+ throw Object.assign(
1429
+ new Error("Ledger requires PSBT format for BTC transaction signing. Provide params.psbt."),
1430
+ { code: HardwareErrorCode4.InvalidParams }
1431
+ );
1432
+ }
1433
+ const btcSigner = await _createBtcSigner(ctx, sessionId);
1434
+ try {
1435
+ const { DefaultWallet, DefaultDescriptorTemplate } = await ctx.importLedgerKit(
1436
+ "@ledgerhq/device-signer-kit-bitcoin"
1437
+ );
1438
+ const path = normalizePath(params.path || "84'/0'/0'");
1439
+ const purpose = path.split("/")[0]?.replace("'", "");
1440
+ let template = DefaultDescriptorTemplate.NATIVE_SEGWIT;
1441
+ if (purpose === "44") template = DefaultDescriptorTemplate.LEGACY;
1442
+ else if (purpose === "49") template = DefaultDescriptorTemplate.NESTED_SEGWIT;
1443
+ else if (purpose === "86") template = DefaultDescriptorTemplate.TAPROOT;
1444
+ const wallet = new DefaultWallet(path, template);
1445
+ let psbtToSign = params.psbt;
1446
+ if (purpose === "86" && params.inputDerivations?.length) {
1447
+ psbtToSign = await _enrichTaprootPsbt(btcSigner, psbtToSign, params.inputDerivations);
1448
+ }
1449
+ const signedTxHex = await btcSigner.signTransaction(wallet, psbtToSign);
1450
+ return { signedPsbt: stripHex2(signedTxHex) };
1451
+ } catch (err) {
1452
+ ctx.invalidateSession(sessionId);
1453
+ throw ctx.wrapError(err);
1454
+ }
1455
+ }
1456
+ async function btcSignMessage(ctx, sessionId, params) {
1457
+ const btcSigner = await _createBtcSigner(ctx, sessionId);
1458
+ const path = normalizePath(params.path);
1459
+ try {
1460
+ const result = await btcSigner.signMessage(path, params.message);
1461
+ const vHex = result.v.toString(16).padStart(2, "0");
1462
+ const rHex = stripHex2(result.r).padStart(64, "0");
1463
+ const sHex = stripHex2(result.s).padStart(64, "0");
1464
+ return { signature: `${vHex}${rHex}${sHex}`, address: "" };
1465
+ } catch (err) {
1466
+ ctx.invalidateSession(sessionId);
1467
+ throw ctx.wrapError(err);
1468
+ }
1469
+ }
1470
+ async function btcGetMasterFingerprint(ctx, sessionId, params) {
1471
+ const btcSigner = await _createBtcSigner(ctx, sessionId);
1472
+ try {
1473
+ const fingerprint = await btcSigner.getMasterFingerprint({
1474
+ skipOpenApp: params?.skipOpenApp
1475
+ });
1476
+ const hex = Array.from(fingerprint).map((b) => b.toString(16).padStart(2, "0")).join("");
1477
+ return { masterFingerprint: hex };
1478
+ } catch (err) {
1479
+ ctx.invalidateSession(sessionId);
1480
+ throw ctx.wrapError(err);
1481
+ }
1482
+ }
1483
+ async function _createBtcSigner(ctx, sessionId) {
1484
+ const dmk = await ctx.getOrCreateDmk();
1485
+ const { SignerBtcBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-bitcoin");
1486
+ const sdkSigner = new SignerBtcBuilder({ dmk, sessionId }).build();
1487
+ const signer = new SignerBtc(sdkSigner);
1488
+ signer.onInteraction = (interaction) => {
1489
+ ctx.emit("ui-event", {
1490
+ type: interaction,
1491
+ payload: { sessionId }
1492
+ });
1493
+ };
1494
+ return signer;
1495
+ }
1496
+ async function _enrichTaprootPsbt(btcSigner, psbtHex, inputDerivations) {
1497
+ const masterFp = await btcSigner.getMasterFingerprint({ skipOpenApp: true });
1498
+ const fpBytes = masterFp.length === 4 ? masterFp : new Uint8Array([0, 0, 0, 0]);
1499
+ const raw = hexToBytes2(psbtHex);
1500
+ const result = [];
1501
+ let pos = 0;
1502
+ for (let i = 0; i < 5; i++) result.push(raw[pos++]);
1503
+ while (pos < raw.length && raw[pos] !== 0) {
1504
+ const { bytes: kv, end } = readKeyValue(raw, pos);
1505
+ kv.forEach((b) => result.push(b));
1506
+ pos = end;
1507
+ }
1508
+ result.push(raw[pos++]);
1509
+ for (let inputIdx = 0; pos < raw.length; inputIdx++) {
1510
+ const inputKvs = [];
1511
+ let witnessUtxoScript = null;
1512
+ while (pos < raw.length && raw[pos] !== 0) {
1513
+ const { bytes: kv, end, keyType, value } = readKeyValue(raw, pos);
1514
+ inputKvs.push(kv);
1515
+ if (keyType === 1 && value) {
1516
+ const scriptStart = 8;
1517
+ const scriptLen = value[scriptStart];
1518
+ witnessUtxoScript = value.slice(scriptStart + 1, scriptStart + 1 + scriptLen);
1519
+ }
1520
+ pos = end;
1521
+ }
1522
+ inputKvs.forEach((kv) => kv.forEach((b) => result.push(b)));
1523
+ if (witnessUtxoScript && witnessUtxoScript.length === 34 && witnessUtxoScript[0] === 81 && // OP_1
1524
+ witnessUtxoScript[1] === 32 && // PUSH 32
1525
+ inputDerivations[inputIdx]) {
1526
+ const xOnlyKey = witnessUtxoScript.slice(2, 34);
1527
+ const fullPath = inputDerivations[inputIdx].path;
1528
+ writeKv(result, new Uint8Array([23]), xOnlyKey);
1529
+ const pathComponents = fullPath.replace(/^m\//, "").split("/");
1530
+ const pathBuf = new Uint8Array(1 + 4 + pathComponents.length * 4);
1531
+ pathBuf[0] = 0;
1532
+ pathBuf.set(fpBytes, 1);
1533
+ for (let i = 0; i < pathComponents.length; i++) {
1534
+ const comp = pathComponents[i];
1535
+ const hardened = comp.endsWith("'");
1536
+ let val = parseInt(hardened ? comp.slice(0, -1) : comp, 10);
1537
+ if (hardened) val += 2147483648;
1538
+ const off = 5 + i * 4;
1539
+ pathBuf[off] = val & 255;
1540
+ pathBuf[off + 1] = val >>> 8 & 255;
1541
+ pathBuf[off + 2] = val >>> 16 & 255;
1542
+ pathBuf[off + 3] = val >>> 24 & 255;
1543
+ }
1544
+ const tapBipKey = new Uint8Array(1 + 32);
1545
+ tapBipKey[0] = 22;
1546
+ tapBipKey.set(xOnlyKey, 1);
1547
+ writeKv(result, tapBipKey, pathBuf);
1548
+ }
1549
+ result.push(raw[pos++]);
1550
+ }
1551
+ while (pos < raw.length) {
1552
+ result.push(raw[pos++]);
1553
+ }
1554
+ return bytesToHex(new Uint8Array(result));
1555
+ }
1556
+
1557
+ // src/connector/chains/sol.ts
1558
+ import { hexToBytes as hexToBytes3, bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
1559
+
1560
+ // src/signer/SignerSol.ts
1561
+ var SignerSol = class {
1562
+ constructor(_sdk) {
1563
+ this._sdk = _sdk;
1564
+ }
1565
+ /**
1566
+ * Get the Solana address (base58-encoded Ed25519 public key) at the given derivation path.
1567
+ */
1568
+ async getAddress(derivationPath, options) {
1569
+ const action = this._sdk.getAddress(derivationPath, {
1570
+ checkOnDevice: options?.checkOnDevice ?? false
1571
+ });
1572
+ return deviceActionToPromise(action, this.onInteraction);
1573
+ }
1574
+ /**
1575
+ * Sign a Solana transaction.
1576
+ */
1577
+ async signTransaction(derivationPath, transaction, options) {
1578
+ const action = this._sdk.signTransaction(derivationPath, transaction, options);
1579
+ return deviceActionToPromise(action, this.onInteraction);
1580
+ }
1581
+ /**
1582
+ * Sign a message with the Solana app.
1583
+ * DMK returns { signature: string } for signMessage (unlike signTransaction which returns Uint8Array).
1584
+ */
1585
+ async signMessage(derivationPath, message, options) {
1586
+ const action = this._sdk.signMessage(derivationPath, message, options);
1587
+ return deviceActionToPromise(action, this.onInteraction);
1588
+ }
1589
+ };
1590
+
1591
+ // src/connector/chains/sol.ts
1592
+ async function solGetAddress(ctx, sessionId, params) {
1593
+ const solSigner = await _createSolSigner(ctx, sessionId);
1594
+ const path = normalizePath(params.path);
1595
+ try {
1596
+ const publicKey = await solSigner.getAddress(path, {
1597
+ checkOnDevice: params.showOnDevice ?? false
1598
+ });
1599
+ return { address: publicKey, path: params.path };
1600
+ } catch (err) {
1601
+ ctx.invalidateSession(sessionId);
1602
+ throw ctx.wrapError(err);
1603
+ }
1604
+ }
1605
+ async function solSignTransaction(ctx, sessionId, params) {
1606
+ const solSigner = await _createSolSigner(ctx, sessionId);
1607
+ const path = normalizePath(params.path);
1608
+ const txBytes = hexToBytes3(params.serializedTx);
1609
+ try {
1610
+ const result = await solSigner.signTransaction(path, txBytes);
1611
+ return { signature: bytesToHex2(result) };
1612
+ } catch (err) {
1613
+ ctx.invalidateSession(sessionId);
1614
+ throw ctx.wrapError(err);
1615
+ }
1616
+ }
1617
+ async function solSignMessage(ctx, sessionId, params) {
1618
+ const solSigner = await _createSolSigner(ctx, sessionId);
1619
+ const path = normalizePath(params.path);
1620
+ const messageBytes = hexToBytes3(params.message);
1621
+ try {
1622
+ const result = await solSigner.signMessage(path, messageBytes);
1623
+ return { signature: result.signature };
1624
+ } catch (err) {
1625
+ ctx.invalidateSession(sessionId);
1626
+ throw ctx.wrapError(err);
1627
+ }
1628
+ }
1629
+ async function _createSolSigner(ctx, sessionId) {
1630
+ const dmk = await ctx.getOrCreateDmk();
1631
+ const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
1632
+ const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).build();
1633
+ const signer = new SignerSol(sdkSigner);
1634
+ signer.onInteraction = (interaction) => {
1635
+ ctx.emit("ui-event", {
1636
+ type: interaction,
1637
+ payload: { sessionId }
1638
+ });
1639
+ };
1640
+ return signer;
1641
+ }
1642
+
1643
+ // src/connector/chains/tron.ts
1644
+ import { EConnectorInteraction as EConnectorInteraction5, HardwareErrorCode as HardwareErrorCode5 } from "@onekeyfe/hwk-adapter-core";
1645
+ import Trx from "@ledgerhq/hw-app-trx";
1646
+
1647
+ // src/connector/chains/legacyAppRetry.ts
1648
+ import { EConnectorInteraction as EConnectorInteraction4 } from "@onekeyfe/hwk-adapter-core";
1649
+
1650
+ // src/app/AppManager.ts
1651
+ import {
1652
+ GetAppAndVersionCommand,
1653
+ OpenAppCommand,
1654
+ CloseAppCommand,
1655
+ isSuccessCommandResult
1656
+ } from "@ledgerhq/device-management-kit";
1657
+ var APP_NAME_MAP = {
1658
+ ETH: "Ethereum",
1659
+ BTC: "Bitcoin",
1660
+ SOL: "Solana",
1661
+ TRX: "Tron",
1662
+ XRP: "XRP",
1663
+ ADA: "Cardano",
1664
+ DOT: "Polkadot",
1665
+ ATOM: "Cosmos"
1666
+ };
1667
+ var DASHBOARD_APP_NAME = "BOLOS";
1668
+ var AppManager = class {
1669
+ constructor(dmk, options) {
1670
+ this._dmk = dmk;
1671
+ this._waitMs = options?.waitMs ?? 1e3;
1672
+ this._maxRetries = options?.maxRetries ?? 10;
1673
+ }
1674
+ /**
1675
+ * Return the Ledger app name for a given chain ticker,
1676
+ * or undefined if the chain is not supported.
1677
+ */
1678
+ static getAppName(chain) {
1679
+ return APP_NAME_MAP[chain];
1680
+ }
1681
+ /**
1682
+ * Ensure the target app is open on the device identified by `sessionId`.
1683
+ *
1684
+ * Flow:
1685
+ * 1. Check the currently running app.
1686
+ * 2. If it is already the target, return immediately.
1687
+ * 3. If a different app is running (not dashboard), close it first.
1688
+ * 4. Open the target app.
1689
+ * 5. Poll until the device confirms the target app is running.
1690
+ */
1691
+ /**
1692
+ * @param onConfirmOnDevice Called after OpenAppCommand succeeds —
1693
+ * the device is now showing a confirmation prompt to the user.
1694
+ * NOT called if the app is already open or if the app is not installed.
1695
+ */
1696
+ async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
1697
+ const currentApp = await this._getCurrentApp(sessionId);
1698
+ if (currentApp === targetAppName) {
1699
+ return;
1700
+ }
1701
+ if (!this._isDashboard(currentApp)) {
1702
+ await this._closeCurrentApp(sessionId);
1703
+ await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
1704
+ }
1705
+ await this._openApp(sessionId, targetAppName);
1706
+ onConfirmOnDevice?.();
1707
+ await this._waitForApp(sessionId, targetAppName);
1708
+ }
1709
+ // ---------------------------------------------------------------------------
1710
+ // Private helpers
1711
+ // ---------------------------------------------------------------------------
1712
+ async _getCurrentApp(sessionId) {
1713
+ const result = await this._dmk.sendCommand({
1714
+ sessionId,
1715
+ command: new GetAppAndVersionCommand()
1716
+ });
1717
+ if (isSuccessCommandResult(result)) {
1718
+ return result.data.name;
1719
+ }
1720
+ throw new Error("Failed to get current app from device");
1721
+ }
1722
+ async _openApp(sessionId, appName) {
1723
+ const result = await this._dmk.sendCommand({
1724
+ sessionId,
1725
+ command: new OpenAppCommand({ appName })
1726
+ });
1727
+ if (!isSuccessCommandResult(result)) {
1728
+ const statusCode = result.statusCode;
1729
+ throw Object.assign(
1730
+ new Error(`Failed to open "${appName}": app may not be installed`),
1731
+ { _tag: "OpenAppCommandError", errorCode: String(statusCode ?? ""), statusCode }
1732
+ );
1733
+ }
1734
+ }
1735
+ async _closeCurrentApp(sessionId) {
1736
+ await this._dmk.sendCommand({
1737
+ sessionId,
1738
+ command: new CloseAppCommand()
1739
+ });
1740
+ }
1741
+ /**
1742
+ * Poll the device until the expected app is reported as running,
1743
+ * or throw after `_maxRetries` attempts.
1744
+ */
1745
+ async _waitForApp(sessionId, expectedAppName) {
1746
+ for (let i = 0; i < this._maxRetries; i++) {
1747
+ await this._wait();
1748
+ const current = await this._getCurrentApp(sessionId);
1749
+ if (current === expectedAppName) {
1750
+ return;
1751
+ }
1752
+ }
1753
+ throw new Error(
1754
+ `Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries`
1755
+ );
1756
+ }
1757
+ _isDashboard(appName) {
1758
+ return appName === DASHBOARD_APP_NAME;
1759
+ }
1760
+ _wait() {
1761
+ return new Promise((resolve) => setTimeout(resolve, this._waitMs));
1762
+ }
1763
+ };
1764
+
1765
+ // src/connector/chains/legacyAppRetry.ts
1766
+ var KNOWN_APP_CODES = {
1767
+ Tron: /* @__PURE__ */ new Set([
1768
+ 27013,
1769
+ // user denied
1770
+ 21781,
1771
+ // device locked
1772
+ 27274,
1773
+ // invalid BIP32 path
1774
+ 27275,
1775
+ 27276,
1776
+ 27277,
1777
+ // app-specific data errors
1778
+ 27264,
1779
+ // invalid data
1780
+ 27392,
1781
+ // wrong parameter
1782
+ 26368
1783
+ // wrong length
1784
+ ])
1785
+ };
1786
+ function isLegacyWrongAppError(err, appName) {
1787
+ if (isWrongAppError(err)) return true;
1788
+ const msg = err instanceof Error ? err.message : "";
1789
+ const match = msg.match(/0x([0-9a-fA-F]{4})/);
1790
+ if (!match) return false;
1791
+ const sw = parseInt(match[1], 16);
1792
+ const knownCodes = KNOWN_APP_CODES[appName];
1793
+ if (knownCodes?.has(sw)) return false;
1794
+ return true;
1795
+ }
1796
+ async function withLegacyAppRetry(ctx, sessionId, appName, action) {
1797
+ try {
1798
+ return await action(sessionId);
1799
+ } catch (err) {
1800
+ ctx.emit("ui-event", {
1801
+ type: EConnectorInteraction4.InteractionComplete,
1802
+ payload: { sessionId }
1803
+ });
1804
+ if (isLegacyWrongAppError(err, appName)) {
1805
+ const dmk = await ctx.getOrCreateDmk();
1806
+ const appManager = new AppManager(dmk);
1807
+ try {
1808
+ await appManager.ensureAppOpen(sessionId, appName, () => {
1809
+ ctx.emit("ui-event", {
1810
+ type: EConnectorInteraction4.ConfirmOpenApp,
1811
+ payload: { sessionId }
1812
+ });
1813
+ });
1814
+ } catch (switchErr) {
1815
+ ctx.emit("ui-event", {
1816
+ type: EConnectorInteraction4.InteractionComplete,
1817
+ payload: { sessionId }
1818
+ });
1819
+ throw ctx.wrapError(switchErr);
1820
+ }
1821
+ ctx.clearAllSigners();
1822
+ ctx.emit("ui-event", {
1823
+ type: EConnectorInteraction4.InteractionComplete,
1824
+ payload: { sessionId }
1825
+ });
1826
+ return await action(sessionId);
1827
+ }
1828
+ ctx.invalidateSession(sessionId);
1829
+ throw ctx.wrapError(err);
1830
+ }
1831
+ }
1832
+
1833
+ // src/transport/DmkTransport.ts
1834
+ import Transport from "@ledgerhq/hw-transport";
1835
+ var DmkTransport = class extends Transport {
1836
+ constructor(dmk, sessionId) {
1837
+ super();
1838
+ this._dmk = dmk;
1839
+ this._sessionId = sessionId;
1840
+ }
1841
+ async exchange(apdu) {
1842
+ const response = await this._dmk.sendApdu({
1843
+ sessionId: this._sessionId,
1844
+ apdu: new Uint8Array(apdu)
1845
+ });
1846
+ const result = Buffer.alloc(response.data.length + 2);
1847
+ if (response.data.length > 0) {
1848
+ result.set(response.data, 0);
1849
+ }
1850
+ result.set(response.statusCode, response.data.length);
1851
+ return result;
1852
+ }
1853
+ async close() {
1854
+ }
1855
+ };
1856
+
1857
+ // src/connector/chains/tron.ts
1858
+ async function tronGetAddress(ctx, sessionId, params) {
1859
+ const path = normalizePath(params.path);
1860
+ return withLegacyAppRetry(ctx, sessionId, "Tron", async (sid) => {
1861
+ const trx = await _createTrx(ctx, sid);
1862
+ if (params.showOnDevice) {
1863
+ ctx.emit("ui-event", {
1864
+ type: EConnectorInteraction5.ConfirmOnDevice,
1865
+ payload: { sessionId: sid }
1866
+ });
1867
+ }
1868
+ const result = await trx.getAddress(path, params.showOnDevice ?? false);
1869
+ ctx.emit("ui-event", {
1870
+ type: EConnectorInteraction5.InteractionComplete,
1871
+ payload: { sessionId: sid }
1872
+ });
1873
+ return { address: result.address, publicKey: result.publicKey, path: params.path };
1874
+ });
1875
+ }
1876
+ async function tronSignTransaction(ctx, sessionId, params) {
1877
+ if (!params.rawTxHex) {
1878
+ throw Object.assign(
1879
+ new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
1880
+ { code: HardwareErrorCode5.InvalidParams }
1881
+ );
1882
+ }
1883
+ const path = normalizePath(params.path);
1884
+ return withLegacyAppRetry(ctx, sessionId, "Tron", async (sid) => {
1885
+ const trx = await _createTrx(ctx, sid);
1886
+ ctx.emit("ui-event", {
1887
+ type: EConnectorInteraction5.ConfirmOnDevice,
1888
+ payload: { sessionId: sid }
1889
+ });
1890
+ const signature = await trx.signTransaction(path, params.rawTxHex, []);
1891
+ ctx.emit("ui-event", {
1892
+ type: EConnectorInteraction5.InteractionComplete,
1893
+ payload: { sessionId: sid }
1894
+ });
1895
+ return { signature };
1896
+ });
1897
+ }
1898
+ async function tronSignMessage(ctx, sessionId, params) {
1899
+ const path = normalizePath(params.path);
1900
+ return withLegacyAppRetry(ctx, sessionId, "Tron", async (sid) => {
1901
+ const trx = await _createTrx(ctx, sid);
1902
+ ctx.emit("ui-event", {
1903
+ type: EConnectorInteraction5.ConfirmOnDevice,
1904
+ payload: { sessionId: sid }
1905
+ });
1906
+ const signature = await trx.signPersonalMessage(path, params.messageHex);
1907
+ ctx.emit("ui-event", {
1908
+ type: EConnectorInteraction5.InteractionComplete,
1909
+ payload: { sessionId: sid }
1910
+ });
1911
+ return { signature };
1912
+ });
1913
+ }
1914
+ async function _createTrx(ctx, sessionId) {
1915
+ const dmk = await ctx.getOrCreateDmk();
1916
+ return new Trx(new DmkTransport(dmk, sessionId));
1917
+ }
1918
+
1919
+ // src/connector/LedgerConnectorBase.ts
1920
+ async function defaultLedgerKitImporter(pkg) {
1921
+ switch (pkg) {
1922
+ case "@ledgerhq/device-management-kit":
1923
+ return import("@ledgerhq/device-management-kit");
1924
+ case "@ledgerhq/device-signer-kit-ethereum":
1925
+ return import("@ledgerhq/device-signer-kit-ethereum");
1926
+ case "@ledgerhq/device-signer-kit-bitcoin":
1927
+ return import("@ledgerhq/device-signer-kit-bitcoin");
1928
+ case "@ledgerhq/device-signer-kit-solana":
1929
+ return import("@ledgerhq/device-signer-kit-solana");
1930
+ default:
1931
+ throw new Error(`Unknown Ledger kit package: ${pkg}`);
1932
+ }
1933
+ }
1934
+ var LedgerConnectorBase = class {
1935
+ constructor(createTransport, options) {
1936
+ this._deviceManager = null;
1937
+ this._signerManager = null;
1938
+ this._dmk = null;
1939
+ this._eventHandlers = /* @__PURE__ */ new Map();
1940
+ // ---------------------------------------------------------------------------
1941
+ // ConnectId <-> DMK path mapping
1942
+ //
1943
+ // DMK uses internal paths (BLE MAC, USB UUID) that may change across sessions.
1944
+ // _resolveConnectId() maps these to stable external IDs (BLE: "A58F", USB: same).
1945
+ // This bidirectional map is the SINGLE SOURCE OF TRUTH for all connectId usage.
1946
+ // ---------------------------------------------------------------------------
1947
+ this._connectIdToPath = /* @__PURE__ */ new Map();
1948
+ // "A58F" -> "D5:75:7D:4B:51:E8"
1949
+ this._pathToConnectId = /* @__PURE__ */ new Map();
1950
+ this._createTransport = createTransport;
1951
+ this._connectionType = options?.connectionType ?? "usb";
1952
+ this._providedDmk = options?.dmk;
1953
+ this._importLedgerKit = options?.importLedgerKit ?? defaultLedgerKitImporter;
1954
+ if (this._providedDmk) {
1955
+ this._initManagers(this._providedDmk);
1956
+ }
1957
+ this._ctx = {
1958
+ emit: (event, data) => this._emit(event, data),
1959
+ invalidateSession: (sid) => this._invalidateSession(sid),
1960
+ wrapError: (err) => this._wrapError(err),
1961
+ getOrCreateDmk: () => this._getOrCreateDmk(),
1962
+ getDeviceManager: () => this._getDeviceManager(),
1963
+ getSignerManager: () => this._getSignerManager(),
1964
+ clearAllSigners: () => this._signerManager?.clearAll(),
1965
+ replaceSession: (oldSid, newSid) => this._replaceSession(oldSid, newSid),
1966
+ importLedgerKit: this._importLedgerKit
1967
+ };
1968
+ }
1969
+ // "D5:75:7D:4B:51:E8" -> "A58F"
1970
+ /** Register a connectId <-> path mapping from a device descriptor. */
1971
+ _registerDeviceId(descriptor) {
1972
+ const connectId = this._resolveConnectId(descriptor);
1973
+ this._connectIdToPath.set(connectId, descriptor.path);
1974
+ this._pathToConnectId.set(descriptor.path, connectId);
1975
+ return connectId;
1976
+ }
1977
+ /** Get DMK path from external connectId. Falls back to connectId itself. */
1978
+ _getPathForConnectId(connectId) {
1979
+ return this._connectIdToPath.get(connectId) ?? connectId;
1980
+ }
1981
+ /** Get external connectId from DMK path. Falls back to path itself. */
1982
+ _getConnectIdForPath(path) {
1983
+ return this._pathToConnectId.get(path) ?? path;
1984
+ }
1985
+ // ---------------------------------------------------------------------------
1986
+ // Protected — hooks for subclasses
1987
+ // ---------------------------------------------------------------------------
1988
+ /**
1989
+ * Resolve the connectId for a discovered device descriptor.
1990
+ * Default: use the DMK path (ephemeral UUID).
1991
+ * Override in subclasses to extract stable identifiers (e.g. BLE hex ID).
1992
+ */
1993
+ _resolveConnectId(descriptor) {
1994
+ return descriptor.path;
1995
+ }
1996
+ // ---------------------------------------------------------------------------
1997
+ // IConnector -- Device discovery
1998
+ // ---------------------------------------------------------------------------
1999
+ async searchDevices() {
2000
+ const dm = await this._getDeviceManager();
2001
+ let descriptors = await dm.enumerate();
2002
+ if (descriptors.length === 0) {
2003
+ try {
2004
+ await dm.requestDevice();
2005
+ } catch {
2006
+ }
2007
+ descriptors = await dm.enumerate();
2008
+ }
2009
+ const result = descriptors.map((d) => {
2010
+ const connectId = this._registerDeviceId(d);
2011
+ return {
2012
+ connectId,
2013
+ deviceId: d.path,
2014
+ name: d.name || d.type || "Ledger",
2015
+ model: d.type
2016
+ };
2017
+ });
2018
+ return result;
2019
+ }
2020
+ // ---------------------------------------------------------------------------
2021
+ // IConnector -- Connection
2022
+ // ---------------------------------------------------------------------------
2023
+ async connect(deviceId) {
2024
+ const dm = await this._getDeviceManager();
2025
+ await this.searchDevices();
2026
+ const dmkPath = deviceId ? this._getPathForConnectId(deviceId) : void 0;
2027
+ let targetPath = dmkPath;
2028
+ if (!targetPath) {
2029
+ const descriptors = await dm.enumerate();
2030
+ if (descriptors.length === 0) {
2031
+ throw new Error(
2032
+ `No Ledger device found. Make sure the device is connected${this._connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
2033
+ );
2034
+ }
2035
+ targetPath = descriptors[0].path;
2036
+ }
2037
+ const externalConnectId = this._getConnectIdForPath(targetPath);
2038
+ const doConnect = async (path) => {
2039
+ const sessionId = await dm.connect(path);
2040
+ const session = {
2041
+ sessionId,
2042
+ deviceInfo: {
2043
+ vendor: "ledger",
2044
+ model: "unknown",
2045
+ firmwareVersion: "unknown",
2046
+ deviceId: path,
2047
+ connectId: externalConnectId,
2048
+ connectionType: this._connectionType,
2049
+ capabilities: { persistentDeviceIdentity: false }
2050
+ }
2051
+ };
2052
+ this._emit("device-connect", {
2053
+ device: {
2054
+ connectId: externalConnectId,
2055
+ deviceId: path,
2056
+ name: "Ledger"
2057
+ }
2058
+ });
2059
+ return session;
2060
+ };
2061
+ try {
2062
+ return await doConnect(targetPath);
2063
+ } catch {
2064
+ this._resetSignersAndSessions();
2065
+ const dm2 = await this._getDeviceManager();
2066
+ await this.searchDevices();
2067
+ const retryPath = this._getPathForConnectId(externalConnectId);
2068
+ if (!retryPath || retryPath === externalConnectId) {
2069
+ const descriptors = await dm2.enumerate();
2070
+ if (descriptors.length === 0) {
2071
+ throw new Error(
2072
+ `No Ledger device found after retry. Make sure the device is connected${this._connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
2073
+ );
2074
+ }
2075
+ return doConnect(descriptors[0].path);
2076
+ }
2077
+ return doConnect(retryPath);
2078
+ }
2079
+ }
2080
+ async disconnect(sessionId) {
2081
+ if (!this._deviceManager) return;
2082
+ const deviceId = this._deviceManager.getDeviceId(sessionId);
2083
+ this._signerManager?.invalidate(sessionId);
2084
+ await this._deviceManager.disconnect(sessionId);
2085
+ if (deviceId) {
2086
+ this._emit("device-disconnect", { connectId: deviceId });
2087
+ }
2088
+ }
2089
+ // ---------------------------------------------------------------------------
2090
+ // IConnector -- Method dispatch
2091
+ // ---------------------------------------------------------------------------
2092
+ async call(sessionId, method, params) {
2093
+ console.log("[DMK] call:", method, JSON.stringify(params));
2094
+ switch (method) {
2095
+ // EVM
2096
+ case "evmGetAddress":
2097
+ return evmGetAddress(this._ctx, sessionId, params);
2098
+ case "evmSignTransaction":
2099
+ return evmSignTransaction(this._ctx, sessionId, params);
2100
+ case "evmSignMessage":
2101
+ return evmSignMessage(this._ctx, sessionId, params);
2102
+ case "evmSignTypedData":
2103
+ return evmSignTypedData(this._ctx, sessionId, params);
2104
+ // BTC
2105
+ case "btcGetAddress":
2106
+ return btcGetAddress(this._ctx, sessionId, params);
2107
+ case "btcGetPublicKey":
2108
+ return btcGetPublicKey(this._ctx, sessionId, params);
2109
+ case "btcSignTransaction":
2110
+ return btcSignTransaction(this._ctx, sessionId, params);
2111
+ case "btcSignMessage":
2112
+ return btcSignMessage(this._ctx, sessionId, params);
2113
+ case "btcGetMasterFingerprint":
2114
+ return btcGetMasterFingerprint(
2115
+ this._ctx,
2116
+ sessionId,
2117
+ params
2118
+ );
2119
+ // SOL
2120
+ case "solGetAddress":
2121
+ return solGetAddress(this._ctx, sessionId, params);
2122
+ case "solSignTransaction":
2123
+ return solSignTransaction(this._ctx, sessionId, params);
2124
+ case "solSignMessage":
2125
+ return solSignMessage(this._ctx, sessionId, params);
2126
+ // TRON
2127
+ case "tronGetAddress":
2128
+ return tronGetAddress(this._ctx, sessionId, params);
2129
+ case "tronSignTransaction":
2130
+ return tronSignTransaction(this._ctx, sessionId, params);
2131
+ case "tronSignMessage":
2132
+ return tronSignMessage(this._ctx, sessionId, params);
2133
+ default:
2134
+ throw new Error(`LedgerConnector: unknown method "${method}"`);
2135
+ }
2136
+ }
2137
+ async cancel(_sessionId) {
2138
+ }
2139
+ uiResponse(_response) {
2140
+ }
2141
+ // ---------------------------------------------------------------------------
2142
+ // IConnector -- Events
2143
+ // ---------------------------------------------------------------------------
2144
+ on(event, handler) {
2145
+ if (!this._eventHandlers.has(event)) {
2146
+ this._eventHandlers.set(event, /* @__PURE__ */ new Set());
2147
+ }
2148
+ this._eventHandlers.get(event).add(handler);
2149
+ }
2150
+ off(event, handler) {
2151
+ this._eventHandlers.get(event)?.delete(handler);
2152
+ }
2153
+ // ---------------------------------------------------------------------------
2154
+ // IConnector -- Reset
2155
+ // ---------------------------------------------------------------------------
2156
+ reset() {
2157
+ this._resetAll();
2158
+ }
2159
+ // ---------------------------------------------------------------------------
2160
+ // Private -- DMK / Manager lifecycle
2161
+ // ---------------------------------------------------------------------------
2162
+ /**
2163
+ * Lazily create or return the DMK instance.
2164
+ * If a DMK was provided via constructor, it is used directly.
2165
+ * Otherwise, one is created via the transport factory.
2166
+ */
2167
+ async _getOrCreateDmk() {
2168
+ console.log(
2169
+ "[DMK] _getOrCreateDmk called, _dmk exists:",
2170
+ !!this._dmk,
2171
+ "_providedDmk exists:",
2172
+ !!this._providedDmk,
2173
+ "stack:",
2174
+ new Error().stack?.split("\n").slice(1, 4).join(" | ")
2175
+ );
2176
+ if (this._dmk) return this._dmk;
2177
+ if (this._providedDmk) {
2178
+ this._dmk = this._providedDmk;
2179
+ return this._dmk;
2180
+ }
2181
+ const { DeviceManagementKitBuilder } = await this._importLedgerKit(
2182
+ "@ledgerhq/device-management-kit"
2183
+ );
2184
+ const transportFactory = await this._createTransport();
2185
+ console.log(
2186
+ "[DMK] _getOrCreateDmk: transportFactory type:",
2187
+ typeof transportFactory,
2188
+ "value:",
2189
+ String(transportFactory).substring(0, 80)
2190
+ );
2191
+ const dmk = new DeviceManagementKitBuilder().addTransport(transportFactory).build();
2192
+ this._dmk = dmk;
2193
+ console.log(
2194
+ "[DMK] _getOrCreateDmk: DMK created, methods:",
2195
+ Object.getOwnPropertyNames(Object.getPrototypeOf(dmk)).join(", ")
2196
+ );
2197
+ return dmk;
2198
+ }
2199
+ _initManagers(dmk) {
2200
+ this._dmk = dmk;
2201
+ this._deviceManager = new LedgerDeviceManager(dmk);
2202
+ const importKit = this._importLedgerKit;
2203
+ this._signerManager = new SignerManager(dmk, async (args) => {
2204
+ const mod = await importKit("@ledgerhq/device-signer-kit-ethereum");
2205
+ return new mod.SignerEthBuilder(args);
2206
+ });
2207
+ }
2208
+ async _getDeviceManager() {
2209
+ if (this._deviceManager) return this._deviceManager;
2210
+ const dmk = await this._getOrCreateDmk();
2211
+ this._initManagers(dmk);
2212
+ return this._deviceManager;
2213
+ }
2214
+ async _getSignerManager() {
2215
+ if (!this._signerManager) {
2216
+ const dmk = await this._getOrCreateDmk();
2217
+ this._initManagers(dmk);
2218
+ }
2219
+ return this._signerManager;
2220
+ }
2221
+ _invalidateSession(sessionId) {
2222
+ this._signerManager?.invalidate(sessionId);
2223
+ }
2224
+ /**
2225
+ * Replace an old session with a new one after app switch.
2226
+ * Updates the connectId→path mapping so subsequent calls() use the new session,
2227
+ * and emits device-connect so the adapter updates its _sessions Map.
2228
+ */
2229
+ _replaceSession(oldSessionId, _newSessionId) {
2230
+ const dm = this._deviceManager;
2231
+ if (!dm) return;
2232
+ const oldDeviceId = dm.getDeviceId(oldSessionId);
2233
+ const connectId = oldDeviceId ? this._pathToConnectId.get(oldDeviceId) : void 0;
2234
+ this._signerManager?.invalidate(oldSessionId);
2235
+ if (connectId) {
2236
+ this._emit("device-connect", {
2237
+ device: {
2238
+ connectId,
2239
+ deviceId: connectId,
2240
+ name: "Ledger"
2241
+ }
2242
+ });
2243
+ }
2244
+ }
2245
+ /**
2246
+ * Light reset: clear signer/session state but keep DMK, BLE scan, and ID mapping alive.
2247
+ * Used by connect() retry — we want to re-discover with the same transport.
2248
+ */
2249
+ _resetSignersAndSessions() {
2250
+ console.log(
2251
+ "[DMK] _resetSignersAndSessions called, stack:",
2252
+ new Error().stack?.split("\n").slice(1, 3).join(" | ")
2253
+ );
2254
+ this._signerManager?.clearAll();
2255
+ this._signerManager = null;
2256
+ this._deviceManager = null;
2257
+ }
2258
+ _resetAll() {
2259
+ console.log(
2260
+ "[DMK] _resetAll called, stack:",
2261
+ new Error().stack?.split("\n").slice(1, 3).join(" | ")
2262
+ );
2263
+ this._signerManager?.clearAll();
2264
+ this._deviceManager?.dispose();
2265
+ this._deviceManager = null;
2266
+ this._signerManager = null;
2267
+ this._dmk = null;
2268
+ this._connectIdToPath.clear();
2269
+ this._pathToConnectId.clear();
2270
+ }
2271
+ // ---------------------------------------------------------------------------
2272
+ // Private -- Events
2273
+ // ---------------------------------------------------------------------------
2274
+ _emit(event, data) {
2275
+ const handlers = this._eventHandlers.get(event);
2276
+ if (handlers) {
2277
+ for (const handler of handlers) {
2278
+ try {
2279
+ handler(data);
2280
+ } catch {
2281
+ }
2282
+ }
2283
+ }
2284
+ }
2285
+ // ---------------------------------------------------------------------------
2286
+ // Private -- Error handling
2287
+ // ---------------------------------------------------------------------------
2288
+ _wrapError(err) {
2289
+ const mapped = mapLedgerError(err);
2290
+ const error = new Error(mapped.message);
2291
+ Object.assign(error, { code: mapped.code });
2292
+ return error;
2293
+ }
2294
+ };
2295
+ export {
2296
+ AppManager,
2297
+ DmkTransport,
2298
+ LedgerAdapter,
2299
+ LedgerConnectorBase,
2300
+ LedgerDeviceManager,
2301
+ SignerBtc,
2302
+ SignerEth,
2303
+ SignerManager,
2304
+ SignerSol,
2305
+ deviceActionToPromise,
2306
+ isDeviceLockedError,
2307
+ mapLedgerError
2308
+ };
2309
+ //# sourceMappingURL=index.mjs.map