@onekeyfe/hwk-ledger-adapter 1.2.5-alpha.3 → 1.2.5-alpha.4
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.d.mts +81 -39
- package/dist/index.d.ts +81 -39
- package/dist/index.js +1614 -831
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1634 -831
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -11
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
SignerEth: () => SignerEth,
|
|
40
40
|
SignerManager: () => SignerManager,
|
|
41
41
|
SignerSol: () => SignerSol,
|
|
42
|
+
SignerZcash: () => SignerZcash,
|
|
42
43
|
debugLog: () => debugLog,
|
|
43
44
|
deviceActionToPromise: () => deviceActionToPromise,
|
|
44
45
|
isDeviceLockedError: () => isDeviceLockedError,
|
|
@@ -57,17 +58,14 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
|
|
|
57
58
|
|
|
58
59
|
// src/errors.ts
|
|
59
60
|
var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
|
|
60
|
-
|
|
61
|
-
function createMultipleUsbLedgerDevicesError() {
|
|
62
|
-
return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
|
|
63
|
-
code: import_hwk_adapter_core.HardwareErrorCode.DeviceOneDeviceOnly
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
function ledgerFailure(code, error, appName, tag, params) {
|
|
61
|
+
function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
|
|
67
62
|
const payload = { error, code };
|
|
68
63
|
if (appName !== void 0) payload.appName = appName;
|
|
69
64
|
if (tag !== void 0) payload._tag = tag;
|
|
70
65
|
if (params !== void 0) payload.params = params;
|
|
66
|
+
const resolvedOrigin = origin ?? (0, import_hwk_adapter_core.defaultOriginForCode)(code);
|
|
67
|
+
if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
|
|
68
|
+
payload.recovery = recovery ?? (0, import_hwk_adapter_core.defaultRecoveryForCode)(code);
|
|
71
69
|
return { success: false, payload };
|
|
72
70
|
}
|
|
73
71
|
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
@@ -208,7 +206,10 @@ var ERROR_TAG = {
|
|
|
208
206
|
UnknownDevice: "UnknownDeviceError",
|
|
209
207
|
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
210
208
|
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
211
|
-
|
|
209
|
+
// DMK class OpeningConnectionError carries `_tag` "ConnectionOpeningError"; both
|
|
210
|
+
// spellings are kept so a DMK release that aligns them still classifies.
|
|
211
|
+
OpeningConnection: "ConnectionOpeningError",
|
|
212
|
+
OpeningConnectionLegacy: "OpeningConnectionError",
|
|
212
213
|
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
213
214
|
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
214
215
|
Disconnect: "DisconnectError",
|
|
@@ -221,7 +222,16 @@ var ERROR_TAG = {
|
|
|
221
222
|
// DMK remote-network failures (manager-api HTTP / secure-channel WS).
|
|
222
223
|
WebSocketConnection: "WebSocketConnectionError",
|
|
223
224
|
HttpFetch: "FetchError",
|
|
224
|
-
|
|
225
|
+
NetworkDA: "NetworkDAError",
|
|
226
|
+
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
|
|
227
|
+
ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
|
|
228
|
+
// DMK OS device actions. SecureChannelError is what mapInstallDAErrors() leaves
|
|
229
|
+
// after splitting out device answers, i.e. the relay itself broke.
|
|
230
|
+
SecureChannel: "SecureChannelError",
|
|
231
|
+
RefusedByUserDA: "RefusedByUserDAError",
|
|
232
|
+
AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
|
|
233
|
+
OutOfMemoryDA: "OutOfMemoryDAError",
|
|
234
|
+
DeviceNotOnboarded: "DeviceNotOnboardedError"
|
|
225
235
|
};
|
|
226
236
|
function isDeviceLockedError(err) {
|
|
227
237
|
if (!err || typeof err !== "object") return false;
|
|
@@ -251,7 +261,6 @@ function isBlePairingFailureError(err) {
|
|
|
251
261
|
return false;
|
|
252
262
|
}
|
|
253
263
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
254
|
-
ERROR_TAG.DeviceLocked,
|
|
255
264
|
ERROR_TAG.DeviceNotAdvertising,
|
|
256
265
|
ERROR_TAG.BlePairingTimeout,
|
|
257
266
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -263,6 +272,7 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
|
263
272
|
ERROR_TAG.DeviceSessionRefresher,
|
|
264
273
|
ERROR_TAG.DeviceNotInitialized,
|
|
265
274
|
ERROR_TAG.OpeningConnection,
|
|
275
|
+
ERROR_TAG.OpeningConnectionLegacy,
|
|
266
276
|
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
267
277
|
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
268
278
|
ERROR_TAG.Disconnect,
|
|
@@ -277,7 +287,10 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
|
277
287
|
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
278
288
|
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
279
289
|
]);
|
|
280
|
-
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
290
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
291
|
+
ERROR_TAG.OpeningConnection,
|
|
292
|
+
ERROR_TAG.OpeningConnectionLegacy
|
|
293
|
+
]);
|
|
281
294
|
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
282
295
|
ERROR_TAG.DeviceNotRecognized,
|
|
283
296
|
ERROR_TAG.DeviceSessionNotFound,
|
|
@@ -289,17 +302,18 @@ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
|
289
302
|
ERROR_TAG.WebHIDDisconnect
|
|
290
303
|
]);
|
|
291
304
|
function isConnectionLevelError(err) {
|
|
292
|
-
|
|
293
|
-
const tag = err._tag;
|
|
294
|
-
if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
|
|
295
|
-
const e = err;
|
|
296
|
-
if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
|
|
297
|
-
if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
|
|
298
|
-
return false;
|
|
305
|
+
return hasErrorTag(err, CONNECTION_LEVEL_TAGS);
|
|
299
306
|
}
|
|
300
307
|
function isKnownConnectionTag(tag) {
|
|
301
308
|
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
302
309
|
}
|
|
310
|
+
var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
|
|
311
|
+
ERROR_TAG.OpeningConnection,
|
|
312
|
+
ERROR_TAG.OpeningConnectionLegacy
|
|
313
|
+
]);
|
|
314
|
+
function isConnectionOpeningTag(tag) {
|
|
315
|
+
return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
|
|
316
|
+
}
|
|
303
317
|
function hasStatusCode(err, codeSet) {
|
|
304
318
|
if (!err || typeof err !== "object") return false;
|
|
305
319
|
const e = err;
|
|
@@ -335,28 +349,28 @@ function hasInvalidArgumentCode(err) {
|
|
|
335
349
|
if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
|
|
336
350
|
return false;
|
|
337
351
|
}
|
|
338
|
-
function
|
|
352
|
+
function hasErrorTag(err, tags) {
|
|
339
353
|
if (!err || typeof err !== "object") return false;
|
|
340
|
-
const tag = err._tag;
|
|
341
|
-
if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
|
|
342
354
|
const e = err;
|
|
343
|
-
if (e.
|
|
344
|
-
if (e.
|
|
355
|
+
if (typeof e._tag === "string" && tags.has(e._tag)) return true;
|
|
356
|
+
if (e.originalError != null && hasErrorTag(e.originalError, tags)) return true;
|
|
357
|
+
if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
|
|
345
358
|
return false;
|
|
346
359
|
}
|
|
360
|
+
function isDeviceNotFoundError(err) {
|
|
361
|
+
return hasErrorTag(err, DEVICE_NOT_FOUND_TAGS);
|
|
362
|
+
}
|
|
347
363
|
function isDeviceBusyError(err) {
|
|
348
|
-
|
|
349
|
-
const tag = err._tag;
|
|
350
|
-
if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
|
|
351
|
-
const e = err;
|
|
352
|
-
if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
|
|
353
|
-
if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
|
|
354
|
-
return false;
|
|
364
|
+
return hasErrorTag(err, DEVICE_BUSY_TAGS);
|
|
355
365
|
}
|
|
366
|
+
var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
|
|
367
|
+
ERROR_TAG.UserRefusedOnDevice,
|
|
368
|
+
ERROR_TAG.RefusedByUserDA
|
|
369
|
+
]);
|
|
356
370
|
function isUserRejectedError(err) {
|
|
357
371
|
if (!err || typeof err !== "object") return false;
|
|
358
372
|
const e = err;
|
|
359
|
-
if (
|
|
373
|
+
if (hasErrorTag(err, USER_REJECTED_TAGS)) return true;
|
|
360
374
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
361
375
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
362
376
|
return false;
|
|
@@ -387,15 +401,33 @@ function isAppNotInstalledError(err) {
|
|
|
387
401
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
388
402
|
return false;
|
|
389
403
|
}
|
|
404
|
+
var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
|
|
390
405
|
function isOutOfMemoryError(err) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
406
|
+
return hasErrorTag(err, OUT_OF_MEMORY_TAGS);
|
|
407
|
+
}
|
|
408
|
+
var APP_ALREADY_INSTALLED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.AppAlreadyInstalledDA]);
|
|
409
|
+
function isAppAlreadyInstalledError(err) {
|
|
410
|
+
return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
|
|
411
|
+
}
|
|
412
|
+
var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.DeviceNotOnboarded]);
|
|
413
|
+
function isDeviceNotOnboardedError(err) {
|
|
414
|
+
return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
|
|
415
|
+
}
|
|
416
|
+
var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
|
|
417
|
+
ERROR_TAG.InvalidFirmwareMetadataResponse,
|
|
418
|
+
ERROR_TAG.ApplicationsMetadataTask
|
|
419
|
+
]);
|
|
420
|
+
function isFirmwareMetadataError(err) {
|
|
421
|
+
return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
|
|
422
|
+
}
|
|
423
|
+
var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
|
|
424
|
+
function isSecureChannelError(err) {
|
|
425
|
+
return hasErrorTag(err, SECURE_CHANNEL_TAGS);
|
|
394
426
|
}
|
|
395
427
|
var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
|
|
396
428
|
ERROR_TAG.WebSocketConnection,
|
|
397
429
|
ERROR_TAG.HttpFetch,
|
|
398
|
-
ERROR_TAG.
|
|
430
|
+
ERROR_TAG.NetworkDA
|
|
399
431
|
]);
|
|
400
432
|
function isNetworkError(err) {
|
|
401
433
|
if (!err || typeof err !== "object") return false;
|
|
@@ -445,23 +477,34 @@ function isTransportStuck(err) {
|
|
|
445
477
|
function isStuckAppStateError(err) {
|
|
446
478
|
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
447
479
|
}
|
|
480
|
+
var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
|
|
481
|
+
function nestedErrorMessage(nested) {
|
|
482
|
+
if (!nested || typeof nested !== "object") return void 0;
|
|
483
|
+
const { message } = nested;
|
|
484
|
+
if (typeof message !== "string") return void 0;
|
|
485
|
+
const trimmed = message.trim();
|
|
486
|
+
if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
|
|
487
|
+
return message;
|
|
488
|
+
}
|
|
448
489
|
function mapLedgerError(err, opts) {
|
|
449
490
|
let originalMessage = "Unknown Ledger error";
|
|
450
491
|
if (err instanceof Error) {
|
|
451
492
|
originalMessage = err.message;
|
|
452
493
|
} else if (err && typeof err === "object") {
|
|
453
494
|
const e = err;
|
|
454
|
-
originalMessage = String(
|
|
495
|
+
originalMessage = String(
|
|
496
|
+
e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
|
|
497
|
+
);
|
|
455
498
|
}
|
|
456
499
|
let code;
|
|
457
500
|
if (isDeviceLockedError(err)) {
|
|
458
501
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceLocked;
|
|
459
502
|
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
460
503
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound;
|
|
461
|
-
} else if (isDeviceBusyError(err)) {
|
|
462
|
-
code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
|
|
463
504
|
} else if (isBlePairingFailureError(err)) {
|
|
464
505
|
code = import_hwk_adapter_core.HardwareErrorCode.BlePairingTimeout;
|
|
506
|
+
} else if (isDeviceBusyError(err)) {
|
|
507
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
|
|
465
508
|
} else if (isUserAbortedError(err)) {
|
|
466
509
|
code = import_hwk_adapter_core.HardwareErrorCode.UserAborted;
|
|
467
510
|
} else if (isUserRejectedError(err)) {
|
|
@@ -470,10 +513,18 @@ function mapLedgerError(err, opts) {
|
|
|
470
513
|
code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
|
|
471
514
|
} else if (isAppNotInstalledError(err)) {
|
|
472
515
|
code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
|
|
516
|
+
} else if (isAppAlreadyInstalledError(err)) {
|
|
517
|
+
code = import_hwk_adapter_core.HardwareErrorCode.AppAlreadyInstalled;
|
|
473
518
|
} else if (isOutOfMemoryError(err)) {
|
|
474
519
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceOutOfMemory;
|
|
520
|
+
} else if (isDeviceNotOnboardedError(err)) {
|
|
521
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotInitialized;
|
|
522
|
+
} else if (isFirmwareMetadataError(err)) {
|
|
523
|
+
code = import_hwk_adapter_core.HardwareErrorCode.LedgerFirmwareMetadataError;
|
|
475
524
|
} else if (isNetworkError(err)) {
|
|
476
525
|
code = import_hwk_adapter_core.HardwareErrorCode.NetworkError;
|
|
526
|
+
} else if (isSecureChannelError(err)) {
|
|
527
|
+
code = import_hwk_adapter_core.HardwareErrorCode.LedgerSecureChannelError;
|
|
477
528
|
} else if (isDeviceDisconnectedError(err)) {
|
|
478
529
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
|
|
479
530
|
} else if (isTimeoutError(err)) {
|
|
@@ -486,7 +537,21 @@ function mapLedgerError(err, opts) {
|
|
|
486
537
|
}
|
|
487
538
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
488
539
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
489
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
code,
|
|
542
|
+
message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage),
|
|
543
|
+
origin: (0, import_hwk_adapter_core.defaultOriginForCode)(code),
|
|
544
|
+
appName
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/utils/queueKey.ts
|
|
549
|
+
var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
|
|
550
|
+
function ledgerQueueKey({
|
|
551
|
+
operationId,
|
|
552
|
+
connectId
|
|
553
|
+
}) {
|
|
554
|
+
return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
|
|
490
555
|
}
|
|
491
556
|
|
|
492
557
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
@@ -547,65 +612,103 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
547
612
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
548
613
|
function createAllNetworkGetAddress({
|
|
549
614
|
callChain,
|
|
550
|
-
getChainFingerprint
|
|
615
|
+
getChainFingerprint,
|
|
616
|
+
retainOperation,
|
|
617
|
+
errorToFailure,
|
|
618
|
+
createCancelScope
|
|
551
619
|
}) {
|
|
552
620
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
553
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
621
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
622
|
+
method: "allNetworkGetAddress",
|
|
623
|
+
connectId,
|
|
624
|
+
itemCount: params.bundle.length
|
|
625
|
+
});
|
|
626
|
+
const target = (0, import_hwk_adapter_core2.resolveHardwareOperationTarget)(connectId, params.operationId, "ledger");
|
|
627
|
+
if (!target.success) return target;
|
|
628
|
+
const effectiveTargetId = target.payload.targetId ?? "";
|
|
629
|
+
let releaseOperationRetention;
|
|
630
|
+
try {
|
|
631
|
+
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
632
|
+
} catch (error) {
|
|
633
|
+
return errorToFailure(error);
|
|
634
|
+
}
|
|
635
|
+
const cancelScope = createCancelScope(
|
|
636
|
+
ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
637
|
+
);
|
|
554
638
|
const installContext = {};
|
|
555
639
|
const commonParams = {
|
|
556
|
-
autoInstallApp: params.autoInstallApp
|
|
640
|
+
autoInstallApp: params.autoInstallApp,
|
|
641
|
+
operationId: target.payload.operationId,
|
|
642
|
+
knownConnections: params.knownConnections,
|
|
643
|
+
extra: params.extra,
|
|
644
|
+
allowDeviceSelection: params.allowDeviceSelection,
|
|
645
|
+
supportedTransports: params.supportedTransports
|
|
557
646
|
};
|
|
558
647
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
648
|
+
try {
|
|
649
|
+
const result = await (0, import_hwk_adapter_core2.runAllNetworkGetAddress)({
|
|
650
|
+
connectId: effectiveTargetId,
|
|
651
|
+
deviceId: _deviceId,
|
|
652
|
+
params,
|
|
653
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
654
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
655
|
+
callItem: async ({ method, chain, item }) => {
|
|
656
|
+
if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
|
|
657
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
658
|
+
return callAllNetworkMethod(
|
|
659
|
+
callChain,
|
|
660
|
+
effectiveTargetId,
|
|
661
|
+
itemDeviceId,
|
|
662
|
+
method,
|
|
663
|
+
item,
|
|
664
|
+
commonParams,
|
|
665
|
+
installContext
|
|
666
|
+
);
|
|
667
|
+
},
|
|
668
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
669
|
+
getChainFingerprint,
|
|
670
|
+
effectiveTargetId,
|
|
572
671
|
item,
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
});
|
|
600
|
-
return result;
|
|
672
|
+
chain,
|
|
673
|
+
payload,
|
|
674
|
+
chainFingerprints,
|
|
675
|
+
installContext,
|
|
676
|
+
cancelScope.signal
|
|
677
|
+
),
|
|
678
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
679
|
+
buildTopLevelFailure: (response) => {
|
|
680
|
+
const code = response.payload?.code ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch;
|
|
681
|
+
return (0, import_hwk_adapter_core2.failure)(
|
|
682
|
+
code,
|
|
683
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
684
|
+
response.payload?.params
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
689
|
+
method: "allNetworkGetAddress",
|
|
690
|
+
success: result.success,
|
|
691
|
+
payload: result
|
|
692
|
+
});
|
|
693
|
+
return result;
|
|
694
|
+
} finally {
|
|
695
|
+
cancelScope.release();
|
|
696
|
+
releaseOperationRetention?.();
|
|
697
|
+
}
|
|
601
698
|
};
|
|
602
699
|
}
|
|
700
|
+
function buildCancelledFailure(signal) {
|
|
701
|
+
const reason = signal.reason;
|
|
702
|
+
const code = typeof reason?.code === "number" ? reason.code : import_hwk_adapter_core2.HardwareErrorCode.UserAborted;
|
|
703
|
+
const message = typeof reason?.message === "string" ? reason.message : "";
|
|
704
|
+
return (0, import_hwk_adapter_core2.failure)(code, message || "All-network get-address cancelled");
|
|
705
|
+
}
|
|
603
706
|
function isTopLevelAllNetworkFailure(response) {
|
|
604
707
|
if (response.success) {
|
|
605
708
|
return false;
|
|
606
709
|
}
|
|
607
710
|
const code = response.payload?.code;
|
|
608
|
-
return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch ||
|
|
711
|
+
return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch || (0, import_hwk_adapter_core2.isUserRefusal)(code) || (0, import_hwk_adapter_core2.isConnectionLost)(code);
|
|
609
712
|
}
|
|
610
713
|
function getItemDeviceId(item) {
|
|
611
714
|
const { deviceId } = item;
|
|
@@ -635,8 +738,18 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
635
738
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
636
739
|
return coin ? { ...item, coin } : item;
|
|
637
740
|
}
|
|
638
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
639
|
-
const
|
|
741
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
|
|
742
|
+
const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
|
|
743
|
+
if (!knownFingerprint && cancelSignal.aborted) {
|
|
744
|
+
const cancelled = buildCancelledFailure(cancelSignal);
|
|
745
|
+
return { ...item, success: false, payload: cancelled.payload };
|
|
746
|
+
}
|
|
747
|
+
const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
|
|
748
|
+
getChainFingerprint,
|
|
749
|
+
context.connection?.connectId ?? connectId,
|
|
750
|
+
chain,
|
|
751
|
+
context
|
|
752
|
+
);
|
|
640
753
|
if (!fingerprint) {
|
|
641
754
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
642
755
|
}
|
|
@@ -657,8 +770,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
657
770
|
}
|
|
658
771
|
};
|
|
659
772
|
}
|
|
660
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
661
|
-
const response = await getChainFingerprint(connectId, chain);
|
|
773
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
|
|
774
|
+
const response = await getChainFingerprint(connectId, chain, context);
|
|
662
775
|
return response.success ? response.payload : "";
|
|
663
776
|
}
|
|
664
777
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -751,6 +864,16 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
|
|
|
751
864
|
function formatDeviceMismatchError(expected, actual) {
|
|
752
865
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
753
866
|
}
|
|
867
|
+
var LOG_REDACTED_RESULT_KEYS = ["ufvk", "orchardFvk"];
|
|
868
|
+
function redactResultForLog(result) {
|
|
869
|
+
if (!result || typeof result !== "object") return result;
|
|
870
|
+
const keys = LOG_REDACTED_RESULT_KEYS.filter((key) => key in result);
|
|
871
|
+
if (!keys.length) return result;
|
|
872
|
+
return { ...result, ...Object.fromEntries(keys.map((key) => [key, "[redacted]"])) };
|
|
873
|
+
}
|
|
874
|
+
function isLostConnectionError(err) {
|
|
875
|
+
return isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err);
|
|
876
|
+
}
|
|
754
877
|
var BTC_HIGH_INDEX_THRESHOLD = 100;
|
|
755
878
|
function btcAccountIndexFromPath(path) {
|
|
756
879
|
const segments = path.replace(/^m\//, "").split("/");
|
|
@@ -762,9 +885,28 @@ function btcAccountIndexFromPath(path) {
|
|
|
762
885
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
763
886
|
constructor(connector, options) {
|
|
764
887
|
this.vendor = "ledger";
|
|
888
|
+
// Cancel frees the DMK action and its intent-queue slot, but cannot retract a
|
|
889
|
+
// confirmation screen the device is already showing.
|
|
890
|
+
this.cancelCapability = "stops-waiting";
|
|
765
891
|
this.emitter = new import_hwk_adapter_core3.TypedEventEmitter();
|
|
892
|
+
this._operations = new import_hwk_adapter_core3.OperationRegistry({
|
|
893
|
+
vendor: "ledger",
|
|
894
|
+
onEnded: (operation, reason) => {
|
|
895
|
+
this.emitter.emit(import_hwk_adapter_core3.SDK.OPERATION_ENDED, {
|
|
896
|
+
type: import_hwk_adapter_core3.SDK.OPERATION_ENDED,
|
|
897
|
+
payload: { operationId: operation.operationId, reason }
|
|
898
|
+
});
|
|
899
|
+
if (reason === "timeout") {
|
|
900
|
+
void this._releaseOperationConnection(operation);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
});
|
|
766
904
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
767
905
|
this._sessions = /* @__PURE__ */ new Map();
|
|
906
|
+
// Transport each session was opened on, by sessionId. The adapter-wide
|
|
907
|
+
// `_activeConnectionType` moves with every discovery and can go stale.
|
|
908
|
+
this._sessionTransports = /* @__PURE__ */ new Map();
|
|
909
|
+
this._selectedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
768
910
|
this._uiRegistry = new import_hwk_adapter_core3.UiRequestRegistry();
|
|
769
911
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
770
912
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -778,10 +920,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
778
920
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
779
921
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
780
922
|
this._doConnectAbortController = null;
|
|
923
|
+
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
924
|
+
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
925
|
+
this._resetPromise = null;
|
|
926
|
+
this._stateGeneration = 0;
|
|
927
|
+
this._connectorTeardownTail = Promise.resolve();
|
|
928
|
+
this._pendingConnectorTeardowns = 0;
|
|
929
|
+
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
930
|
+
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
781
931
|
this._installProgressLastEmittedValue = -Infinity;
|
|
782
932
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
783
933
|
callChain: this.callChain.bind(this),
|
|
784
|
-
getChainFingerprint: (connectId, chain) =>
|
|
934
|
+
getChainFingerprint: async (connectId, chain, context) => {
|
|
935
|
+
try {
|
|
936
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
937
|
+
chain,
|
|
938
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
939
|
+
);
|
|
940
|
+
return (0, import_hwk_adapter_core3.success)(fingerprint);
|
|
941
|
+
} catch (error) {
|
|
942
|
+
return this.errorToFailure(error);
|
|
943
|
+
}
|
|
944
|
+
},
|
|
945
|
+
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
946
|
+
errorToFailure: (error) => this.errorToFailure(error),
|
|
947
|
+
createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
|
|
785
948
|
});
|
|
786
949
|
// ---------------------------------------------------------------------------
|
|
787
950
|
// Private helpers
|
|
@@ -791,7 +954,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
791
954
|
*
|
|
792
955
|
* - If a session already exists for the given connectId, reuse it.
|
|
793
956
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
794
|
-
* - Otherwise: search →
|
|
957
|
+
* - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
|
|
795
958
|
*/
|
|
796
959
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
797
960
|
this._connectingPromise = null;
|
|
@@ -808,6 +971,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
808
971
|
});
|
|
809
972
|
};
|
|
810
973
|
this.deviceDisconnectHandler = (data) => {
|
|
974
|
+
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
975
|
+
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
976
|
+
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
977
|
+
} else {
|
|
978
|
+
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
979
|
+
}
|
|
811
980
|
this._discoveredDevices.delete(data.connectId);
|
|
812
981
|
this._sessions.delete(data.connectId);
|
|
813
982
|
this.emitter.emit(import_hwk_adapter_core3.DEVICE.DISCONNECT, {
|
|
@@ -816,10 +985,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
816
985
|
});
|
|
817
986
|
};
|
|
818
987
|
// Forward connector `ui-event` to the public hw.emitter so consumers only
|
|
819
|
-
// need to subscribe in one place.
|
|
820
|
-
//
|
|
821
|
-
// exists (race during teardown) we drop. All other variants pass through
|
|
822
|
-
// unchanged.
|
|
988
|
+
// need to subscribe in one place. The AppInstallProgress variant re-keys
|
|
989
|
+
// sessionId to connectId via `_sessions`, and drops if no mapping exists.
|
|
823
990
|
this.uiEventForwarder = (event) => {
|
|
824
991
|
if (event.type === import_hwk_adapter_core3.EConnectorInteraction.AppInstallProgress) {
|
|
825
992
|
let connectId;
|
|
@@ -864,11 +1031,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
864
1031
|
this._jobQueue = new import_hwk_adapter_core3.DeviceJobQueue();
|
|
865
1032
|
this.registerEventListeners();
|
|
866
1033
|
}
|
|
1034
|
+
_finishBleBinding(status) {
|
|
1035
|
+
const selectionRequestId = this._bindingSelectionRequestId;
|
|
1036
|
+
this._bindingSelectionRequestId = void 0;
|
|
1037
|
+
if (selectionRequestId)
|
|
1038
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
1039
|
+
type: import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
1040
|
+
payload: { selectionRequestId, status }
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
_isBleConnection() {
|
|
1044
|
+
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
1045
|
+
}
|
|
867
1046
|
// Transport
|
|
868
1047
|
get activeTransport() {
|
|
869
|
-
return
|
|
1048
|
+
return this._isBleConnection() ? "ble" : "hid";
|
|
870
1049
|
}
|
|
871
1050
|
getAvailableTransports() {
|
|
1051
|
+
if (this.connector.availableTransports) {
|
|
1052
|
+
return this.connector.availableTransports.map(
|
|
1053
|
+
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
872
1056
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
873
1057
|
}
|
|
874
1058
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -885,21 +1069,40 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
885
1069
|
* The next operation will re-discover and re-connect automatically.
|
|
886
1070
|
*/
|
|
887
1071
|
resetState() {
|
|
1072
|
+
void this._resetStateAndDisconnectSessions();
|
|
1073
|
+
}
|
|
1074
|
+
_resetStateAndDisconnectSessions() {
|
|
1075
|
+
if (this._resetPromise) return this._resetPromise;
|
|
1076
|
+
const sessionIds = new Set(this._sessions.values());
|
|
1077
|
+
this._stateGeneration += 1;
|
|
1078
|
+
this._finishBleBinding("cancelled");
|
|
1079
|
+
this._operations.endAll("runtime-reset");
|
|
1080
|
+
this._doConnectAbortController?.abort();
|
|
888
1081
|
this._discoveredDevices.clear();
|
|
889
1082
|
this._sessions.clear();
|
|
1083
|
+
this._sessionTransports.clear();
|
|
1084
|
+
this._selectedBleReconnectTargets.clear();
|
|
890
1085
|
this._connectingPromise = null;
|
|
1086
|
+
this._doConnectAbortController = null;
|
|
891
1087
|
this._uiRegistry.reset();
|
|
892
1088
|
this._jobQueue.clear();
|
|
893
1089
|
this._btcHighIndexConfirmedThisSession = false;
|
|
1090
|
+
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1091
|
+
for (const sessionId of sessionIds) {
|
|
1092
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
this._resetPromise = resetPromise;
|
|
1096
|
+
return resetPromise.finally(() => {
|
|
1097
|
+
if (this._resetPromise === resetPromise) {
|
|
1098
|
+
this._resetPromise = null;
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
894
1101
|
}
|
|
895
1102
|
async dispose() {
|
|
896
|
-
this.
|
|
897
|
-
this._jobQueue.clear();
|
|
1103
|
+
await this._resetStateAndDisconnectSessions();
|
|
898
1104
|
this.unregisterEventListeners();
|
|
899
1105
|
this.connector.reset();
|
|
900
|
-
this._discoveredDevices.clear();
|
|
901
|
-
this._sessions.clear();
|
|
902
|
-
this._btcHighIndexConfirmedThisSession = false;
|
|
903
1106
|
this.emitter.removeAllListeners();
|
|
904
1107
|
}
|
|
905
1108
|
uiResponse(response) {
|
|
@@ -909,17 +1112,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
909
1112
|
// Device management
|
|
910
1113
|
// ---------------------------------------------------------------------------
|
|
911
1114
|
async searchDevices(options) {
|
|
1115
|
+
return this._searchDevices(options);
|
|
1116
|
+
}
|
|
1117
|
+
async _searchDevices(options, signal) {
|
|
912
1118
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
913
1119
|
try {
|
|
914
1120
|
if (options?.resetSession) {
|
|
915
|
-
this.
|
|
916
|
-
|
|
917
|
-
this.
|
|
918
|
-
|
|
919
|
-
|
|
1121
|
+
await this._resetStateAndDisconnectSessions();
|
|
1122
|
+
} else {
|
|
1123
|
+
await this._connectorTeardownTail;
|
|
1124
|
+
}
|
|
1125
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1126
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1127
|
+
const stateGeneration = this._stateGeneration;
|
|
1128
|
+
const devices = await this.connector.searchDevices(
|
|
1129
|
+
options?.transportType ? {
|
|
1130
|
+
transportType: options.transportType,
|
|
1131
|
+
waitForAll: options.waitForAllTransports
|
|
1132
|
+
} : void 0
|
|
1133
|
+
);
|
|
1134
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1135
|
+
if (stateGeneration !== this._stateGeneration) {
|
|
1136
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1137
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
1138
|
+
message: "Ledger discovery was reset"
|
|
1139
|
+
});
|
|
920
1140
|
}
|
|
921
|
-
await this._ensureDevicePermission();
|
|
922
|
-
const devices = await this.connector.searchDevices();
|
|
923
1141
|
this._discoveredDevices.clear();
|
|
924
1142
|
for (const d of devices) {
|
|
925
1143
|
if (d.connectId) {
|
|
@@ -927,7 +1145,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
927
1145
|
}
|
|
928
1146
|
}
|
|
929
1147
|
if (this._discoveredDevices.size === 0) {
|
|
930
|
-
await this._ensureDevicePermission();
|
|
1148
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
931
1149
|
}
|
|
932
1150
|
const result = Array.from(this._discoveredDevices.values());
|
|
933
1151
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -946,41 +1164,233 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
946
1164
|
throw err;
|
|
947
1165
|
}
|
|
948
1166
|
}
|
|
1167
|
+
async searchDeviceTargets(options) {
|
|
1168
|
+
const devices = await this.searchDevices(options);
|
|
1169
|
+
return devices.map((device) => ({
|
|
1170
|
+
searchTargetId: device.connectId,
|
|
1171
|
+
searchTargetReusePolicy: (0, import_hwk_adapter_core3.resolveSearchTargetReusePolicy)(device),
|
|
1172
|
+
vendor: "ledger",
|
|
1173
|
+
connectionType: device.connectionType,
|
|
1174
|
+
kind: "physical",
|
|
1175
|
+
label: device.label,
|
|
1176
|
+
model: device.model,
|
|
1177
|
+
modelName: device.modelName,
|
|
1178
|
+
serialNumber: device.serialNumber
|
|
1179
|
+
}));
|
|
1180
|
+
}
|
|
1181
|
+
async listConnectionTargets(options) {
|
|
1182
|
+
const targets = await this.searchDeviceTargets(options);
|
|
1183
|
+
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1184
|
+
...target,
|
|
1185
|
+
targetId: searchTargetId
|
|
1186
|
+
}));
|
|
1187
|
+
}
|
|
949
1188
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
950
|
-
async _evictAllSessions() {
|
|
1189
|
+
async _evictAllSessions(preserveOperationId) {
|
|
1190
|
+
this._operations.endAll("explicit", preserveOperationId);
|
|
951
1191
|
if (this._sessions.size === 0) return;
|
|
952
1192
|
const stale = [...this._sessions.values()];
|
|
953
1193
|
this._sessions.clear();
|
|
954
|
-
for (const sid of stale)
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1194
|
+
for (const sid of stale) this._sessionTransports.delete(sid);
|
|
1195
|
+
await this._runConnectorTeardown(async () => {
|
|
1196
|
+
for (const sid of stale) {
|
|
1197
|
+
try {
|
|
1198
|
+
await this.connector.disconnect(sid);
|
|
1199
|
+
} catch {
|
|
1200
|
+
}
|
|
958
1201
|
}
|
|
959
|
-
}
|
|
1202
|
+
});
|
|
960
1203
|
}
|
|
961
1204
|
static _createDeviceBusyError(method) {
|
|
962
1205
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
963
1206
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceBusy
|
|
964
1207
|
});
|
|
965
1208
|
}
|
|
966
|
-
async connectDevice(
|
|
1209
|
+
async connectDevice(searchTargetId) {
|
|
1210
|
+
try {
|
|
1211
|
+
return await this._jobQueue.enqueue(
|
|
1212
|
+
searchTargetId || "__ledger_connect__",
|
|
1213
|
+
async (signal) => {
|
|
1214
|
+
const connected = await this._connectTarget(searchTargetId, void 0, signal);
|
|
1215
|
+
if (!connected.success) return connected;
|
|
1216
|
+
return this._createOperation(searchTargetId, connected.payload);
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
label: "connectDevice",
|
|
1220
|
+
rejectIfBusy: true,
|
|
1221
|
+
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1222
|
+
}
|
|
1223
|
+
);
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
return this.errorToFailure(error);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
/** Ledger binds whichever BLE device the user picks; its wallet is not checked. */
|
|
1229
|
+
async bindBleDevice(params) {
|
|
1230
|
+
try {
|
|
1231
|
+
return await this._jobQueue.enqueue(
|
|
1232
|
+
"__ledger_bind_ble__",
|
|
1233
|
+
async (signal) => {
|
|
1234
|
+
if (!this.getAvailableTransports().includes("ble")) {
|
|
1235
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1236
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
1237
|
+
message: "Ledger Bluetooth transport is not available"
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
this._activeConnectionType = "ble";
|
|
1241
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1242
|
+
const attempt = {
|
|
1243
|
+
extra: params.extra,
|
|
1244
|
+
bindingReason: "manual-rebind"
|
|
1245
|
+
};
|
|
1246
|
+
try {
|
|
1247
|
+
const connectId = await this._connectFirstOrSelect(
|
|
1248
|
+
[],
|
|
1249
|
+
void 0,
|
|
1250
|
+
true,
|
|
1251
|
+
void 0,
|
|
1252
|
+
attempt,
|
|
1253
|
+
signal
|
|
1254
|
+
);
|
|
1255
|
+
if (!attempt.bindingSaved) {
|
|
1256
|
+
await this._dropSession(connectId, signal);
|
|
1257
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1258
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UnknownError,
|
|
1259
|
+
message: "Bluetooth binding could not be saved",
|
|
1260
|
+
origin: "host"
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
return (0, import_hwk_adapter_core3.success)(connectId);
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1266
|
+
throw error;
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
label: "bindBleDevice",
|
|
1271
|
+
rejectIfBusy: true,
|
|
1272
|
+
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
return this.errorToFailure(error);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async acquireOperation(connectId, context) {
|
|
1280
|
+
try {
|
|
1281
|
+
return await this._jobQueue.enqueue(
|
|
1282
|
+
connectId || "__ledger_acquire__",
|
|
1283
|
+
async (signal) => {
|
|
1284
|
+
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1285
|
+
const hint = context.knownConnections?.find(
|
|
1286
|
+
(connection) => connection.transport === transport
|
|
1287
|
+
);
|
|
1288
|
+
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1289
|
+
await this._ensureDevicePermission(target, void 0, signal);
|
|
1290
|
+
const attempt = {
|
|
1291
|
+
...context,
|
|
1292
|
+
extra: context.extra ? { ...context.extra } : void 0
|
|
1293
|
+
};
|
|
1294
|
+
try {
|
|
1295
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1296
|
+
target,
|
|
1297
|
+
signal,
|
|
1298
|
+
true,
|
|
1299
|
+
void 0,
|
|
1300
|
+
attempt
|
|
1301
|
+
);
|
|
1302
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1303
|
+
return this._createOperation(connectId, resolvedConnectId);
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1306
|
+
throw error;
|
|
1307
|
+
}
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
label: "acquireOperation",
|
|
1311
|
+
rejectIfBusy: true,
|
|
1312
|
+
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1313
|
+
}
|
|
1314
|
+
);
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
return this.errorToFailure(error);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1320
|
+
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1321
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1322
|
+
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1323
|
+
const connectionType = sessionId && this._sessionTransports.get(sessionId) || (this._isBleConnection() ? "ble" : "usb");
|
|
1324
|
+
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1325
|
+
vendor: "ledger",
|
|
1326
|
+
model: "unknown",
|
|
1327
|
+
firmwareVersion: "",
|
|
1328
|
+
deviceId: "",
|
|
1329
|
+
connectId: resolvedConnectId,
|
|
1330
|
+
connectionType
|
|
1331
|
+
};
|
|
1332
|
+
const operation = this._operations.create({
|
|
1333
|
+
searchTargetId,
|
|
1334
|
+
connectId: resolvedConnectId,
|
|
1335
|
+
device,
|
|
1336
|
+
connectionType,
|
|
1337
|
+
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1338
|
+
});
|
|
1339
|
+
return (0, import_hwk_adapter_core3.success)(operation.operationId);
|
|
1340
|
+
}
|
|
1341
|
+
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
967
1342
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
968
1343
|
try {
|
|
969
|
-
|
|
1344
|
+
this._assertConnectorReady("connectDevice");
|
|
1345
|
+
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1346
|
+
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1347
|
+
this._activeConnectionType = discoveredType;
|
|
1348
|
+
}
|
|
1349
|
+
if (this._isBleConnection() && !connectId) {
|
|
970
1350
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
971
1351
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
972
1352
|
});
|
|
973
1353
|
}
|
|
974
|
-
|
|
975
|
-
|
|
1354
|
+
await this._ensureDevicePermission(connectId, void 0, signal);
|
|
1355
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1356
|
+
const isMultiTransport = (this.connector.availableTransports?.length ?? 0) > 1;
|
|
1357
|
+
if (!isMultiTransport && this._isBleConnection()) {
|
|
1358
|
+
const previousSessionId = this._sessions.get(connectId);
|
|
1359
|
+
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1360
|
+
if (previousSessionId) {
|
|
1361
|
+
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1362
|
+
this._sessions.delete(connectId);
|
|
1363
|
+
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1364
|
+
}
|
|
1365
|
+
} else {
|
|
1366
|
+
await this._evictAllSessions(preserveOperationId);
|
|
1367
|
+
}
|
|
1368
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1369
|
+
const stateGeneration = this._stateGeneration;
|
|
1370
|
+
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1371
|
+
let session;
|
|
1372
|
+
this._connectingConnectId = connectId;
|
|
1373
|
+
try {
|
|
1374
|
+
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1375
|
+
transportType: this._activeConnectionType ?? "usb"
|
|
1376
|
+
}) : await this.connector.connect(connectId);
|
|
1377
|
+
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1378
|
+
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1379
|
+
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1380
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
} finally {
|
|
1384
|
+
if (this._connectingConnectId === connectId) this._connectingConnectId = void 0;
|
|
1385
|
+
releaseOperation();
|
|
976
1386
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
this.
|
|
1387
|
+
const resolvedConnectId = session.deviceInfo?.connectId || connectId;
|
|
1388
|
+
this._sessions.set(resolvedConnectId, session.sessionId);
|
|
1389
|
+
this._sessionTransports.set(session.sessionId, this._isBleConnection() ? "ble" : "usb");
|
|
980
1390
|
if (session.deviceInfo) {
|
|
981
|
-
this._discoveredDevices.set(
|
|
1391
|
+
this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
|
|
982
1392
|
}
|
|
983
|
-
const result = (0, import_hwk_adapter_core3.success)(
|
|
1393
|
+
const result = (0, import_hwk_adapter_core3.success)(resolvedConnectId);
|
|
984
1394
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
985
1395
|
return result;
|
|
986
1396
|
} catch (err) {
|
|
@@ -993,30 +1403,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
993
1403
|
return failureResult;
|
|
994
1404
|
}
|
|
995
1405
|
}
|
|
996
|
-
async
|
|
1406
|
+
async releaseOperation(operationId) {
|
|
1407
|
+
const operation = this._operations.find(operationId);
|
|
1408
|
+
if (!operation) {
|
|
1409
|
+
this._operations.resolve(operationId);
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1413
|
+
if (!endedOperation) return;
|
|
1414
|
+
const { connectId } = operation;
|
|
997
1415
|
debugLog("[LedgerAdapter][REQ]", {
|
|
998
|
-
method: "
|
|
1416
|
+
method: "releaseOperation",
|
|
999
1417
|
connectId,
|
|
1000
1418
|
params: { connectId }
|
|
1001
1419
|
});
|
|
1002
1420
|
try {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
await this.connector.disconnect(sessionId);
|
|
1006
|
-
this._sessions.delete(connectId);
|
|
1007
|
-
}
|
|
1008
|
-
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1421
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1422
|
+
debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
|
|
1009
1423
|
} catch (err) {
|
|
1010
1424
|
const e = err;
|
|
1011
1425
|
debugLog("[LedgerAdapter][RES]", {
|
|
1012
|
-
method: "
|
|
1426
|
+
method: "releaseOperation",
|
|
1013
1427
|
success: false,
|
|
1014
1428
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1015
1429
|
});
|
|
1016
1430
|
throw err;
|
|
1017
1431
|
}
|
|
1018
1432
|
}
|
|
1019
|
-
async
|
|
1433
|
+
async _releaseOperationConnection(operation) {
|
|
1434
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
1435
|
+
for (const [connectId, sessionId] of this._sessions) {
|
|
1436
|
+
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1437
|
+
this._sessions.delete(connectId);
|
|
1438
|
+
sessionIds.add(sessionId);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
for (const sessionId of sessionIds) {
|
|
1442
|
+
await this._runConnectorTeardown(
|
|
1443
|
+
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
async _releaseLostOperationConnection(operationId) {
|
|
1448
|
+
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1449
|
+
if (!endedOperation) return;
|
|
1450
|
+
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1451
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1452
|
+
}
|
|
1453
|
+
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1454
|
+
let connectId;
|
|
1455
|
+
try {
|
|
1456
|
+
connectId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
return this.errorToFailure(error);
|
|
1459
|
+
}
|
|
1020
1460
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1021
1461
|
method: "getDeviceInfo",
|
|
1022
1462
|
connectId,
|
|
@@ -1051,12 +1491,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1051
1491
|
success: false,
|
|
1052
1492
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1053
1493
|
});
|
|
1054
|
-
|
|
1494
|
+
return this.errorToFailure(err);
|
|
1055
1495
|
}
|
|
1056
1496
|
}
|
|
1057
|
-
getSupportedChains() {
|
|
1058
|
-
return ["evm", "btc", "sol", "tron"];
|
|
1059
|
-
}
|
|
1060
1497
|
// ---------------------------------------------------------------------------
|
|
1061
1498
|
// Chain call helper
|
|
1062
1499
|
// ---------------------------------------------------------------------------
|
|
@@ -1091,13 +1528,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1091
1528
|
if (params && typeof params === "object") {
|
|
1092
1529
|
const {
|
|
1093
1530
|
autoInstallApp,
|
|
1531
|
+
operationId,
|
|
1094
1532
|
passphraseState: _passphraseState,
|
|
1095
1533
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1534
|
+
knownConnections,
|
|
1535
|
+
extra,
|
|
1536
|
+
allowDeviceSelection,
|
|
1537
|
+
supportedTransports,
|
|
1096
1538
|
...rest
|
|
1097
1539
|
} = params;
|
|
1098
1540
|
return {
|
|
1099
1541
|
commonParams: {
|
|
1100
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1542
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
|
|
1543
|
+
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1544
|
+
knownConnections,
|
|
1545
|
+
extra,
|
|
1546
|
+
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0,
|
|
1547
|
+
supportedTransports
|
|
1101
1548
|
},
|
|
1102
1549
|
rest
|
|
1103
1550
|
};
|
|
@@ -1273,9 +1720,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1273
1720
|
);
|
|
1274
1721
|
}
|
|
1275
1722
|
// ---------------------------------------------------------------------------
|
|
1276
|
-
//
|
|
1277
|
-
//
|
|
1278
|
-
|
|
1723
|
+
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1724
|
+
// ---------------------------------------------------------------------------
|
|
1725
|
+
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
1726
|
+
return this.callChainWithMergedParams(
|
|
1727
|
+
connectId,
|
|
1728
|
+
deviceId,
|
|
1729
|
+
"zcash",
|
|
1730
|
+
"zcashGetFullViewingKey",
|
|
1731
|
+
params
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1735
|
+
return this.callChainWithMergedParams(
|
|
1736
|
+
connectId,
|
|
1737
|
+
deviceId,
|
|
1738
|
+
"zcash",
|
|
1739
|
+
"zcashGetShieldedAddress",
|
|
1740
|
+
params
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
// ---------------------------------------------------------------------------
|
|
1744
|
+
// App management: OS-level app install/list, no fingerprint or chain dispatch.
|
|
1279
1745
|
// ---------------------------------------------------------------------------
|
|
1280
1746
|
async installApp(connectId, appName) {
|
|
1281
1747
|
try {
|
|
@@ -1361,7 +1827,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1361
1827
|
);
|
|
1362
1828
|
}
|
|
1363
1829
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1364
|
-
this.
|
|
1830
|
+
await this._resetStateAndDisconnectSessions();
|
|
1365
1831
|
}
|
|
1366
1832
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1367
1833
|
if (!result.isGenuine) {
|
|
@@ -1387,10 +1853,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1387
1853
|
if (relayUrl) {
|
|
1388
1854
|
try {
|
|
1389
1855
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1390
|
-
this.
|
|
1856
|
+
await this._resetStateAndDisconnectSessions();
|
|
1391
1857
|
} catch {
|
|
1392
1858
|
this.connector.reset();
|
|
1393
|
-
this.
|
|
1859
|
+
await this._resetStateAndDisconnectSessions();
|
|
1394
1860
|
}
|
|
1395
1861
|
}
|
|
1396
1862
|
}
|
|
@@ -1402,6 +1868,20 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1402
1868
|
this.emitter.off(event, listener);
|
|
1403
1869
|
}
|
|
1404
1870
|
cancel(connectId) {
|
|
1871
|
+
const namedOperationIsLive = (id) => {
|
|
1872
|
+
try {
|
|
1873
|
+
this._operations.resolve(id);
|
|
1874
|
+
return true;
|
|
1875
|
+
} catch {
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) && !namedOperationIsLive(connectId)) {
|
|
1880
|
+
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1881
|
+
connectId
|
|
1882
|
+
});
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1405
1885
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1406
1886
|
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
1407
1887
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1412,14 +1892,46 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1412
1892
|
this._lastCancelReason = void 0;
|
|
1413
1893
|
}
|
|
1414
1894
|
}, 2e3);
|
|
1415
|
-
this.
|
|
1416
|
-
|
|
1417
|
-
|
|
1895
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1896
|
+
let operationId;
|
|
1897
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId)) {
|
|
1898
|
+
operationId = connectId;
|
|
1899
|
+
} else if (!connectId && (0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) {
|
|
1900
|
+
operationId = activeJobId;
|
|
1901
|
+
}
|
|
1902
|
+
let resolvedConnectId = connectId;
|
|
1903
|
+
if (operationId) {
|
|
1904
|
+
try {
|
|
1905
|
+
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1906
|
+
} catch {
|
|
1907
|
+
resolvedConnectId = void 0;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1911
|
+
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1912
|
+
if (!connectId) {
|
|
1913
|
+
this._uiRegistry.cancel();
|
|
1914
|
+
} else if (pendingOperationId) {
|
|
1915
|
+
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1916
|
+
}
|
|
1917
|
+
this._finishBleBinding("cancelled");
|
|
1918
|
+
const queueKeys = /* @__PURE__ */ new Set();
|
|
1919
|
+
if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
|
|
1920
|
+
if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
|
|
1921
|
+
if (queueKeys.size) {
|
|
1922
|
+
let cancelledAnyJob = false;
|
|
1923
|
+
for (const key of queueKeys) {
|
|
1924
|
+
cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
|
|
1925
|
+
}
|
|
1926
|
+
debugLog("[LedgerAdapter] cancel routed to queue keys", {
|
|
1927
|
+
queueKeys: [...queueKeys],
|
|
1928
|
+
cancelledAnyJob
|
|
1929
|
+
});
|
|
1418
1930
|
} else {
|
|
1419
1931
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1420
1932
|
}
|
|
1421
|
-
if (
|
|
1422
|
-
const sessionId = this._sessions.get(
|
|
1933
|
+
if (resolvedConnectId) {
|
|
1934
|
+
const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
|
|
1423
1935
|
void this.connector.cancel(sessionId);
|
|
1424
1936
|
} else {
|
|
1425
1937
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1427,45 +1939,85 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1427
1939
|
if (this._connectingPromise) {
|
|
1428
1940
|
this._doConnectAbortController?.abort(userAbortReason);
|
|
1429
1941
|
}
|
|
1942
|
+
const connectingConnectId = this._connectingConnectId;
|
|
1943
|
+
if (connectingConnectId) void this.connector.cancel(connectingConnectId);
|
|
1430
1944
|
}
|
|
1431
1945
|
// ---------------------------------------------------------------------------
|
|
1432
1946
|
// Chain fingerprint
|
|
1433
1947
|
// ---------------------------------------------------------------------------
|
|
1948
|
+
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1434
1949
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1435
1950
|
try {
|
|
1436
1951
|
const fingerprint = await this._computeChainFingerprint(
|
|
1437
1952
|
chain,
|
|
1438
1953
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1439
1954
|
);
|
|
1955
|
+
if (deviceId) {
|
|
1956
|
+
if (fingerprint !== deviceId) {
|
|
1957
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
1958
|
+
import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1959
|
+
formatDeviceMismatchError(deviceId, fingerprint)
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1440
1963
|
return (0, import_hwk_adapter_core3.success)(fingerprint);
|
|
1441
1964
|
} catch (err) {
|
|
1442
1965
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
1443
1966
|
return this.errorToFailure(err);
|
|
1444
1967
|
}
|
|
1445
1968
|
}
|
|
1446
|
-
/**
|
|
1447
|
-
|
|
1448
|
-
* Safe to call inside connectorCall without causing queue deadlock.
|
|
1449
|
-
*/
|
|
1450
|
-
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
1451
|
-
if (!deviceId) return { success: true };
|
|
1969
|
+
/** Ledger takes the selected BLE endpoint as the device and binds it right away. */
|
|
1970
|
+
async _saveSelectedBleBinding(connectId, selectionRequestId, context, operationId, signal) {
|
|
1452
1971
|
try {
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
|
|
1972
|
+
const outcome = await (0, import_hwk_adapter_core3.requestSaveDeviceBinding)(
|
|
1973
|
+
this.emitter,
|
|
1974
|
+
this._uiRegistry,
|
|
1975
|
+
{
|
|
1976
|
+
selectionRequestId,
|
|
1977
|
+
connection: { transport: "ble", connectId },
|
|
1978
|
+
extra: context?.extra,
|
|
1979
|
+
operationId
|
|
1980
|
+
},
|
|
1981
|
+
signal
|
|
1456
1982
|
);
|
|
1457
|
-
if (
|
|
1458
|
-
|
|
1983
|
+
if (context) context.bindingSaved = outcome.saved;
|
|
1984
|
+
if (!outcome.saved) {
|
|
1985
|
+
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
1986
|
+
connectId,
|
|
1987
|
+
reason: outcome.reason
|
|
1988
|
+
});
|
|
1459
1989
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
await this._dropSession(connectId, signal);
|
|
1992
|
+
throw error;
|
|
1993
|
+
} finally {
|
|
1994
|
+
if (this._bindingSelectionRequestId === selectionRequestId) {
|
|
1995
|
+
this._bindingSelectionRequestId = void 0;
|
|
1465
1996
|
}
|
|
1466
|
-
throw err;
|
|
1467
1997
|
}
|
|
1468
1998
|
}
|
|
1999
|
+
async _dropSession(connectId, signal) {
|
|
2000
|
+
const sessionId = this._sessions.get(connectId);
|
|
2001
|
+
this._sessions.delete(connectId);
|
|
2002
|
+
if (!sessionId) return;
|
|
2003
|
+
this._sessionTransports.delete(sessionId);
|
|
2004
|
+
const teardown = this._runConnectorTeardown(() => this.connector.disconnect(sessionId)).catch(
|
|
2005
|
+
() => void 0
|
|
2006
|
+
);
|
|
2007
|
+
if (!signal.aborted) await teardown;
|
|
2008
|
+
}
|
|
2009
|
+
/** Verify on the acquired session without re-entering the job queue. */
|
|
2010
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
2011
|
+
if (!deviceId) return { success: true };
|
|
2012
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
2013
|
+
chain,
|
|
2014
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
2015
|
+
);
|
|
2016
|
+
if (fingerprint === deviceId) {
|
|
2017
|
+
return { success: true };
|
|
2018
|
+
}
|
|
2019
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
2020
|
+
}
|
|
1469
2021
|
/**
|
|
1470
2022
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
1471
2023
|
*
|
|
@@ -1493,11 +2045,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1493
2045
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
1494
2046
|
} else if (chain === "tron") {
|
|
1495
2047
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2048
|
+
} else if (chain === "zcash") {
|
|
2049
|
+
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
1496
2050
|
} else {
|
|
1497
2051
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
1498
2052
|
}
|
|
1499
2053
|
return (0, import_hwk_adapter_core3.deriveDeviceFingerprint)(address);
|
|
1500
2054
|
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Operation owning the running job, so a mid-call UI request can name it.
|
|
2057
|
+
* Undefined at cold start when no operation owns the job.
|
|
2058
|
+
*/
|
|
2059
|
+
_activeOperationId() {
|
|
2060
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
2061
|
+
if (!activeJobId) return void 0;
|
|
2062
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) return activeJobId;
|
|
2063
|
+
return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
|
|
2064
|
+
}
|
|
1501
2065
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
1502
2066
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
1503
2067
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
@@ -1507,15 +2071,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1507
2071
|
if (signal?.aborted) {
|
|
1508
2072
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1509
2073
|
}
|
|
2074
|
+
const operationId = this._activeOperationId();
|
|
1510
2075
|
const waitPromise = this._uiRegistry.wait(
|
|
1511
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2076
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2077
|
+
{ operationId }
|
|
1512
2078
|
);
|
|
1513
2079
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
1514
2080
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
1515
2081
|
payload: {
|
|
1516
2082
|
vendor: "ledger",
|
|
1517
2083
|
reason: "device-not-found",
|
|
1518
|
-
message: "Please connect and unlock your Ledger device"
|
|
2084
|
+
message: "Please connect and unlock your Ledger device",
|
|
2085
|
+
operationId
|
|
1519
2086
|
}
|
|
1520
2087
|
});
|
|
1521
2088
|
let payload;
|
|
@@ -1576,15 +2143,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1576
2143
|
return { ...params, showOnDevice: true };
|
|
1577
2144
|
}
|
|
1578
2145
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2146
|
+
const operationId = this._activeOperationId();
|
|
1579
2147
|
const waitPromise = this._uiRegistry.wait(
|
|
1580
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2148
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2149
|
+
{ operationId }
|
|
1581
2150
|
);
|
|
1582
2151
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
1583
2152
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
1584
2153
|
payload: {
|
|
1585
2154
|
vendor: "ledger",
|
|
1586
2155
|
path,
|
|
1587
|
-
accountIndex
|
|
2156
|
+
accountIndex,
|
|
2157
|
+
operationId
|
|
1588
2158
|
}
|
|
1589
2159
|
});
|
|
1590
2160
|
try {
|
|
@@ -1601,12 +2171,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1601
2171
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
1602
2172
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
1603
2173
|
async _waitForInstallAppConfirm(appName) {
|
|
2174
|
+
const operationId = this._activeOperationId();
|
|
1604
2175
|
const waitPromise = this._uiRegistry.wait(
|
|
1605
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP
|
|
2176
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2177
|
+
{ operationId }
|
|
1606
2178
|
);
|
|
1607
2179
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
1608
2180
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
|
|
1609
|
-
payload: { vendor: "ledger", appName }
|
|
2181
|
+
payload: { vendor: "ledger", appName, operationId }
|
|
1610
2182
|
});
|
|
1611
2183
|
try {
|
|
1612
2184
|
const payload = await waitPromise;
|
|
@@ -1622,16 +2194,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1622
2194
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
1623
2195
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
1624
2196
|
// doesn't kill caller B's await.
|
|
1625
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2197
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
1626
2198
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
1627
|
-
|
|
2199
|
+
this._assertConnectorReady("connectDevice");
|
|
2200
|
+
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1628
2201
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1629
2202
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1630
2203
|
});
|
|
1631
2204
|
}
|
|
1632
2205
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
1633
2206
|
if (!connectId && this._sessions.size > 0) {
|
|
1634
|
-
if (!
|
|
2207
|
+
if (!this._isBleConnection() && this._sessions.size > 1) {
|
|
1635
2208
|
throw Object.assign(
|
|
1636
2209
|
new Error(
|
|
1637
2210
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -1641,15 +2214,24 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1641
2214
|
}
|
|
1642
2215
|
return this._sessions.keys().next().value;
|
|
1643
2216
|
}
|
|
1644
|
-
if (!this._connectingPromise) {
|
|
1645
|
-
|
|
1646
|
-
|
|
2217
|
+
if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
|
|
2218
|
+
const controller = new AbortController();
|
|
2219
|
+
this._doConnectAbortController = controller;
|
|
2220
|
+
const innerSignal = controller.signal;
|
|
1647
2221
|
this._connectingPromise = (async () => {
|
|
1648
2222
|
try {
|
|
1649
|
-
return await this._doConnect(
|
|
2223
|
+
return await this._doConnect(
|
|
2224
|
+
innerSignal,
|
|
2225
|
+
connectId,
|
|
2226
|
+
allowUsbEphemeralFallback,
|
|
2227
|
+
preserveOperationId,
|
|
2228
|
+
context
|
|
2229
|
+
);
|
|
1650
2230
|
} finally {
|
|
1651
|
-
this.
|
|
1652
|
-
|
|
2231
|
+
if (this._doConnectAbortController === controller) {
|
|
2232
|
+
this._connectingPromise = null;
|
|
2233
|
+
this._doConnectAbortController = null;
|
|
2234
|
+
}
|
|
1653
2235
|
}
|
|
1654
2236
|
})();
|
|
1655
2237
|
}
|
|
@@ -1658,22 +2240,57 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1658
2240
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1659
2241
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1660
2242
|
// throw DeviceNotFound so the user is kicked out of the loop.
|
|
1661
|
-
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
2243
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
2244
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2245
|
+
if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
|
|
2246
|
+
this._activeConnectionType = "usb";
|
|
2247
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
|
|
2248
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2249
|
+
if (usbDevices.length > 0) {
|
|
2250
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2251
|
+
(connection) => connection.transport === "usb"
|
|
2252
|
+
);
|
|
2253
|
+
const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
|
|
2254
|
+
return this._connectFirstOrSelect(
|
|
2255
|
+
usbDevices,
|
|
2256
|
+
usbTarget,
|
|
2257
|
+
allowUsbEphemeralFallback,
|
|
2258
|
+
preserveOperationId,
|
|
2259
|
+
context,
|
|
2260
|
+
internalSignal
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
|
|
2264
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2265
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2266
|
+
message: "This Ledger model has no Bluetooth transport; connect it over USB"
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
this._activeConnectionType = "ble";
|
|
2270
|
+
const knownBle = context?.knownConnections?.find(
|
|
2271
|
+
(connection) => connection.transport === "ble"
|
|
2272
|
+
);
|
|
2273
|
+
if (knownBle?.transport === "ble") {
|
|
2274
|
+
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2275
|
+
}
|
|
2276
|
+
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2277
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2278
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2279
|
+
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2280
|
+
});
|
|
1676
2281
|
}
|
|
2282
|
+
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2283
|
+
return this._connectFirstOrSelect(
|
|
2284
|
+
bleDevices,
|
|
2285
|
+
void 0,
|
|
2286
|
+
allowUsbEphemeralFallback,
|
|
2287
|
+
preserveOperationId,
|
|
2288
|
+
context,
|
|
2289
|
+
internalSignal
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
if (this._isBleConnection() && targetConnectId) {
|
|
2293
|
+
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
1677
2294
|
}
|
|
1678
2295
|
let confirms = 0;
|
|
1679
2296
|
while (!internalSignal.aborted) {
|
|
@@ -1681,22 +2298,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1681
2298
|
type: import_hwk_adapter_core3.EConnectorInteraction.Searching,
|
|
1682
2299
|
payload: { sessionId: "" }
|
|
1683
2300
|
});
|
|
1684
|
-
let devices = await this.
|
|
2301
|
+
let devices = await this._searchDevices(void 0, internalSignal);
|
|
2302
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1685
2303
|
if (devices.length === 0) {
|
|
1686
2304
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1687
2305
|
await new Promise((resolve) => {
|
|
1688
2306
|
setTimeout(resolve, import_hwk_adapter_core3.DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
1689
2307
|
});
|
|
1690
|
-
|
|
2308
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2309
|
+
devices = await this._searchDevices(void 0, internalSignal);
|
|
2310
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1691
2311
|
if (devices.length > 0) break;
|
|
1692
2312
|
}
|
|
1693
2313
|
}
|
|
1694
|
-
if (devices.length > 0) {
|
|
2314
|
+
if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
|
|
1695
2315
|
try {
|
|
1696
2316
|
return await this._connectFirstOrSelect(
|
|
1697
2317
|
devices,
|
|
1698
2318
|
targetConnectId,
|
|
1699
|
-
allowUsbEphemeralFallback
|
|
2319
|
+
allowUsbEphemeralFallback,
|
|
2320
|
+
preserveOperationId,
|
|
2321
|
+
context,
|
|
2322
|
+
internalSignal
|
|
1700
2323
|
);
|
|
1701
2324
|
} catch (err) {
|
|
1702
2325
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -1725,45 +2348,143 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1725
2348
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1726
2349
|
throw new Error("_doConnect aborted");
|
|
1727
2350
|
}
|
|
1728
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2351
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
|
|
2352
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1729
2353
|
if (targetConnectId) {
|
|
1730
2354
|
const target = devices.find(
|
|
1731
2355
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1732
2356
|
);
|
|
1733
2357
|
if (target) {
|
|
1734
|
-
return this._connectDeviceOrThrow(target.connectId);
|
|
2358
|
+
return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
|
|
1735
2359
|
}
|
|
1736
|
-
if (!
|
|
2360
|
+
if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
1737
2361
|
debugLog(
|
|
1738
2362
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
1739
2363
|
);
|
|
1740
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2364
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1741
2365
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2366
|
+
if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
|
|
2367
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
2368
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
2369
|
+
});
|
|
2370
|
+
if (this._isBleConnection()) {
|
|
2371
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2372
|
+
}
|
|
2373
|
+
throw err;
|
|
1747
2374
|
}
|
|
1748
|
-
throw err;
|
|
1749
2375
|
}
|
|
1750
|
-
|
|
2376
|
+
const requiresBleSelection = this._isBleConnection();
|
|
2377
|
+
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1751
2378
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1752
2379
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1753
2380
|
});
|
|
1754
2381
|
}
|
|
1755
|
-
if (
|
|
1756
|
-
|
|
2382
|
+
if (requiresBleSelection && context?.allowDeviceSelection !== false) {
|
|
2383
|
+
const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
|
|
2384
|
+
if (context) context.bindingSessionId = bindingSessionId;
|
|
2385
|
+
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2386
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2387
|
+
(connection) => connection.transport === "usb"
|
|
2388
|
+
);
|
|
2389
|
+
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2390
|
+
const { device, requestId } = await (0, import_hwk_adapter_core3.requestBleDeviceSelection)({
|
|
2391
|
+
emitter: this.emitter,
|
|
2392
|
+
registry: this._uiRegistry,
|
|
2393
|
+
signal,
|
|
2394
|
+
allowUsbFallback,
|
|
2395
|
+
scan: async () => {
|
|
2396
|
+
if (allowUsbFallback) {
|
|
2397
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2398
|
+
const candidate = usbDevices.find(
|
|
2399
|
+
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2400
|
+
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2401
|
+
if (candidate) return [candidate];
|
|
2402
|
+
}
|
|
2403
|
+
return this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal);
|
|
2404
|
+
},
|
|
2405
|
+
request: {
|
|
2406
|
+
devices,
|
|
2407
|
+
bindingSessionId,
|
|
2408
|
+
context: {
|
|
2409
|
+
kind: "bind-connection",
|
|
2410
|
+
transport: "ble",
|
|
2411
|
+
reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
|
|
2412
|
+
},
|
|
2413
|
+
extra: context?.extra,
|
|
2414
|
+
operationId: preserveOperationId
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
if (device.connectionType === "usb") {
|
|
2418
|
+
this._activeConnectionType = "usb";
|
|
2419
|
+
if (context) context.selectedConnection = void 0;
|
|
2420
|
+
this._bindingSelectionRequestId = requestId;
|
|
2421
|
+
this._finishBleBinding("cancelled");
|
|
2422
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2423
|
+
}
|
|
2424
|
+
this._bindingSelectionRequestId = requestId;
|
|
2425
|
+
const connected = await this._connectDeviceOrThrow(
|
|
2426
|
+
device.connectId,
|
|
2427
|
+
preserveOperationId,
|
|
2428
|
+
signal
|
|
2429
|
+
);
|
|
2430
|
+
if (context) context.selectedConnection = { connectId: connected, requestId };
|
|
2431
|
+
await this._saveSelectedBleBinding(
|
|
2432
|
+
connected,
|
|
2433
|
+
requestId,
|
|
2434
|
+
context,
|
|
2435
|
+
preserveOperationId,
|
|
2436
|
+
signal
|
|
2437
|
+
);
|
|
2438
|
+
return connected;
|
|
2439
|
+
}
|
|
2440
|
+
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2441
|
+
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2442
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2443
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2444
|
+
message: "Select a Ledger device before continuing"
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
const requestId = this._uiRegistry.createRequestId();
|
|
2448
|
+
const operationId = this._activeOperationId();
|
|
2449
|
+
const waitPromise = this._uiRegistry.wait(
|
|
2450
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2451
|
+
{ requestId, operationId }
|
|
2452
|
+
);
|
|
2453
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2454
|
+
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2455
|
+
payload: {
|
|
2456
|
+
devices,
|
|
2457
|
+
requestId,
|
|
2458
|
+
operationId,
|
|
2459
|
+
context: requiresBleSelection ? {
|
|
2460
|
+
kind: "bind-connection",
|
|
2461
|
+
transport: "ble",
|
|
2462
|
+
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2463
|
+
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2464
|
+
extra: context?.extra
|
|
2465
|
+
}
|
|
2466
|
+
});
|
|
2467
|
+
const { sdkConnectId } = await this._abortable(signal, waitPromise);
|
|
2468
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2469
|
+
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2470
|
+
if (!selected) {
|
|
2471
|
+
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2472
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
if (context && requiresBleSelection)
|
|
2476
|
+
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2477
|
+
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1757
2478
|
}
|
|
1758
2479
|
if (devices.length !== 1) {
|
|
1759
2480
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
1760
2481
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1761
2482
|
});
|
|
1762
2483
|
}
|
|
1763
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2484
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1764
2485
|
}
|
|
1765
|
-
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1766
|
-
const result = await this.
|
|
2486
|
+
async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
|
|
2487
|
+
const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
|
|
1767
2488
|
if (!result.success) {
|
|
1768
2489
|
const payload = result.payload;
|
|
1769
2490
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -1774,7 +2495,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1774
2495
|
}
|
|
1775
2496
|
throw rethrow;
|
|
1776
2497
|
}
|
|
1777
|
-
return
|
|
2498
|
+
return result.payload;
|
|
1778
2499
|
}
|
|
1779
2500
|
/**
|
|
1780
2501
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -1804,33 +2525,133 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1804
2525
|
* lives in one place.
|
|
1805
2526
|
*/
|
|
1806
2527
|
async _callConnector(sessionId, method, params, signal) {
|
|
1807
|
-
|
|
2528
|
+
this._assertConnectorReady(method);
|
|
2529
|
+
if (signal?.aborted) throw this._abortReason(signal);
|
|
2530
|
+
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2531
|
+
let promise;
|
|
2532
|
+
try {
|
|
2533
|
+
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2534
|
+
} catch (error) {
|
|
2535
|
+
releaseOperation();
|
|
2536
|
+
throw error;
|
|
2537
|
+
}
|
|
1808
2538
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
1809
2539
|
return this._unwrapConnectorResult(result);
|
|
1810
2540
|
}
|
|
2541
|
+
_assertConnectorReady(method) {
|
|
2542
|
+
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2543
|
+
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
_runConnectorTeardown(task) {
|
|
2547
|
+
const previous = this._connectorTeardownTail;
|
|
2548
|
+
let releaseTail = () => void 0;
|
|
2549
|
+
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2550
|
+
releaseTail = resolve;
|
|
2551
|
+
});
|
|
2552
|
+
this._pendingConnectorTeardowns += 1;
|
|
2553
|
+
return (async () => {
|
|
2554
|
+
try {
|
|
2555
|
+
await previous;
|
|
2556
|
+
await this._waitForConnectorOperationsToDrain();
|
|
2557
|
+
await task();
|
|
2558
|
+
} finally {
|
|
2559
|
+
this._pendingConnectorTeardowns -= 1;
|
|
2560
|
+
releaseTail();
|
|
2561
|
+
}
|
|
2562
|
+
})();
|
|
2563
|
+
}
|
|
2564
|
+
_waitForConnectorOperationsToDrain() {
|
|
2565
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2566
|
+
return Promise.resolve();
|
|
2567
|
+
}
|
|
2568
|
+
return new Promise((resolve) => {
|
|
2569
|
+
this._connectorIdleWaiters.add(resolve);
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
_retainConnectorOperation(key) {
|
|
2573
|
+
this._unsettledConnectorOperations.set(
|
|
2574
|
+
key,
|
|
2575
|
+
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2576
|
+
);
|
|
2577
|
+
let released = false;
|
|
2578
|
+
return () => {
|
|
2579
|
+
if (released) return;
|
|
2580
|
+
released = true;
|
|
2581
|
+
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2582
|
+
if (remaining > 0) {
|
|
2583
|
+
this._unsettledConnectorOperations.set(key, remaining);
|
|
2584
|
+
} else {
|
|
2585
|
+
this._unsettledConnectorOperations.delete(key);
|
|
2586
|
+
}
|
|
2587
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2588
|
+
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2589
|
+
this._connectorIdleWaiters.clear();
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
1811
2593
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
1812
|
-
|
|
1813
|
-
|
|
2594
|
+
const positionalOperationId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) ? connectId : void 0;
|
|
2595
|
+
if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
|
|
2596
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2597
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
2598
|
+
message: "Conflicting Ledger operation ids",
|
|
2599
|
+
params: {
|
|
2600
|
+
positionalOperationId,
|
|
2601
|
+
commonOperationId: commonParams.operationId
|
|
2602
|
+
}
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2606
|
+
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2607
|
+
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2608
|
+
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2609
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
2610
|
+
method,
|
|
2611
|
+
connectId: effectiveConnectId || "(empty)",
|
|
2612
|
+
params
|
|
2613
|
+
});
|
|
2614
|
+
const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
|
|
1814
2615
|
try {
|
|
1815
2616
|
const result = await this._jobQueue.enqueue(
|
|
1816
2617
|
queueKey,
|
|
1817
|
-
async (signal) =>
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2618
|
+
async (signal) => {
|
|
2619
|
+
if (operationId) this._activeOperationJobs.add(operationId);
|
|
2620
|
+
try {
|
|
2621
|
+
return await this._runConnectorCall(
|
|
2622
|
+
effectiveConnectId,
|
|
2623
|
+
method,
|
|
2624
|
+
params,
|
|
2625
|
+
signal,
|
|
2626
|
+
fingerprint,
|
|
2627
|
+
permissionDeviceId,
|
|
2628
|
+
commonParams,
|
|
2629
|
+
installContext ?? {},
|
|
2630
|
+
operationId
|
|
2631
|
+
);
|
|
2632
|
+
} catch (error) {
|
|
2633
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
2634
|
+
throw error;
|
|
2635
|
+
} finally {
|
|
2636
|
+
if (operationId) {
|
|
2637
|
+
this._activeOperationJobs.delete(operationId);
|
|
2638
|
+
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2639
|
+
await this._releaseLostOperationConnection(operationId);
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
},
|
|
1827
2644
|
{
|
|
1828
2645
|
label: method,
|
|
1829
2646
|
rejectIfBusy: true,
|
|
1830
2647
|
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
1831
2648
|
}
|
|
1832
2649
|
);
|
|
1833
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
2650
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
2651
|
+
method,
|
|
2652
|
+
success: true,
|
|
2653
|
+
payload: redactResultForLog(result)
|
|
2654
|
+
});
|
|
1834
2655
|
return result;
|
|
1835
2656
|
} catch (err) {
|
|
1836
2657
|
const e = err;
|
|
@@ -1844,15 +2665,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1844
2665
|
}
|
|
1845
2666
|
});
|
|
1846
2667
|
throw err;
|
|
2668
|
+
} finally {
|
|
2669
|
+
releaseOperationRetention?.();
|
|
1847
2670
|
}
|
|
1848
2671
|
}
|
|
2672
|
+
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2673
|
+
_abortReason(signal) {
|
|
2674
|
+
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2675
|
+
}
|
|
1849
2676
|
/**
|
|
1850
2677
|
* Race a promise against an abort signal. On abort, rejects with
|
|
1851
2678
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
1852
2679
|
*/
|
|
1853
2680
|
_abortable(signal, promise) {
|
|
1854
|
-
const getAbortReason = () =>
|
|
2681
|
+
const getAbortReason = () => this._abortReason(signal);
|
|
1855
2682
|
if (signal.aborted) {
|
|
2683
|
+
void promise.catch(() => void 0);
|
|
1856
2684
|
return Promise.reject(getAbortReason());
|
|
1857
2685
|
}
|
|
1858
2686
|
return new Promise((resolve, reject) => {
|
|
@@ -1879,13 +2707,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1879
2707
|
}
|
|
1880
2708
|
}
|
|
1881
2709
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
1882
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2710
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
|
|
1883
2711
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2712
|
+
if (!operationId) {
|
|
2713
|
+
await this._ensureDevicePermission(
|
|
2714
|
+
connectId,
|
|
2715
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
2716
|
+
signal
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
1889
2719
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1890
2720
|
let effectiveParams = params;
|
|
1891
2721
|
if (method === "btcGetPublicKey") {
|
|
@@ -1899,19 +2729,49 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1899
2729
|
effectiveParams = gatedParams;
|
|
1900
2730
|
}
|
|
1901
2731
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
1902
|
-
|
|
1903
|
-
|
|
2732
|
+
let businessCallStarted = false;
|
|
2733
|
+
const selectedBleTarget = connectId ? this._selectedBleReconnectTargets.get(connectId) : void 0;
|
|
2734
|
+
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2735
|
+
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2736
|
+
(connection) => connection.transport === knownTransport
|
|
2737
|
+
);
|
|
2738
|
+
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2739
|
+
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && selectedBleTarget?.chain === fingerprint.chain && selectedBleTarget.fingerprint === fingerprint.deviceId ? selectedBleTarget.connectId : inputConnectId;
|
|
2740
|
+
const connectionAttempt = { ...commonParams };
|
|
2741
|
+
const bundleConnection = installContext?.connection;
|
|
2742
|
+
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2743
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2744
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2745
|
+
message: "Ledger all-network connection ended"
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
const resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2749
|
+
preferredConnectId,
|
|
1904
2750
|
signal,
|
|
1905
|
-
allowUsbEphemeralFallback
|
|
2751
|
+
allowUsbEphemeralFallback,
|
|
2752
|
+
void 0,
|
|
2753
|
+
connectionAttempt
|
|
1906
2754
|
);
|
|
1907
2755
|
const sessionId = this._sessions.get(resolvedConnectId);
|
|
2756
|
+
if (sessionId && installContext && !installContext.connection) {
|
|
2757
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2758
|
+
}
|
|
1908
2759
|
if (!sessionId) {
|
|
2760
|
+
if (operationId) {
|
|
2761
|
+
this._operations.end(operationId, "disconnect");
|
|
2762
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2763
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.OperationEnded,
|
|
2764
|
+
message: "Ledger operation connection is no longer active",
|
|
2765
|
+
params: { operationId, reason: "disconnect" }
|
|
2766
|
+
});
|
|
2767
|
+
}
|
|
1909
2768
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
1910
2769
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
1911
2770
|
});
|
|
1912
2771
|
}
|
|
1913
2772
|
try {
|
|
1914
|
-
|
|
2773
|
+
const isBleSession = this._sessionTransports.get(sessionId) === "ble";
|
|
2774
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId && !isBleSession) {
|
|
1915
2775
|
const fp = await this._abortable(
|
|
1916
2776
|
signal,
|
|
1917
2777
|
this._verifyDeviceFingerprintWithSession(
|
|
@@ -1926,9 +2786,33 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1926
2786
|
});
|
|
1927
2787
|
}
|
|
1928
2788
|
}
|
|
2789
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId && !operationId && isBleSession && connectionAttempt.selectedConnection?.connectId === resolvedConnectId) {
|
|
2790
|
+
this._selectedBleReconnectTargets.set(connectId, {
|
|
2791
|
+
connectId: resolvedConnectId,
|
|
2792
|
+
chain: fingerprint.chain,
|
|
2793
|
+
fingerprint: fingerprint.deviceId
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
businessCallStarted = true;
|
|
1929
2797
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
1930
2798
|
} catch (err) {
|
|
1931
2799
|
if (signal.aborted) throw err;
|
|
2800
|
+
if (isLostConnectionError(err)) {
|
|
2801
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2802
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2803
|
+
else {
|
|
2804
|
+
this._sessions.delete(resolvedConnectId);
|
|
2805
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2806
|
+
}
|
|
2807
|
+
const ambiguous = businessCallStarted && !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method);
|
|
2808
|
+
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2809
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2810
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(err).code,
|
|
2811
|
+
message: "Ledger operation connection was lost; start a new operation",
|
|
2812
|
+
params: ambiguous ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, { operationId }) : interactionParams,
|
|
2813
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
1932
2816
|
const errObj = err;
|
|
1933
2817
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1934
2818
|
message: errObj?.message,
|
|
@@ -1940,6 +2824,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1940
2824
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1941
2825
|
isStuckApp: isStuckAppStateError(err)
|
|
1942
2826
|
});
|
|
2827
|
+
const assertSessionCurrent = (message) => {
|
|
2828
|
+
if (this._sessions.get(resolvedConnectId) === sessionId) return;
|
|
2829
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2830
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2831
|
+
message
|
|
2832
|
+
});
|
|
2833
|
+
};
|
|
2834
|
+
if ((isDeviceLockedError(err) || errObj?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2835
|
+
await this._waitForDeviceConnect(signal);
|
|
2836
|
+
assertSessionCurrent("Ledger connection ended while waiting for unlock");
|
|
2837
|
+
return this._runConnectorCall(
|
|
2838
|
+
resolvedConnectId,
|
|
2839
|
+
method,
|
|
2840
|
+
effectiveParams,
|
|
2841
|
+
signal,
|
|
2842
|
+
fingerprint,
|
|
2843
|
+
permissionDeviceId,
|
|
2844
|
+
commonParams,
|
|
2845
|
+
installContext,
|
|
2846
|
+
operationId,
|
|
2847
|
+
lockedRetryBudget - 1
|
|
2848
|
+
);
|
|
2849
|
+
}
|
|
2850
|
+
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2851
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2852
|
+
assertSessionCurrent("Ledger connection ended during the app transition");
|
|
2853
|
+
try {
|
|
2854
|
+
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2855
|
+
} catch (retryErr) {
|
|
2856
|
+
if (isStuckAppStateError(retryErr)) throw err;
|
|
2857
|
+
if (isLostConnectionError(retryErr)) {
|
|
2858
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2859
|
+
else {
|
|
2860
|
+
this._sessions.delete(resolvedConnectId);
|
|
2861
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2862
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2863
|
+
}
|
|
2864
|
+
const ambiguous = !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method);
|
|
2865
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2866
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(retryErr).code,
|
|
2867
|
+
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2868
|
+
params: ambiguous ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, { operationId, reason: "disconnect" }) : { operationId, reason: "disconnect" },
|
|
2869
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
throw retryErr;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
if (!operationId && errObj?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch) {
|
|
2876
|
+
this._sessions.delete(resolvedConnectId);
|
|
2877
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2878
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2879
|
+
throw err;
|
|
2880
|
+
}
|
|
1943
2881
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
1944
2882
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === import_hwk_adapter_core3.HardwareErrorCode.AppNotInstalled;
|
|
1945
2883
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -1995,157 +2933,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1995
2933
|
fingerprint,
|
|
1996
2934
|
permissionDeviceId,
|
|
1997
2935
|
commonParams,
|
|
1998
|
-
installContext
|
|
1999
|
-
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
if (isStuckAppStateError(err)) {
|
|
2003
|
-
try {
|
|
2004
|
-
this._sessions.delete(resolvedConnectId);
|
|
2005
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2006
|
-
this.connector.reset?.();
|
|
2007
|
-
} catch {
|
|
2008
|
-
}
|
|
2009
|
-
debugLog(
|
|
2010
|
-
"[LedgerAdapter] stuck-app retry: method=",
|
|
2011
|
-
method,
|
|
2012
|
-
"delayMs=",
|
|
2013
|
-
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
|
|
2014
|
-
"_tag=",
|
|
2015
|
-
err?._tag
|
|
2016
|
-
);
|
|
2017
|
-
try {
|
|
2018
|
-
const retryResult = await this._retryAfterStuckApp(
|
|
2019
|
-
resolvedConnectId,
|
|
2020
|
-
method,
|
|
2021
|
-
effectiveParams,
|
|
2022
|
-
signal,
|
|
2023
|
-
err,
|
|
2024
|
-
fingerprint
|
|
2025
|
-
);
|
|
2026
|
-
debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
|
|
2027
|
-
return retryResult;
|
|
2028
|
-
} catch (retryErr) {
|
|
2029
|
-
if (signal.aborted) throw retryErr;
|
|
2030
|
-
if (isStuckAppStateError(retryErr)) {
|
|
2031
|
-
debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
|
|
2032
|
-
throw err;
|
|
2033
|
-
}
|
|
2034
|
-
debugLog(
|
|
2035
|
-
"[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
|
|
2036
|
-
method,
|
|
2037
|
-
"retryErrTag=",
|
|
2038
|
-
retryErr?._tag
|
|
2936
|
+
installContext,
|
|
2937
|
+
operationId
|
|
2039
2938
|
);
|
|
2040
|
-
throw retryErr;
|
|
2041
2939
|
}
|
|
2042
2940
|
}
|
|
2043
|
-
if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
|
|
2044
|
-
let lastErr = err;
|
|
2045
|
-
for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
|
|
2046
|
-
if (signal.aborted) throw lastErr;
|
|
2047
|
-
try {
|
|
2048
|
-
this._sessions.delete(resolvedConnectId);
|
|
2049
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2050
|
-
if (isDeviceDisconnectedError(lastErr)) {
|
|
2051
|
-
try {
|
|
2052
|
-
this.connector.reset?.();
|
|
2053
|
-
} catch {
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
const reConnectId = await this.ensureConnected(
|
|
2057
|
-
resolvedConnectId,
|
|
2058
|
-
signal,
|
|
2059
|
-
allowUsbEphemeralFallback
|
|
2060
|
-
);
|
|
2061
|
-
const reSessionId = this._sessions.get(reConnectId);
|
|
2062
|
-
if (!reSessionId) throw lastErr;
|
|
2063
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2064
|
-
const fp = await this._abortable(
|
|
2065
|
-
signal,
|
|
2066
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2067
|
-
reSessionId,
|
|
2068
|
-
fingerprint.deviceId,
|
|
2069
|
-
fingerprint.chain
|
|
2070
|
-
)
|
|
2071
|
-
);
|
|
2072
|
-
if (!fp.success) {
|
|
2073
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2074
|
-
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
2075
|
-
});
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
return await this._callConnector(reSessionId, method, effectiveParams, signal);
|
|
2079
|
-
} catch (retryErr) {
|
|
2080
|
-
if (signal.aborted) throw retryErr;
|
|
2081
|
-
lastErr = retryErr;
|
|
2082
|
-
const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
|
|
2083
|
-
if (!canRetry) {
|
|
2084
|
-
throw retryErr;
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
throw lastErr;
|
|
2089
|
-
}
|
|
2090
|
-
if (isTimeoutError(err)) {
|
|
2091
|
-
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
2092
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2093
|
-
return this._retryWithFreshConnection(
|
|
2094
|
-
resolvedConnectId,
|
|
2095
|
-
method,
|
|
2096
|
-
effectiveParams,
|
|
2097
|
-
signal,
|
|
2098
|
-
err,
|
|
2099
|
-
fingerprint
|
|
2100
|
-
);
|
|
2101
|
-
}
|
|
2102
|
-
if (isConnectionLevelError(err)) {
|
|
2103
|
-
debugLog("[LedgerAdapter] connection-level fail-closed reset");
|
|
2104
|
-
this._sessions.delete(resolvedConnectId);
|
|
2105
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2106
|
-
this.connector.reset();
|
|
2107
|
-
const codeNum = err?.code;
|
|
2108
|
-
throw Object.assign(err, {
|
|
2109
|
-
code: codeNum ?? import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected
|
|
2110
|
-
});
|
|
2111
|
-
}
|
|
2112
2941
|
throw err;
|
|
2113
2942
|
}
|
|
2114
2943
|
}
|
|
2115
|
-
/**
|
|
2116
|
-
* Stuck-app recovery: pause for the device's UI transition, then retry once.
|
|
2117
|
-
*
|
|
2118
|
-
* Caller has already cleared the session + reset connector. We wait so Stax
|
|
2119
|
-
* finishes its post-CloseApp animation, then go through ensureConnected +
|
|
2120
|
-
* fingerprint check + call exactly once. Caller decides what to do on a
|
|
2121
|
-
* second stuck-app hit.
|
|
2122
|
-
*/
|
|
2123
|
-
async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2124
|
-
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2125
|
-
const retryConnectId = await this.ensureConnected(
|
|
2126
|
-
resolvedConnectId,
|
|
2127
|
-
signal,
|
|
2128
|
-
!!fingerprint?.deviceId && !fingerprint.skipFingerprint
|
|
2129
|
-
);
|
|
2130
|
-
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2131
|
-
if (!retrySessionId) throw originalErr;
|
|
2132
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2133
|
-
const fp = await this._abortable(
|
|
2134
|
-
signal,
|
|
2135
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2136
|
-
retrySessionId,
|
|
2137
|
-
fingerprint.deviceId,
|
|
2138
|
-
fingerprint.chain
|
|
2139
|
-
)
|
|
2140
|
-
);
|
|
2141
|
-
if (!fp.success) {
|
|
2142
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2143
|
-
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
2144
|
-
});
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
return this._callConnector(retrySessionId, method, params, signal);
|
|
2148
|
-
}
|
|
2149
2944
|
_sleepAbortable(ms, signal) {
|
|
2150
2945
|
return new Promise((resolve, reject) => {
|
|
2151
2946
|
if (signal.aborted) {
|
|
@@ -2164,111 +2959,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2164
2959
|
});
|
|
2165
2960
|
}
|
|
2166
2961
|
/**
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
2169
|
-
* Timeout recovery starts with a full connector reset. After an APDU
|
|
2170
|
-
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
2171
|
-
* on the same DMK can poison the next chain switch.
|
|
2172
|
-
*/
|
|
2173
|
-
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2174
|
-
this.connector.reset();
|
|
2175
|
-
this._sessions.clear();
|
|
2176
|
-
this._discoveredDevices.clear();
|
|
2177
|
-
this._connectingPromise = null;
|
|
2178
|
-
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2179
|
-
const retryConnectId = await this.ensureConnected(
|
|
2180
|
-
targetConnectId,
|
|
2181
|
-
signal,
|
|
2182
|
-
allowUsbEphemeralFallback
|
|
2183
|
-
);
|
|
2184
|
-
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2185
|
-
if (!retrySessionId) {
|
|
2186
|
-
throw originalErr;
|
|
2187
|
-
}
|
|
2188
|
-
try {
|
|
2189
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2190
|
-
const fp = await this._abortable(
|
|
2191
|
-
signal,
|
|
2192
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2193
|
-
retrySessionId,
|
|
2194
|
-
fingerprint.deviceId,
|
|
2195
|
-
fingerprint.chain
|
|
2196
|
-
)
|
|
2197
|
-
);
|
|
2198
|
-
if (!fp.success) {
|
|
2199
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2200
|
-
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
2201
|
-
});
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
return await this._callConnector(retrySessionId, method, params, signal);
|
|
2205
|
-
} catch (retryErr) {
|
|
2206
|
-
if (signal.aborted) throw retryErr;
|
|
2207
|
-
this.connector.reset();
|
|
2208
|
-
this._sessions.clear();
|
|
2209
|
-
this._discoveredDevices.clear();
|
|
2210
|
-
this._connectingPromise = null;
|
|
2211
|
-
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
2212
|
-
throw retryErr;
|
|
2213
|
-
}
|
|
2214
|
-
debugLog(
|
|
2215
|
-
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
2216
|
-
);
|
|
2217
|
-
this.connector.reset();
|
|
2218
|
-
this._sessions.clear();
|
|
2219
|
-
this._discoveredDevices.clear();
|
|
2220
|
-
this._connectingPromise = null;
|
|
2221
|
-
const finalConnectId = await this.ensureConnected(
|
|
2222
|
-
targetConnectId,
|
|
2223
|
-
signal,
|
|
2224
|
-
allowUsbEphemeralFallback
|
|
2225
|
-
);
|
|
2226
|
-
const finalSessionId = this._sessions.get(finalConnectId);
|
|
2227
|
-
if (!finalSessionId) {
|
|
2228
|
-
throw originalErr;
|
|
2229
|
-
}
|
|
2230
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2231
|
-
const fp = await this._abortable(
|
|
2232
|
-
signal,
|
|
2233
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2234
|
-
finalSessionId,
|
|
2235
|
-
fingerprint.deviceId,
|
|
2236
|
-
fingerprint.chain
|
|
2237
|
-
)
|
|
2238
|
-
);
|
|
2239
|
-
if (!fp.success) {
|
|
2240
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2241
|
-
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
2242
|
-
});
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
return this._callConnector(finalSessionId, method, params, signal);
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
/**
|
|
2249
|
-
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
2250
|
-
*
|
|
2251
|
-
* Emits `REQUEST_DEVICE_PERMISSION` and awaits the consumer's
|
|
2252
|
-
* `RECEIVE_DEVICE_PERMISSION` reply (60s budget covers "probe → system
|
|
2253
|
-
* prompt → user tap" plus a generous margin). If the consumer never wires
|
|
2254
|
-
* a handler or never replies, the wait times out and the operation fails
|
|
2255
|
-
* fast so scanners/callers don't hang silently.
|
|
2256
|
-
*
|
|
2257
|
-
* - No connectId (searchDevices): environment-level permission
|
|
2258
|
-
* - With connectId (business methods): device-level permission
|
|
2962
|
+
* Ensure OS-level Bluetooth/USB permission; times out after 60s if the host never
|
|
2963
|
+
* replies. Without connectId the request is environment-level, else device-level.
|
|
2259
2964
|
*/
|
|
2260
2965
|
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
2261
2966
|
if (signal?.aborted) {
|
|
2262
2967
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2263
2968
|
}
|
|
2264
2969
|
const transportType = this.activeTransport ?? "hid";
|
|
2970
|
+
const operationId = this._activeOperationId();
|
|
2265
2971
|
const waitPromise = this._uiRegistry.wait(
|
|
2266
2972
|
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2267
|
-
{ timeoutMs: 6e4 }
|
|
2973
|
+
{ timeoutMs: 6e4, operationId }
|
|
2268
2974
|
);
|
|
2269
2975
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
2270
2976
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2271
|
-
payload: { transportType, connectId, deviceId }
|
|
2977
|
+
payload: { transportType, connectId, deviceId, operationId }
|
|
2272
2978
|
});
|
|
2273
2979
|
let response;
|
|
2274
2980
|
const onAbort = () => {
|
|
@@ -2314,7 +3020,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2314
3020
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
2315
3021
|
const e = err;
|
|
2316
3022
|
const params = e.code === import_hwk_adapter_core3.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
2317
|
-
return ledgerFailure(
|
|
3023
|
+
return ledgerFailure(
|
|
3024
|
+
e.code,
|
|
3025
|
+
e.message ?? "Unknown error",
|
|
3026
|
+
e.appName,
|
|
3027
|
+
tag,
|
|
3028
|
+
params,
|
|
3029
|
+
void 0,
|
|
3030
|
+
(0, import_hwk_adapter_core3.isHwkRecoveryHint)(e.recovery) ? e.recovery : void 0
|
|
3031
|
+
);
|
|
2318
3032
|
}
|
|
2319
3033
|
const mapped = mapLedgerError(err);
|
|
2320
3034
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -2341,7 +3055,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2341
3055
|
deviceId: device.deviceId,
|
|
2342
3056
|
connectId: device.connectId,
|
|
2343
3057
|
label: device.name,
|
|
2344
|
-
connectionType: this.connector.connectionType,
|
|
3058
|
+
connectionType: device.connectionType ?? this.connector.connectionType,
|
|
2345
3059
|
rssi: device.rssi,
|
|
2346
3060
|
isConnectable: device.isConnectable,
|
|
2347
3061
|
serialNumber: device.serialNumber,
|
|
@@ -2366,7 +3080,7 @@ _LedgerAdapter.APP_INSTALL_PROGRESS_MIN_DELTA = 0.05;
|
|
|
2366
3080
|
var LedgerAdapter = _LedgerAdapter;
|
|
2367
3081
|
|
|
2368
3082
|
// src/connector/LedgerConnectorBase.ts
|
|
2369
|
-
var
|
|
3083
|
+
var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
|
|
2370
3084
|
|
|
2371
3085
|
// src/device/LedgerDeviceManager.ts
|
|
2372
3086
|
var LedgerDeviceManager = class {
|
|
@@ -2902,7 +3616,7 @@ var SignerManager = class _SignerManager {
|
|
|
2902
3616
|
return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
|
|
2903
3617
|
}
|
|
2904
3618
|
static _createContextModule() {
|
|
2905
|
-
const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3619
|
+
const contextModule = new import_context_module.ContextModuleBuilder({}).setChain(import_context_module.ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
|
|
2906
3620
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2907
3621
|
}
|
|
2908
3622
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -2940,6 +3654,27 @@ function collapseSignerInteraction(interaction) {
|
|
|
2940
3654
|
return import_hwk_adapter_core5.EConnectorInteraction.ConfirmOnDevice;
|
|
2941
3655
|
}
|
|
2942
3656
|
}
|
|
3657
|
+
function wireSignerToSession(ctx, sessionId, chain, signer) {
|
|
3658
|
+
signer.onInteraction = (interaction) => {
|
|
3659
|
+
debugLog(`[LedgerConnector] ${chain}.onInteraction:`, interaction);
|
|
3660
|
+
ctx.emit("ui-event", {
|
|
3661
|
+
type: collapseSignerInteraction(interaction),
|
|
3662
|
+
payload: { sessionId }
|
|
3663
|
+
});
|
|
3664
|
+
};
|
|
3665
|
+
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
3666
|
+
return signer;
|
|
3667
|
+
}
|
|
3668
|
+
async function runSignerCall(ctx, sessionId, call) {
|
|
3669
|
+
try {
|
|
3670
|
+
return await call();
|
|
3671
|
+
} catch (err) {
|
|
3672
|
+
ctx.invalidateSession(sessionId);
|
|
3673
|
+
throw ctx.wrapError(err);
|
|
3674
|
+
} finally {
|
|
3675
|
+
ctx.clearCanceller(sessionId);
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
2943
3678
|
|
|
2944
3679
|
// src/connector/chains/evm.ts
|
|
2945
3680
|
async function evmGetAddress(ctx, sessionId, params) {
|
|
@@ -3373,6 +4108,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
3373
4108
|
|
|
3374
4109
|
// src/connector/chains/sol.ts
|
|
3375
4110
|
var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
|
|
4111
|
+
var import_bs58 = __toESM(require("bs58"));
|
|
3376
4112
|
|
|
3377
4113
|
// src/signer/SignerSol.ts
|
|
3378
4114
|
var SignerSol = class {
|
|
@@ -3456,8 +4192,21 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3456
4192
|
const path = normalizePath(params.path);
|
|
3457
4193
|
const messageBytes = (0, import_hwk_adapter_core9.hexToBytes)(params.message);
|
|
3458
4194
|
try {
|
|
4195
|
+
if (params.messageVersion === 1) {
|
|
4196
|
+
const preparedMessage = (0, import_hwk_adapter_core9.prepareSolanaOffchainMessageV1)({
|
|
4197
|
+
message: messageBytes,
|
|
4198
|
+
requiredSigners: params.requiredSigners
|
|
4199
|
+
});
|
|
4200
|
+
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4201
|
+
"@ledgerhq/device-signer-kit-solana"
|
|
4202
|
+
);
|
|
4203
|
+
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4204
|
+
version: SignMessageVersion.Raw
|
|
4205
|
+
});
|
|
4206
|
+
return { signature: decodeBase58Signature(result2.signature) };
|
|
4207
|
+
}
|
|
3459
4208
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
3460
|
-
return { signature: result.signature };
|
|
4209
|
+
return { signature: decodeBase58EnvelopeSignature(result.signature) };
|
|
3461
4210
|
} catch (err) {
|
|
3462
4211
|
ctx.invalidateSession(sessionId);
|
|
3463
4212
|
throw ctx.wrapError(err);
|
|
@@ -3465,11 +4214,27 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3465
4214
|
ctx.clearCanceller(sessionId);
|
|
3466
4215
|
}
|
|
3467
4216
|
}
|
|
4217
|
+
function decodeBase58Signature(signature) {
|
|
4218
|
+
const bytes = import_bs58.default.decode(signature);
|
|
4219
|
+
if (bytes.length !== 64) {
|
|
4220
|
+
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4221
|
+
}
|
|
4222
|
+
return (0, import_hwk_adapter_core9.bytesToHex)(bytes);
|
|
4223
|
+
}
|
|
4224
|
+
function decodeBase58EnvelopeSignature(envelope) {
|
|
4225
|
+
const bytes = import_bs58.default.decode(envelope);
|
|
4226
|
+
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4227
|
+
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4228
|
+
}
|
|
4229
|
+
return (0, import_hwk_adapter_core9.bytesToHex)(bytes.subarray(1, 65));
|
|
4230
|
+
}
|
|
3468
4231
|
async function _createSolSigner(ctx, sessionId) {
|
|
3469
4232
|
const dmk = await ctx.getOrCreateDmk();
|
|
3470
|
-
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit(
|
|
4233
|
+
const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
|
|
4234
|
+
"@ledgerhq/context-module"
|
|
4235
|
+
);
|
|
3471
4236
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
3472
|
-
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4237
|
+
const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
|
|
3473
4238
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
3474
4239
|
const signer = new SignerSol(sdkSigner);
|
|
3475
4240
|
signer.onInteraction = (interaction) => {
|
|
@@ -3485,360 +4250,151 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
3485
4250
|
return signer;
|
|
3486
4251
|
}
|
|
3487
4252
|
|
|
3488
|
-
// src/connector/chains/
|
|
3489
|
-
var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
|
|
3490
|
-
var import_hw_app_trx = __toESM(require("@ledgerhq/hw-app-trx"));
|
|
3491
|
-
|
|
3492
|
-
// src/connector/chains/legacyChainCall.ts
|
|
3493
|
-
var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
|
|
3494
|
-
|
|
3495
|
-
// src/app/AppManager.ts
|
|
3496
|
-
var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
|
|
4253
|
+
// src/connector/chains/zcash.ts
|
|
3497
4254
|
var import_hwk_adapter_core10 = require("@onekeyfe/hwk-adapter-core");
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
ADA: "Cardano",
|
|
3505
|
-
DOT: "Polkadot",
|
|
3506
|
-
ATOM: "Cosmos"
|
|
3507
|
-
};
|
|
3508
|
-
var DASHBOARD_APP_NAME = "BOLOS";
|
|
3509
|
-
var AppManager = class {
|
|
3510
|
-
constructor(dmk, options) {
|
|
3511
|
-
this._dmk = dmk;
|
|
3512
|
-
this._waitMs = options?.waitMs ?? 1e3;
|
|
3513
|
-
this._maxRetries = options?.maxRetries ?? 10;
|
|
3514
|
-
}
|
|
3515
|
-
/**
|
|
3516
|
-
* Return the Ledger app name for a given chain ticker,
|
|
3517
|
-
* or undefined if the chain is not supported.
|
|
3518
|
-
*/
|
|
3519
|
-
static getAppName(chain) {
|
|
3520
|
-
return APP_NAME_MAP[chain];
|
|
3521
|
-
}
|
|
3522
|
-
/**
|
|
3523
|
-
* Ensure the target app is open on the device identified by `sessionId`.
|
|
3524
|
-
*
|
|
3525
|
-
* Flow:
|
|
3526
|
-
* 1. Check the currently running app.
|
|
3527
|
-
* 2. If it is already the target, return immediately.
|
|
3528
|
-
* 3. If a different app is running (not dashboard), close it first.
|
|
3529
|
-
* 4. Open the target app.
|
|
3530
|
-
* 5. Poll until the device confirms the target app is running.
|
|
3531
|
-
*/
|
|
3532
|
-
/**
|
|
3533
|
-
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
3534
|
-
* device is about to display "Open <app>" on screen and wait for the
|
|
3535
|
-
* user's button press. UI consumers should show their "open app" prompt
|
|
3536
|
-
* in response. NOT called when the target app is already open (no user
|
|
3537
|
-
* interaction needed in that case).
|
|
3538
|
-
*
|
|
3539
|
-
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
3540
|
-
* has physically confirmed on the device, so anything that runs AFTER
|
|
3541
|
-
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
3542
|
-
* Hence the callback must fire BEFORE that await.
|
|
3543
|
-
*/
|
|
3544
|
-
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
3545
|
-
const currentApp = await this._getCurrentApp(sessionId);
|
|
3546
|
-
if (currentApp === targetAppName) {
|
|
3547
|
-
return;
|
|
3548
|
-
}
|
|
3549
|
-
if (!this._isDashboard(currentApp)) {
|
|
3550
|
-
await this._closeCurrentApp(sessionId);
|
|
3551
|
-
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
3552
|
-
}
|
|
3553
|
-
onConfirmOnDevice?.();
|
|
3554
|
-
await this._openApp(sessionId, targetAppName);
|
|
3555
|
-
await this._waitForApp(sessionId, targetAppName);
|
|
4255
|
+
|
|
4256
|
+
// src/signer/SignerZcash.ts
|
|
4257
|
+
var SignerZcash = class {
|
|
4258
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4259
|
+
constructor(_sdk) {
|
|
4260
|
+
this._sdk = _sdk;
|
|
3556
4261
|
}
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
if ((0, import_device_management_kit2.isSuccessCommandResult)(result)) {
|
|
3566
|
-
debugLog("[AppManager] currentApp:", result.data.name);
|
|
3567
|
-
return result.data.name;
|
|
3568
|
-
}
|
|
3569
|
-
const errResult = result;
|
|
3570
|
-
const dmkErr = errResult.error ?? {};
|
|
3571
|
-
const original = dmkErr.originalError;
|
|
3572
|
-
debugLog(
|
|
3573
|
-
"[AppManager] _getCurrentApp failed sessionId=",
|
|
3574
|
-
sessionId,
|
|
3575
|
-
"tag=",
|
|
3576
|
-
dmkErr._tag,
|
|
3577
|
-
"errorCode=",
|
|
3578
|
-
dmkErr.errorCode,
|
|
3579
|
-
"message=",
|
|
3580
|
-
dmkErr.message,
|
|
3581
|
-
"originalErrorMessage=",
|
|
3582
|
-
original?.message ?? String(original ?? "")
|
|
3583
|
-
);
|
|
3584
|
-
throw Object.assign(
|
|
3585
|
-
new Error(
|
|
3586
|
-
dmkErr.message ?? "Failed to get current app from device"
|
|
3587
|
-
),
|
|
3588
|
-
{
|
|
3589
|
-
_tag: dmkErr._tag,
|
|
3590
|
-
errorCode: dmkErr.errorCode,
|
|
3591
|
-
originalError: original
|
|
3592
|
-
}
|
|
4262
|
+
/** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
|
|
4263
|
+
async getFullViewingKey(derivationPath, options) {
|
|
4264
|
+
const action = this._sdk.getFullViewingKey(derivationPath, options);
|
|
4265
|
+
return deviceActionToPromise(
|
|
4266
|
+
action,
|
|
4267
|
+
this.onInteraction,
|
|
4268
|
+
void 0,
|
|
4269
|
+
this.onRegisterCanceller
|
|
3593
4270
|
);
|
|
3594
4271
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
3604
|
-
debugLog(
|
|
3605
|
-
"[AppManager] openApp failed:",
|
|
3606
|
-
appName,
|
|
3607
|
-
"errorCode:",
|
|
3608
|
-
errorCode,
|
|
3609
|
-
"tag:",
|
|
3610
|
-
dmkErr._tag
|
|
3611
|
-
);
|
|
3612
|
-
let code;
|
|
3613
|
-
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
3614
|
-
code = import_hwk_adapter_core10.HardwareErrorCode.AppNotInstalled;
|
|
3615
|
-
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
3616
|
-
code = import_hwk_adapter_core10.HardwareErrorCode.UserRejected;
|
|
3617
|
-
}
|
|
3618
|
-
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
3619
|
-
_tag: ERROR_TAG.OpenAppCommand,
|
|
3620
|
-
code,
|
|
3621
|
-
errorCode,
|
|
3622
|
-
statusCode: errorCode,
|
|
3623
|
-
appName,
|
|
3624
|
-
originalError: dmkErr
|
|
3625
|
-
});
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
async _closeCurrentApp(sessionId) {
|
|
3629
|
-
debugLog("[AppManager] closeCurrentApp");
|
|
3630
|
-
await this._dmk.sendCommand({
|
|
3631
|
-
sessionId,
|
|
3632
|
-
command: new import_device_management_kit2.CloseAppCommand()
|
|
3633
|
-
});
|
|
3634
|
-
}
|
|
3635
|
-
/**
|
|
3636
|
-
* Poll the device until the expected app is reported as running,
|
|
3637
|
-
* or throw after `_maxRetries` attempts.
|
|
3638
|
-
*/
|
|
3639
|
-
async _waitForApp(sessionId, expectedAppName) {
|
|
3640
|
-
let lastSeen = "";
|
|
3641
|
-
for (let i = 0; i < this._maxRetries; i++) {
|
|
3642
|
-
await this._wait();
|
|
3643
|
-
const current = await this._getCurrentApp(sessionId);
|
|
3644
|
-
lastSeen = current;
|
|
3645
|
-
if (current === expectedAppName) {
|
|
3646
|
-
return;
|
|
3647
|
-
}
|
|
3648
|
-
}
|
|
3649
|
-
debugLog(
|
|
3650
|
-
"[AppManager] waitForApp exhausted: expected=",
|
|
3651
|
-
expectedAppName,
|
|
3652
|
-
"lastSeen=",
|
|
3653
|
-
lastSeen
|
|
3654
|
-
);
|
|
3655
|
-
throw new Error(
|
|
3656
|
-
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
4272
|
+
/** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
|
|
4273
|
+
async getShieldedAddress(derivationPath, options) {
|
|
4274
|
+
const action = this._sdk.getShieldedAddress(derivationPath, options);
|
|
4275
|
+
return deviceActionToPromise(
|
|
4276
|
+
action,
|
|
4277
|
+
this.onInteraction,
|
|
4278
|
+
void 0,
|
|
4279
|
+
this.onRegisterCanceller
|
|
3657
4280
|
);
|
|
3658
4281
|
}
|
|
3659
|
-
_isDashboard(appName) {
|
|
3660
|
-
return appName === DASHBOARD_APP_NAME;
|
|
3661
|
-
}
|
|
3662
|
-
_wait() {
|
|
3663
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3664
|
-
}
|
|
3665
4282
|
};
|
|
3666
4283
|
|
|
3667
|
-
// src/connector/chains/
|
|
3668
|
-
function
|
|
3669
|
-
|
|
4284
|
+
// src/connector/chains/zcash.ts
|
|
4285
|
+
async function zcashGetFullViewingKey(ctx, sessionId, params) {
|
|
4286
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4287
|
+
const path = normalizePath(params.path);
|
|
4288
|
+
const mode = params.mode ?? "ufvk";
|
|
4289
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4290
|
+
const result = await signer.getFullViewingKey(path, { mode });
|
|
4291
|
+
if (result.mode === "ufvk") {
|
|
4292
|
+
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
4293
|
+
}
|
|
4294
|
+
return { path: params.path, mode, orchardFvk: (0, import_hwk_adapter_core10.bytesToHex)(result.fullViewingKey) };
|
|
4295
|
+
});
|
|
3670
4296
|
}
|
|
3671
|
-
async function
|
|
3672
|
-
const
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.ConfirmOpenApp,
|
|
3678
|
-
payload: { sessionId }
|
|
4297
|
+
async function zcashGetShieldedAddress(ctx, sessionId, params) {
|
|
4298
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4299
|
+
const path = normalizePath(params.path);
|
|
4300
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4301
|
+
const result = await signer.getShieldedAddress(path, {
|
|
4302
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
3679
4303
|
});
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
if (openAppPromptShown) {
|
|
3683
|
-
ctx.emit("ui-event", {
|
|
3684
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.InteractionComplete,
|
|
3685
|
-
payload: { sessionId }
|
|
3686
|
-
});
|
|
3687
|
-
openAppPromptShown = false;
|
|
3688
|
-
}
|
|
3689
|
-
};
|
|
3690
|
-
try {
|
|
3691
|
-
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3692
|
-
} catch (err) {
|
|
3693
|
-
debugLog(
|
|
3694
|
-
"[LegacyChainCall] pre-flight ensureAppOpen failed:",
|
|
3695
|
-
appName,
|
|
3696
|
-
err?.message
|
|
3697
|
-
);
|
|
3698
|
-
closeOpenAppUiIfShown();
|
|
3699
|
-
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3700
|
-
}
|
|
3701
|
-
const runOnce = async () => {
|
|
3702
|
-
let confirmEmitted = false;
|
|
3703
|
-
if (needsConfirmation) {
|
|
3704
|
-
ctx.emit("ui-event", {
|
|
3705
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.ConfirmOnDevice,
|
|
3706
|
-
payload: { sessionId }
|
|
3707
|
-
});
|
|
3708
|
-
confirmEmitted = true;
|
|
3709
|
-
}
|
|
3710
|
-
try {
|
|
3711
|
-
return await action(sessionId);
|
|
3712
|
-
} finally {
|
|
3713
|
-
if (confirmEmitted || openAppPromptShown) {
|
|
3714
|
-
ctx.emit("ui-event", {
|
|
3715
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.InteractionComplete,
|
|
3716
|
-
payload: { sessionId }
|
|
3717
|
-
});
|
|
3718
|
-
openAppPromptShown = false;
|
|
3719
|
-
}
|
|
3720
|
-
}
|
|
3721
|
-
};
|
|
3722
|
-
try {
|
|
3723
|
-
return await runOnce();
|
|
3724
|
-
} catch (err) {
|
|
3725
|
-
if (!isLegacyWrongAppError(err, appName)) {
|
|
3726
|
-
debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
|
|
3727
|
-
ctx.invalidateSession(sessionId);
|
|
3728
|
-
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3729
|
-
}
|
|
3730
|
-
debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
|
|
3731
|
-
try {
|
|
3732
|
-
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3733
|
-
} catch (switchErr) {
|
|
3734
|
-
debugLog(
|
|
3735
|
-
"[LegacyChainCall] retry ensureAppOpen failed:",
|
|
3736
|
-
appName,
|
|
3737
|
-
switchErr?.message
|
|
3738
|
-
);
|
|
3739
|
-
closeOpenAppUiIfShown();
|
|
3740
|
-
throw ctx.wrapError(switchErr, { defaultAppName: appName });
|
|
3741
|
-
}
|
|
3742
|
-
ctx.clearAllSigners();
|
|
3743
|
-
const result = await runOnce();
|
|
3744
|
-
debugLog("[LegacyChainCall] retry succeeded:", appName);
|
|
3745
|
-
return result;
|
|
3746
|
-
}
|
|
4304
|
+
return { address: result.address, path: params.path };
|
|
4305
|
+
});
|
|
3747
4306
|
}
|
|
3748
|
-
async function
|
|
4307
|
+
async function _createZcashSigner(ctx, sessionId) {
|
|
3749
4308
|
const dmk = await ctx.getOrCreateDmk();
|
|
3750
|
-
const
|
|
3751
|
-
|
|
4309
|
+
const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
|
|
4310
|
+
const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
|
|
4311
|
+
return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
|
|
3752
4312
|
}
|
|
3753
4313
|
|
|
3754
|
-
// src/
|
|
3755
|
-
var
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
4314
|
+
// src/connector/chains/tron.ts
|
|
4315
|
+
var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
|
|
4316
|
+
|
|
4317
|
+
// src/signer/SignerTron.ts
|
|
4318
|
+
var SignerTron = class {
|
|
4319
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4320
|
+
constructor(_sdk) {
|
|
4321
|
+
this._sdk = _sdk;
|
|
3761
4322
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
}
|
|
3772
|
-
result.set(statusCode, data.length);
|
|
3773
|
-
return result;
|
|
4323
|
+
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4324
|
+
async getAddress(derivationPath, options) {
|
|
4325
|
+
const action = this._sdk.getAddress(derivationPath, options);
|
|
4326
|
+
const result = await deviceActionToPromise(
|
|
4327
|
+
action,
|
|
4328
|
+
this.onInteraction,
|
|
4329
|
+
void 0,
|
|
4330
|
+
this.onRegisterCanceller
|
|
4331
|
+
);
|
|
4332
|
+
return { address: result.address, publicKey: result.publicKey };
|
|
3774
4333
|
}
|
|
3775
|
-
|
|
4334
|
+
/** Sign a protobuf-encoded raw transaction. */
|
|
4335
|
+
async signTransaction(derivationPath, transaction, options) {
|
|
4336
|
+
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
4337
|
+
return deviceActionToPromise(
|
|
4338
|
+
action,
|
|
4339
|
+
this.onInteraction,
|
|
4340
|
+
void 0,
|
|
4341
|
+
this.onRegisterCanceller
|
|
4342
|
+
);
|
|
4343
|
+
}
|
|
4344
|
+
/** Sign a personal message (TIP-191). */
|
|
4345
|
+
async signPersonalMessage(derivationPath, message, options) {
|
|
4346
|
+
const action = this._sdk.signPersonalMessage(derivationPath, message, options);
|
|
4347
|
+
return deviceActionToPromise(
|
|
4348
|
+
action,
|
|
4349
|
+
this.onInteraction,
|
|
4350
|
+
void 0,
|
|
4351
|
+
this.onRegisterCanceller
|
|
4352
|
+
);
|
|
3776
4353
|
}
|
|
3777
4354
|
};
|
|
3778
4355
|
|
|
3779
4356
|
// src/connector/chains/tron.ts
|
|
3780
4357
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4358
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3781
4359
|
const path = normalizePath(params.path);
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
{
|
|
3787
|
-
|
|
3788
|
-
// Only show "confirm on device" UI when the device is actually going
|
|
3789
|
-
// to display the address for the user to verify.
|
|
3790
|
-
needsConfirmation: showOnDevice
|
|
3791
|
-
},
|
|
3792
|
-
async (sid) => {
|
|
3793
|
-
const trx = await _createTrx(ctx, sid);
|
|
3794
|
-
const result = await trx.getAddress(path, showOnDevice);
|
|
3795
|
-
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
3796
|
-
}
|
|
3797
|
-
);
|
|
4360
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4361
|
+
const result = await tronSigner.getAddress(path, {
|
|
4362
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4363
|
+
});
|
|
4364
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
4365
|
+
});
|
|
3798
4366
|
}
|
|
3799
4367
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
3800
4368
|
if (!params.rawTxHex) {
|
|
3801
4369
|
throw Object.assign(
|
|
3802
4370
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
3803
|
-
{ code:
|
|
4371
|
+
{ code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
|
|
3804
4372
|
);
|
|
3805
4373
|
}
|
|
4374
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3806
4375
|
const path = normalizePath(params.path);
|
|
3807
|
-
return
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
async (sid) => {
|
|
3812
|
-
const trx = await _createTrx(ctx, sid);
|
|
3813
|
-
const signature = await trx.signTransaction(
|
|
3814
|
-
path,
|
|
3815
|
-
params.rawTxHex,
|
|
3816
|
-
params.tokenSignatures ?? []
|
|
3817
|
-
);
|
|
3818
|
-
return { signature };
|
|
3819
|
-
}
|
|
3820
|
-
);
|
|
4376
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4377
|
+
const signature = await tronSigner.signTransaction(path, (0, import_hwk_adapter_core11.hexToBytes)(params.rawTxHex));
|
|
4378
|
+
return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
|
|
4379
|
+
});
|
|
3821
4380
|
}
|
|
3822
4381
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4382
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3823
4383
|
const path = normalizePath(params.path);
|
|
3824
|
-
return
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
async (sid) => {
|
|
3829
|
-
const trx = await _createTrx(ctx, sid);
|
|
3830
|
-
const signature = await trx.signPersonalMessage(path, params.messageHex);
|
|
3831
|
-
return { signature };
|
|
3832
|
-
}
|
|
3833
|
-
);
|
|
4384
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4385
|
+
const signature = await tronSigner.signPersonalMessage(path, (0, import_hwk_adapter_core11.hexToBytes)(params.messageHex));
|
|
4386
|
+
return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
|
|
4387
|
+
});
|
|
3834
4388
|
}
|
|
3835
|
-
async function
|
|
4389
|
+
async function _createTronSigner(ctx, sessionId) {
|
|
3836
4390
|
const dmk = await ctx.getOrCreateDmk();
|
|
3837
|
-
|
|
4391
|
+
const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
|
|
4392
|
+
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4393
|
+
return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
|
|
3838
4394
|
}
|
|
3839
4395
|
|
|
3840
4396
|
// src/device-apps/customActions.ts
|
|
3841
|
-
var
|
|
4397
|
+
var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
|
|
3842
4398
|
var import_rxjs = require("rxjs");
|
|
3843
4399
|
var GetOsVersionDeviceAction = class {
|
|
3844
4400
|
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
@@ -3852,7 +4408,7 @@ var GetOsVersionDeviceAction = class {
|
|
|
3852
4408
|
(async () => {
|
|
3853
4409
|
try {
|
|
3854
4410
|
subject.next({
|
|
3855
|
-
status:
|
|
4411
|
+
status: import_device_management_kit2.DeviceActionStatus.Pending,
|
|
3856
4412
|
intermediateValue: { requiredUserInteraction: "none" }
|
|
3857
4413
|
});
|
|
3858
4414
|
const result = await internalApi.sendCommand(new this._deps.GetOsVersionCommand());
|
|
@@ -3861,11 +4417,11 @@ var GetOsVersionDeviceAction = class {
|
|
|
3861
4417
|
const errObj = result?.error;
|
|
3862
4418
|
throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
|
|
3863
4419
|
}
|
|
3864
|
-
subject.next({ status:
|
|
4420
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: result.data });
|
|
3865
4421
|
subject.complete();
|
|
3866
4422
|
} catch (err) {
|
|
3867
4423
|
if (cancelled) return;
|
|
3868
|
-
subject.next({ status:
|
|
4424
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
|
|
3869
4425
|
subject.complete();
|
|
3870
4426
|
}
|
|
3871
4427
|
})();
|
|
@@ -3873,7 +4429,7 @@ var GetOsVersionDeviceAction = class {
|
|
|
3873
4429
|
observable: subject.asObservable(),
|
|
3874
4430
|
cancel: () => {
|
|
3875
4431
|
cancelled = true;
|
|
3876
|
-
subject.next({ status:
|
|
4432
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
|
|
3877
4433
|
subject.complete();
|
|
3878
4434
|
}
|
|
3879
4435
|
};
|
|
@@ -3891,7 +4447,7 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3891
4447
|
(async () => {
|
|
3892
4448
|
try {
|
|
3893
4449
|
subject.next({
|
|
3894
|
-
status:
|
|
4450
|
+
status: import_device_management_kit2.DeviceActionStatus.Pending,
|
|
3895
4451
|
intermediateValue: { requiredUserInteraction: "none" }
|
|
3896
4452
|
});
|
|
3897
4453
|
const osVersionResult = await internalApi.sendCommand(new this._GetOsVersionCommand());
|
|
@@ -3908,11 +4464,11 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3908
4464
|
throw new Error(httpErr?.message ?? "Manager API getAppList failed");
|
|
3909
4465
|
}
|
|
3910
4466
|
const apps = either.extract();
|
|
3911
|
-
subject.next({ status:
|
|
4467
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: apps });
|
|
3912
4468
|
subject.complete();
|
|
3913
4469
|
} catch (err) {
|
|
3914
4470
|
if (cancelled) return;
|
|
3915
|
-
subject.next({ status:
|
|
4471
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
|
|
3916
4472
|
subject.complete();
|
|
3917
4473
|
}
|
|
3918
4474
|
})();
|
|
@@ -3920,7 +4476,7 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3920
4476
|
observable: subject.asObservable(),
|
|
3921
4477
|
cancel: () => {
|
|
3922
4478
|
cancelled = true;
|
|
3923
|
-
subject.next({ status:
|
|
4479
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
|
|
3924
4480
|
subject.complete();
|
|
3925
4481
|
}
|
|
3926
4482
|
};
|
|
@@ -4013,7 +4569,7 @@ var DeviceApps = class {
|
|
|
4013
4569
|
seTargetId: v.seTargetId,
|
|
4014
4570
|
mcuTargetId: v.mcuTargetId,
|
|
4015
4571
|
seVersion: v.seVersion,
|
|
4016
|
-
seFlagsHex:
|
|
4572
|
+
seFlagsHex: bytesToHex4(v.seFlags),
|
|
4017
4573
|
mcuSephVersion: v.mcuSephVersion,
|
|
4018
4574
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
4019
4575
|
hwVersion: v.hwVersion
|
|
@@ -4087,7 +4643,7 @@ var DeviceApps = class {
|
|
|
4087
4643
|
}
|
|
4088
4644
|
}
|
|
4089
4645
|
};
|
|
4090
|
-
function
|
|
4646
|
+
function bytesToHex4(bytes) {
|
|
4091
4647
|
if (!bytes) return "";
|
|
4092
4648
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4093
4649
|
}
|
|
@@ -4130,10 +4686,11 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4130
4686
|
evm: "Ethereum",
|
|
4131
4687
|
btc: "Bitcoin",
|
|
4132
4688
|
sol: "Solana",
|
|
4133
|
-
tron: "Tron"
|
|
4689
|
+
tron: "Tron",
|
|
4690
|
+
zcash: "Zcash"
|
|
4134
4691
|
};
|
|
4135
4692
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4136
|
-
Object.values(
|
|
4693
|
+
Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
|
|
4137
4694
|
);
|
|
4138
4695
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4139
4696
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4170,6 +4727,10 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4170
4727
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4171
4728
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4172
4729
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4730
|
+
case "@ledgerhq/device-signer-kit-tron":
|
|
4731
|
+
return import("@ledgerhq/device-signer-kit-tron");
|
|
4732
|
+
case "@ledgerhq/device-signer-kit-zcash":
|
|
4733
|
+
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4173
4734
|
case "@ledgerhq/context-module":
|
|
4174
4735
|
return import("@ledgerhq/context-module");
|
|
4175
4736
|
default:
|
|
@@ -4229,6 +4790,7 @@ var LedgerConnectorBase = class {
|
|
|
4229
4790
|
this._ctx = {
|
|
4230
4791
|
emit: (event, data) => this._emit(event, data),
|
|
4231
4792
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4793
|
+
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4232
4794
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4233
4795
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4234
4796
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -4280,7 +4842,7 @@ var LedgerConnectorBase = class {
|
|
|
4280
4842
|
async searchDevices() {
|
|
4281
4843
|
const dm = await this._getDeviceManager();
|
|
4282
4844
|
const descriptors = await this._discoverDescriptors(dm);
|
|
4283
|
-
const resolvedDescriptors = descriptors.filter((d) => !(0,
|
|
4845
|
+
const resolvedDescriptors = descriptors.filter((d) => !(0, import_hwk_adapter_core12.isKnownNonTargetHardwareVendor)(d, "ledger")).map((d) => ({
|
|
4284
4846
|
descriptor: d,
|
|
4285
4847
|
connectId: this._resolveConnectId(d)
|
|
4286
4848
|
}));
|
|
@@ -4328,7 +4890,7 @@ var LedgerConnectorBase = class {
|
|
|
4328
4890
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
4329
4891
|
);
|
|
4330
4892
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
4331
|
-
err.code =
|
|
4893
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
4332
4894
|
reject(err);
|
|
4333
4895
|
}, HANG_CEILING_MS);
|
|
4334
4896
|
});
|
|
@@ -4361,7 +4923,7 @@ var LedgerConnectorBase = class {
|
|
|
4361
4923
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
4362
4924
|
);
|
|
4363
4925
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
4364
|
-
err.code =
|
|
4926
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
|
|
4365
4927
|
throw err;
|
|
4366
4928
|
};
|
|
4367
4929
|
const doConnect = async (path) => {
|
|
@@ -4418,14 +4980,14 @@ var LedgerConnectorBase = class {
|
|
|
4418
4980
|
this._resetSignersAndSessions();
|
|
4419
4981
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
4420
4982
|
const tag = err?._tag;
|
|
4421
|
-
if (isKnownConnectionTag(tag)) {
|
|
4983
|
+
if (isKnownConnectionTag(tag) && !isConnectionOpeningTag(tag)) {
|
|
4422
4984
|
throw err;
|
|
4423
4985
|
}
|
|
4424
4986
|
const wrapped = new Error(
|
|
4425
4987
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
4426
4988
|
);
|
|
4427
4989
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
4428
|
-
wrapped.code =
|
|
4990
|
+
wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
4429
4991
|
wrapped.originalError = err;
|
|
4430
4992
|
throw wrapped;
|
|
4431
4993
|
}
|
|
@@ -4503,7 +5065,7 @@ var LedgerConnectorBase = class {
|
|
|
4503
5065
|
this._unwatchSessionState(sessionId);
|
|
4504
5066
|
this._signerManager?.invalidate(sessionId);
|
|
4505
5067
|
this._cancellers.get(sessionId)?.({
|
|
4506
|
-
code:
|
|
5068
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
|
|
4507
5069
|
tag: "DeviceDisconnected",
|
|
4508
5070
|
message: "Device disconnected"
|
|
4509
5071
|
});
|
|
@@ -4527,9 +5089,9 @@ var LedgerConnectorBase = class {
|
|
|
4527
5089
|
if (isAppStuckByApdu(err)) {
|
|
4528
5090
|
return {
|
|
4529
5091
|
success: false,
|
|
4530
|
-
error: (0,
|
|
5092
|
+
error: (0, import_hwk_adapter_core12.serializeConnectorError)(
|
|
4531
5093
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
4532
|
-
code:
|
|
5094
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
|
|
4533
5095
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
4534
5096
|
originalError: err
|
|
4535
5097
|
})
|
|
@@ -4539,16 +5101,16 @@ var LedgerConnectorBase = class {
|
|
|
4539
5101
|
if (isTransportStuck(err)) {
|
|
4540
5102
|
return {
|
|
4541
5103
|
success: false,
|
|
4542
|
-
error: (0,
|
|
5104
|
+
error: (0, import_hwk_adapter_core12.serializeConnectorError)(
|
|
4543
5105
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
4544
|
-
code:
|
|
5106
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
|
|
4545
5107
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
4546
5108
|
originalError: err
|
|
4547
5109
|
})
|
|
4548
5110
|
)
|
|
4549
5111
|
};
|
|
4550
5112
|
}
|
|
4551
|
-
return { success: false, error: (0,
|
|
5113
|
+
return { success: false, error: (0, import_hwk_adapter_core12.serializeConnectorError)(err) };
|
|
4552
5114
|
}
|
|
4553
5115
|
}
|
|
4554
5116
|
async _dispatch(sessionId, method, params) {
|
|
@@ -4587,6 +5149,11 @@ var LedgerConnectorBase = class {
|
|
|
4587
5149
|
return solSignTransaction(ctx, sessionId, params);
|
|
4588
5150
|
case "solSignMessage":
|
|
4589
5151
|
return solSignMessage(ctx, sessionId, params);
|
|
5152
|
+
// ZCASH
|
|
5153
|
+
case "zcashGetFullViewingKey":
|
|
5154
|
+
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5155
|
+
case "zcashGetShieldedAddress":
|
|
5156
|
+
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
4590
5157
|
// TRON
|
|
4591
5158
|
case "tronGetAddress":
|
|
4592
5159
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -4611,7 +5178,7 @@ var LedgerConnectorBase = class {
|
|
|
4611
5178
|
try {
|
|
4612
5179
|
return await apps.install(p.appName, ({ progress }) => {
|
|
4613
5180
|
ctx.emit("ui-event", {
|
|
4614
|
-
type:
|
|
5181
|
+
type: import_hwk_adapter_core12.EConnectorInteraction.AppInstallProgress,
|
|
4615
5182
|
payload: {
|
|
4616
5183
|
sessionId,
|
|
4617
5184
|
appName: p.appName,
|
|
@@ -4620,7 +5187,7 @@ var LedgerConnectorBase = class {
|
|
|
4620
5187
|
});
|
|
4621
5188
|
});
|
|
4622
5189
|
} catch (err) {
|
|
4623
|
-
ctx.
|
|
5190
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4624
5191
|
throw ctx.wrapError(err);
|
|
4625
5192
|
} finally {
|
|
4626
5193
|
ctx.clearCanceller(sessionId);
|
|
@@ -4710,7 +5277,7 @@ var LedgerConnectorBase = class {
|
|
|
4710
5277
|
);
|
|
4711
5278
|
return { isGenuine: output.isGenuine, deviceId };
|
|
4712
5279
|
} catch (err) {
|
|
4713
|
-
ctx.
|
|
5280
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4714
5281
|
throw ctx.wrapError(err);
|
|
4715
5282
|
} finally {
|
|
4716
5283
|
ctx.clearCanceller(sessionId);
|
|
@@ -4829,9 +5396,26 @@ var LedgerConnectorBase = class {
|
|
|
4829
5396
|
}
|
|
4830
5397
|
return this._deviceAppsManager;
|
|
4831
5398
|
}
|
|
5399
|
+
// DeviceAppsManager is a per-call factory with no cached session state, so
|
|
5400
|
+
// there is nothing to invalidate for it here.
|
|
4832
5401
|
_invalidateSession(sessionId) {
|
|
4833
5402
|
this._signerManager?.invalidate(sessionId);
|
|
4834
5403
|
}
|
|
5404
|
+
/**
|
|
5405
|
+
* Fire the canceller so DMK closes the secure-channel WebSocket. The DMK device
|
|
5406
|
+
* session is kept: unlock/retry recovery reuses it.
|
|
5407
|
+
*/
|
|
5408
|
+
_teardownSecureChannelSession(sessionId) {
|
|
5409
|
+
const cancel = this._cancellers.get(sessionId);
|
|
5410
|
+
this._cancellers.delete(sessionId);
|
|
5411
|
+
if (cancel) {
|
|
5412
|
+
try {
|
|
5413
|
+
cancel();
|
|
5414
|
+
} catch {
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
this._invalidateSession(sessionId);
|
|
5418
|
+
}
|
|
4835
5419
|
/**
|
|
4836
5420
|
* Replace an old session with a new one after app switch.
|
|
4837
5421
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -4916,7 +5500,7 @@ var LedgerConnectorBase = class {
|
|
|
4916
5500
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
4917
5501
|
*/
|
|
4918
5502
|
_ctxForMethod(method) {
|
|
4919
|
-
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5503
|
+
const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
|
|
4920
5504
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
4921
5505
|
if (!defaultAppName) return this._ctx;
|
|
4922
5506
|
return {
|
|
@@ -4950,6 +5534,204 @@ var LedgerConnectorBase = class {
|
|
|
4950
5534
|
return error;
|
|
4951
5535
|
}
|
|
4952
5536
|
};
|
|
5537
|
+
|
|
5538
|
+
// src/transport/DmkTransport.ts
|
|
5539
|
+
var import_hw_transport = __toESM(require("@ledgerhq/hw-transport"));
|
|
5540
|
+
var DmkTransport = class extends import_hw_transport.default {
|
|
5541
|
+
constructor(dmk, sessionId) {
|
|
5542
|
+
super();
|
|
5543
|
+
this._dmk = dmk;
|
|
5544
|
+
this._sessionId = sessionId;
|
|
5545
|
+
}
|
|
5546
|
+
async exchange(apdu) {
|
|
5547
|
+
const response = await this._dmk.sendApdu({
|
|
5548
|
+
sessionId: this._sessionId,
|
|
5549
|
+
apdu: new Uint8Array(apdu)
|
|
5550
|
+
});
|
|
5551
|
+
const { data, statusCode } = response;
|
|
5552
|
+
const result = Buffer.alloc(data.length + 2);
|
|
5553
|
+
if (data.length > 0) {
|
|
5554
|
+
result.set(data, 0);
|
|
5555
|
+
}
|
|
5556
|
+
result.set(statusCode, data.length);
|
|
5557
|
+
return result;
|
|
5558
|
+
}
|
|
5559
|
+
async close() {
|
|
5560
|
+
}
|
|
5561
|
+
};
|
|
5562
|
+
|
|
5563
|
+
// src/app/AppManager.ts
|
|
5564
|
+
var import_device_management_kit3 = require("@ledgerhq/device-management-kit");
|
|
5565
|
+
var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
|
|
5566
|
+
var APP_NAME_MAP = {
|
|
5567
|
+
ETH: "Ethereum",
|
|
5568
|
+
BTC: "Bitcoin",
|
|
5569
|
+
SOL: "Solana",
|
|
5570
|
+
TRX: "Tron",
|
|
5571
|
+
ZEC: "Zcash",
|
|
5572
|
+
XRP: "XRP",
|
|
5573
|
+
ADA: "Cardano",
|
|
5574
|
+
DOT: "Polkadot",
|
|
5575
|
+
ATOM: "Cosmos"
|
|
5576
|
+
};
|
|
5577
|
+
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5578
|
+
var AppManager = class {
|
|
5579
|
+
constructor(dmk, options) {
|
|
5580
|
+
this._dmk = dmk;
|
|
5581
|
+
this._waitMs = options?.waitMs ?? 1e3;
|
|
5582
|
+
this._maxRetries = options?.maxRetries ?? 10;
|
|
5583
|
+
}
|
|
5584
|
+
/**
|
|
5585
|
+
* Return the Ledger app name for a given chain ticker,
|
|
5586
|
+
* or undefined if the chain is not supported.
|
|
5587
|
+
*/
|
|
5588
|
+
static getAppName(chain) {
|
|
5589
|
+
return APP_NAME_MAP[chain];
|
|
5590
|
+
}
|
|
5591
|
+
/**
|
|
5592
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5593
|
+
*
|
|
5594
|
+
* Flow:
|
|
5595
|
+
* 1. Check the currently running app.
|
|
5596
|
+
* 2. If it is already the target, return immediately.
|
|
5597
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
5598
|
+
* 4. Open the target app.
|
|
5599
|
+
* 5. Poll until the device confirms the target app is running.
|
|
5600
|
+
*/
|
|
5601
|
+
/**
|
|
5602
|
+
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5603
|
+
* device is about to display "Open <app>" on screen and wait for the
|
|
5604
|
+
* user's button press. UI consumers should show their "open app" prompt
|
|
5605
|
+
* in response. NOT called when the target app is already open (no user
|
|
5606
|
+
* interaction needed in that case).
|
|
5607
|
+
*
|
|
5608
|
+
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5609
|
+
* has physically confirmed on the device, so anything that runs AFTER
|
|
5610
|
+
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5611
|
+
* Hence the callback must fire BEFORE that await.
|
|
5612
|
+
*/
|
|
5613
|
+
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5614
|
+
const currentApp = await this._getCurrentApp(sessionId);
|
|
5615
|
+
if (currentApp === targetAppName) {
|
|
5616
|
+
return;
|
|
5617
|
+
}
|
|
5618
|
+
if (!this._isDashboard(currentApp)) {
|
|
5619
|
+
await this._closeCurrentApp(sessionId);
|
|
5620
|
+
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5621
|
+
}
|
|
5622
|
+
onConfirmOnDevice?.();
|
|
5623
|
+
await this._openApp(sessionId, targetAppName);
|
|
5624
|
+
await this._waitForApp(sessionId, targetAppName);
|
|
5625
|
+
}
|
|
5626
|
+
// ---------------------------------------------------------------------------
|
|
5627
|
+
// Private helpers
|
|
5628
|
+
// ---------------------------------------------------------------------------
|
|
5629
|
+
async _getCurrentApp(sessionId) {
|
|
5630
|
+
const result = await this._dmk.sendCommand({
|
|
5631
|
+
sessionId,
|
|
5632
|
+
command: new import_device_management_kit3.GetAppAndVersionCommand()
|
|
5633
|
+
});
|
|
5634
|
+
if ((0, import_device_management_kit3.isSuccessCommandResult)(result)) {
|
|
5635
|
+
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5636
|
+
return result.data.name;
|
|
5637
|
+
}
|
|
5638
|
+
const errResult = result;
|
|
5639
|
+
const dmkErr = errResult.error ?? {};
|
|
5640
|
+
const original = dmkErr.originalError;
|
|
5641
|
+
debugLog(
|
|
5642
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5643
|
+
sessionId,
|
|
5644
|
+
"tag=",
|
|
5645
|
+
dmkErr._tag,
|
|
5646
|
+
"errorCode=",
|
|
5647
|
+
dmkErr.errorCode,
|
|
5648
|
+
"message=",
|
|
5649
|
+
dmkErr.message,
|
|
5650
|
+
"originalErrorMessage=",
|
|
5651
|
+
original?.message ?? String(original ?? "")
|
|
5652
|
+
);
|
|
5653
|
+
throw Object.assign(
|
|
5654
|
+
new Error(
|
|
5655
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
5656
|
+
),
|
|
5657
|
+
{
|
|
5658
|
+
_tag: dmkErr._tag,
|
|
5659
|
+
errorCode: dmkErr.errorCode,
|
|
5660
|
+
originalError: original
|
|
5661
|
+
}
|
|
5662
|
+
);
|
|
5663
|
+
}
|
|
5664
|
+
async _openApp(sessionId, appName) {
|
|
5665
|
+
const result = await this._dmk.sendCommand({
|
|
5666
|
+
sessionId,
|
|
5667
|
+
command: new import_device_management_kit3.OpenAppCommand({ appName })
|
|
5668
|
+
});
|
|
5669
|
+
if (!(0, import_device_management_kit3.isSuccessCommandResult)(result)) {
|
|
5670
|
+
const dmkErr = result.error;
|
|
5671
|
+
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5672
|
+
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5673
|
+
debugLog(
|
|
5674
|
+
"[AppManager] openApp failed:",
|
|
5675
|
+
appName,
|
|
5676
|
+
"errorCode:",
|
|
5677
|
+
errorCode,
|
|
5678
|
+
"tag:",
|
|
5679
|
+
dmkErr._tag
|
|
5680
|
+
);
|
|
5681
|
+
let code;
|
|
5682
|
+
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5683
|
+
code = import_hwk_adapter_core13.HardwareErrorCode.AppNotInstalled;
|
|
5684
|
+
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5685
|
+
code = import_hwk_adapter_core13.HardwareErrorCode.UserRejected;
|
|
5686
|
+
}
|
|
5687
|
+
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5688
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
5689
|
+
code,
|
|
5690
|
+
errorCode,
|
|
5691
|
+
statusCode: errorCode,
|
|
5692
|
+
appName,
|
|
5693
|
+
originalError: dmkErr
|
|
5694
|
+
});
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
async _closeCurrentApp(sessionId) {
|
|
5698
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
5699
|
+
await this._dmk.sendCommand({
|
|
5700
|
+
sessionId,
|
|
5701
|
+
command: new import_device_management_kit3.CloseAppCommand()
|
|
5702
|
+
});
|
|
5703
|
+
}
|
|
5704
|
+
/**
|
|
5705
|
+
* Poll the device until the expected app is reported as running,
|
|
5706
|
+
* or throw after `_maxRetries` attempts.
|
|
5707
|
+
*/
|
|
5708
|
+
async _waitForApp(sessionId, expectedAppName) {
|
|
5709
|
+
let lastSeen = "";
|
|
5710
|
+
for (let i = 0; i < this._maxRetries; i++) {
|
|
5711
|
+
await this._wait();
|
|
5712
|
+
const current = await this._getCurrentApp(sessionId);
|
|
5713
|
+
lastSeen = current;
|
|
5714
|
+
if (current === expectedAppName) {
|
|
5715
|
+
return;
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
debugLog(
|
|
5719
|
+
"[AppManager] waitForApp exhausted: expected=",
|
|
5720
|
+
expectedAppName,
|
|
5721
|
+
"lastSeen=",
|
|
5722
|
+
lastSeen
|
|
5723
|
+
);
|
|
5724
|
+
throw new Error(
|
|
5725
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5726
|
+
);
|
|
5727
|
+
}
|
|
5728
|
+
_isDashboard(appName) {
|
|
5729
|
+
return appName === DASHBOARD_APP_NAME;
|
|
5730
|
+
}
|
|
5731
|
+
_wait() {
|
|
5732
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5733
|
+
}
|
|
5734
|
+
};
|
|
4953
5735
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4954
5736
|
0 && (module.exports = {
|
|
4955
5737
|
AppManager,
|
|
@@ -4961,6 +5743,7 @@ var LedgerConnectorBase = class {
|
|
|
4961
5743
|
SignerEth,
|
|
4962
5744
|
SignerManager,
|
|
4963
5745
|
SignerSol,
|
|
5746
|
+
SignerZcash,
|
|
4964
5747
|
debugLog,
|
|
4965
5748
|
deviceActionToPromise,
|
|
4966
5749
|
isDeviceLockedError,
|