@onekeyfe/hwk-ledger-adapter 1.2.3-alpha.7 → 1.2.3-alpha.8
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 +86 -29
- package/dist/index.d.ts +86 -29
- package/dist/index.js +1781 -826
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1789 -814
- 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,12 @@ var ERROR_TAG = {
|
|
|
208
206
|
UnknownDevice: "UnknownDeviceError",
|
|
209
207
|
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
210
208
|
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
211
|
-
|
|
209
|
+
// DMK's class is named OpeningConnectionError but its `_tag` reads
|
|
210
|
+
// "ConnectionOpeningError" — and its typings widen `_tag` to `string`, which
|
|
211
|
+
// is why the class name looked authoritative. Both spellings are kept so a
|
|
212
|
+
// DMK version that aligns them does not silently drop back to UnknownError.
|
|
213
|
+
OpeningConnection: "ConnectionOpeningError",
|
|
214
|
+
OpeningConnectionLegacy: "OpeningConnectionError",
|
|
212
215
|
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
213
216
|
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
214
217
|
Disconnect: "DisconnectError",
|
|
@@ -221,7 +224,18 @@ var ERROR_TAG = {
|
|
|
221
224
|
// DMK remote-network failures (manager-api HTTP / secure-channel WS).
|
|
222
225
|
WebSocketConnection: "WebSocketConnectionError",
|
|
223
226
|
HttpFetch: "FetchError",
|
|
224
|
-
|
|
227
|
+
NetworkDA: "NetworkDAError",
|
|
228
|
+
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
|
|
229
|
+
ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
|
|
230
|
+
// DMK OS/secure-channel device actions. `SecureChannelError` is the residual
|
|
231
|
+
// bucket left by SecureChannelError.mapInstallDAErrors() after the locked /
|
|
232
|
+
// refused / already-installed / OOM cases have been split out, so it means
|
|
233
|
+
// "the relay itself broke", not "the device answered".
|
|
234
|
+
SecureChannel: "SecureChannelError",
|
|
235
|
+
RefusedByUserDA: "RefusedByUserDAError",
|
|
236
|
+
AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
|
|
237
|
+
OutOfMemoryDA: "OutOfMemoryDAError",
|
|
238
|
+
DeviceNotOnboarded: "DeviceNotOnboardedError"
|
|
225
239
|
};
|
|
226
240
|
function isDeviceLockedError(err) {
|
|
227
241
|
if (!err || typeof err !== "object") return false;
|
|
@@ -251,7 +265,6 @@ function isBlePairingFailureError(err) {
|
|
|
251
265
|
return false;
|
|
252
266
|
}
|
|
253
267
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
254
|
-
ERROR_TAG.DeviceLocked,
|
|
255
268
|
ERROR_TAG.DeviceNotAdvertising,
|
|
256
269
|
ERROR_TAG.BlePairingTimeout,
|
|
257
270
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -263,6 +276,7 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
|
263
276
|
ERROR_TAG.DeviceSessionRefresher,
|
|
264
277
|
ERROR_TAG.DeviceNotInitialized,
|
|
265
278
|
ERROR_TAG.OpeningConnection,
|
|
279
|
+
ERROR_TAG.OpeningConnectionLegacy,
|
|
266
280
|
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
267
281
|
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
268
282
|
ERROR_TAG.Disconnect,
|
|
@@ -277,7 +291,10 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
|
277
291
|
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
278
292
|
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
279
293
|
]);
|
|
280
|
-
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
294
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
295
|
+
ERROR_TAG.OpeningConnection,
|
|
296
|
+
ERROR_TAG.OpeningConnectionLegacy
|
|
297
|
+
]);
|
|
281
298
|
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
282
299
|
ERROR_TAG.DeviceNotRecognized,
|
|
283
300
|
ERROR_TAG.DeviceSessionNotFound,
|
|
@@ -300,6 +317,13 @@ function isConnectionLevelError(err) {
|
|
|
300
317
|
function isKnownConnectionTag(tag) {
|
|
301
318
|
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
302
319
|
}
|
|
320
|
+
var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
|
|
321
|
+
ERROR_TAG.OpeningConnection,
|
|
322
|
+
ERROR_TAG.OpeningConnectionLegacy
|
|
323
|
+
]);
|
|
324
|
+
function isConnectionOpeningTag(tag) {
|
|
325
|
+
return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
|
|
326
|
+
}
|
|
303
327
|
function hasStatusCode(err, codeSet) {
|
|
304
328
|
if (!err || typeof err !== "object") return false;
|
|
305
329
|
const e = err;
|
|
@@ -335,6 +359,14 @@ function hasInvalidArgumentCode(err) {
|
|
|
335
359
|
if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
|
|
336
360
|
return false;
|
|
337
361
|
}
|
|
362
|
+
function hasErrorTag(err, tags) {
|
|
363
|
+
if (!err || typeof err !== "object") return false;
|
|
364
|
+
const e = err;
|
|
365
|
+
if (typeof e._tag === "string" && tags.has(e._tag)) return true;
|
|
366
|
+
if (e.originalError != null && hasErrorTag(e.originalError, tags)) return true;
|
|
367
|
+
if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
338
370
|
function isDeviceNotFoundError(err) {
|
|
339
371
|
if (!err || typeof err !== "object") return false;
|
|
340
372
|
const tag = err._tag;
|
|
@@ -353,10 +385,14 @@ function isDeviceBusyError(err) {
|
|
|
353
385
|
if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
|
|
354
386
|
return false;
|
|
355
387
|
}
|
|
388
|
+
var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
|
|
389
|
+
ERROR_TAG.UserRefusedOnDevice,
|
|
390
|
+
ERROR_TAG.RefusedByUserDA
|
|
391
|
+
]);
|
|
356
392
|
function isUserRejectedError(err) {
|
|
357
393
|
if (!err || typeof err !== "object") return false;
|
|
358
394
|
const e = err;
|
|
359
|
-
if (
|
|
395
|
+
if (hasErrorTag(err, USER_REJECTED_TAGS)) return true;
|
|
360
396
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
361
397
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
362
398
|
return false;
|
|
@@ -387,15 +423,37 @@ function isAppNotInstalledError(err) {
|
|
|
387
423
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
388
424
|
return false;
|
|
389
425
|
}
|
|
426
|
+
var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
|
|
390
427
|
function isOutOfMemoryError(err) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
428
|
+
return hasErrorTag(err, OUT_OF_MEMORY_TAGS);
|
|
429
|
+
}
|
|
430
|
+
var APP_ALREADY_INSTALLED_TAGS = /* @__PURE__ */ new Set([
|
|
431
|
+
ERROR_TAG.AppAlreadyInstalledDA
|
|
432
|
+
]);
|
|
433
|
+
function isAppAlreadyInstalledError(err) {
|
|
434
|
+
return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
|
|
435
|
+
}
|
|
436
|
+
var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([
|
|
437
|
+
ERROR_TAG.DeviceNotOnboarded
|
|
438
|
+
]);
|
|
439
|
+
function isDeviceNotOnboardedError(err) {
|
|
440
|
+
return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
|
|
441
|
+
}
|
|
442
|
+
var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
|
|
443
|
+
ERROR_TAG.InvalidFirmwareMetadataResponse,
|
|
444
|
+
ERROR_TAG.ApplicationsMetadataTask
|
|
445
|
+
]);
|
|
446
|
+
function isFirmwareMetadataError(err) {
|
|
447
|
+
return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
|
|
448
|
+
}
|
|
449
|
+
var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
|
|
450
|
+
function isSecureChannelError(err) {
|
|
451
|
+
return hasErrorTag(err, SECURE_CHANNEL_TAGS);
|
|
394
452
|
}
|
|
395
453
|
var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
|
|
396
454
|
ERROR_TAG.WebSocketConnection,
|
|
397
455
|
ERROR_TAG.HttpFetch,
|
|
398
|
-
ERROR_TAG.
|
|
456
|
+
ERROR_TAG.NetworkDA
|
|
399
457
|
]);
|
|
400
458
|
function isNetworkError(err) {
|
|
401
459
|
if (!err || typeof err !== "object") return false;
|
|
@@ -445,23 +503,34 @@ function isTransportStuck(err) {
|
|
|
445
503
|
function isStuckAppStateError(err) {
|
|
446
504
|
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
447
505
|
}
|
|
506
|
+
var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
|
|
507
|
+
function nestedErrorMessage(nested) {
|
|
508
|
+
if (!nested || typeof nested !== "object") return void 0;
|
|
509
|
+
const { message } = nested;
|
|
510
|
+
if (typeof message !== "string") return void 0;
|
|
511
|
+
const trimmed = message.trim();
|
|
512
|
+
if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
|
|
513
|
+
return message;
|
|
514
|
+
}
|
|
448
515
|
function mapLedgerError(err, opts) {
|
|
449
516
|
let originalMessage = "Unknown Ledger error";
|
|
450
517
|
if (err instanceof Error) {
|
|
451
518
|
originalMessage = err.message;
|
|
452
519
|
} else if (err && typeof err === "object") {
|
|
453
520
|
const e = err;
|
|
454
|
-
originalMessage = String(
|
|
521
|
+
originalMessage = String(
|
|
522
|
+
e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
|
|
523
|
+
);
|
|
455
524
|
}
|
|
456
525
|
let code;
|
|
457
526
|
if (isDeviceLockedError(err)) {
|
|
458
527
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceLocked;
|
|
459
528
|
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
460
529
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound;
|
|
461
|
-
} else if (isDeviceBusyError(err)) {
|
|
462
|
-
code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
|
|
463
530
|
} else if (isBlePairingFailureError(err)) {
|
|
464
531
|
code = import_hwk_adapter_core.HardwareErrorCode.BlePairingTimeout;
|
|
532
|
+
} else if (isDeviceBusyError(err)) {
|
|
533
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
|
|
465
534
|
} else if (isUserAbortedError(err)) {
|
|
466
535
|
code = import_hwk_adapter_core.HardwareErrorCode.UserAborted;
|
|
467
536
|
} else if (isUserRejectedError(err)) {
|
|
@@ -470,10 +539,18 @@ function mapLedgerError(err, opts) {
|
|
|
470
539
|
code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
|
|
471
540
|
} else if (isAppNotInstalledError(err)) {
|
|
472
541
|
code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
|
|
542
|
+
} else if (isAppAlreadyInstalledError(err)) {
|
|
543
|
+
code = import_hwk_adapter_core.HardwareErrorCode.AppAlreadyInstalled;
|
|
473
544
|
} else if (isOutOfMemoryError(err)) {
|
|
474
545
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceOutOfMemory;
|
|
546
|
+
} else if (isDeviceNotOnboardedError(err)) {
|
|
547
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotInitialized;
|
|
548
|
+
} else if (isFirmwareMetadataError(err)) {
|
|
549
|
+
code = import_hwk_adapter_core.HardwareErrorCode.LedgerFirmwareMetadataError;
|
|
475
550
|
} else if (isNetworkError(err)) {
|
|
476
551
|
code = import_hwk_adapter_core.HardwareErrorCode.NetworkError;
|
|
552
|
+
} else if (isSecureChannelError(err)) {
|
|
553
|
+
code = import_hwk_adapter_core.HardwareErrorCode.LedgerSecureChannelError;
|
|
477
554
|
} else if (isDeviceDisconnectedError(err)) {
|
|
478
555
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
|
|
479
556
|
} else if (isTimeoutError(err)) {
|
|
@@ -486,7 +563,21 @@ function mapLedgerError(err, opts) {
|
|
|
486
563
|
}
|
|
487
564
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
488
565
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
489
|
-
return {
|
|
566
|
+
return {
|
|
567
|
+
code,
|
|
568
|
+
message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage),
|
|
569
|
+
origin: (0, import_hwk_adapter_core.defaultOriginForCode)(code),
|
|
570
|
+
appName
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/utils/queueKey.ts
|
|
575
|
+
var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
|
|
576
|
+
function ledgerQueueKey({
|
|
577
|
+
operationId,
|
|
578
|
+
connectId
|
|
579
|
+
}) {
|
|
580
|
+
return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
|
|
490
581
|
}
|
|
491
582
|
|
|
492
583
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
@@ -547,65 +638,102 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
547
638
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
548
639
|
function createAllNetworkGetAddress({
|
|
549
640
|
callChain,
|
|
550
|
-
getChainFingerprint
|
|
641
|
+
getChainFingerprint,
|
|
642
|
+
retainOperation,
|
|
643
|
+
errorToFailure,
|
|
644
|
+
createCancelScope
|
|
551
645
|
}) {
|
|
552
646
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
553
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
647
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
648
|
+
method: "allNetworkGetAddress",
|
|
649
|
+
connectId,
|
|
650
|
+
itemCount: params.bundle.length
|
|
651
|
+
});
|
|
652
|
+
const target = (0, import_hwk_adapter_core2.resolveHardwareOperationTarget)(connectId, params.operationId, "ledger");
|
|
653
|
+
if (!target.success) return target;
|
|
654
|
+
const effectiveTargetId = target.payload.targetId ?? "";
|
|
655
|
+
let releaseOperationRetention;
|
|
656
|
+
try {
|
|
657
|
+
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
658
|
+
} catch (error) {
|
|
659
|
+
return errorToFailure(error);
|
|
660
|
+
}
|
|
661
|
+
const cancelScope = createCancelScope(
|
|
662
|
+
ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
663
|
+
);
|
|
554
664
|
const installContext = {};
|
|
555
665
|
const commonParams = {
|
|
556
|
-
autoInstallApp: params.autoInstallApp
|
|
666
|
+
autoInstallApp: params.autoInstallApp,
|
|
667
|
+
operationId: target.payload.operationId,
|
|
668
|
+
knownConnections: params.knownConnections,
|
|
669
|
+
extra: params.extra,
|
|
670
|
+
allowDeviceSelection: params.allowDeviceSelection
|
|
557
671
|
};
|
|
558
672
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
673
|
+
try {
|
|
674
|
+
const result = await (0, import_hwk_adapter_core2.runAllNetworkGetAddress)({
|
|
675
|
+
connectId: effectiveTargetId,
|
|
676
|
+
deviceId: _deviceId,
|
|
677
|
+
params,
|
|
678
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
679
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
680
|
+
callItem: async ({ method, chain, item }) => {
|
|
681
|
+
if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
|
|
682
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
683
|
+
return callAllNetworkMethod(
|
|
684
|
+
callChain,
|
|
685
|
+
effectiveTargetId,
|
|
686
|
+
itemDeviceId,
|
|
687
|
+
method,
|
|
688
|
+
item,
|
|
689
|
+
commonParams,
|
|
690
|
+
installContext
|
|
691
|
+
);
|
|
692
|
+
},
|
|
693
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
694
|
+
getChainFingerprint,
|
|
695
|
+
effectiveTargetId,
|
|
572
696
|
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;
|
|
697
|
+
chain,
|
|
698
|
+
payload,
|
|
699
|
+
chainFingerprints,
|
|
700
|
+
installContext,
|
|
701
|
+
cancelScope.signal
|
|
702
|
+
),
|
|
703
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
704
|
+
buildTopLevelFailure: (response) => {
|
|
705
|
+
const code = response.payload?.code ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch;
|
|
706
|
+
return (0, import_hwk_adapter_core2.failure)(
|
|
707
|
+
code,
|
|
708
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
709
|
+
response.payload?.params
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
714
|
+
method: "allNetworkGetAddress",
|
|
715
|
+
success: result.success,
|
|
716
|
+
payload: result
|
|
717
|
+
});
|
|
718
|
+
return result;
|
|
719
|
+
} finally {
|
|
720
|
+
cancelScope.release();
|
|
721
|
+
releaseOperationRetention?.();
|
|
722
|
+
}
|
|
601
723
|
};
|
|
602
724
|
}
|
|
725
|
+
function buildCancelledFailure(signal) {
|
|
726
|
+
const reason = signal.reason;
|
|
727
|
+
const code = typeof reason?.code === "number" ? reason.code : import_hwk_adapter_core2.HardwareErrorCode.UserAborted;
|
|
728
|
+
const message = typeof reason?.message === "string" ? reason.message : "";
|
|
729
|
+
return (0, import_hwk_adapter_core2.failure)(code, message || "All-network get-address cancelled");
|
|
730
|
+
}
|
|
603
731
|
function isTopLevelAllNetworkFailure(response) {
|
|
604
732
|
if (response.success) {
|
|
605
733
|
return false;
|
|
606
734
|
}
|
|
607
735
|
const code = response.payload?.code;
|
|
608
|
-
return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch ||
|
|
736
|
+
return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch || (0, import_hwk_adapter_core2.isUserRefusal)(code) || (0, import_hwk_adapter_core2.isConnectionLost)(code);
|
|
609
737
|
}
|
|
610
738
|
function getItemDeviceId(item) {
|
|
611
739
|
const { deviceId } = item;
|
|
@@ -635,8 +763,18 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
635
763
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
636
764
|
return coin ? { ...item, coin } : item;
|
|
637
765
|
}
|
|
638
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
639
|
-
const
|
|
766
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
|
|
767
|
+
const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
|
|
768
|
+
if (!knownFingerprint && cancelSignal.aborted) {
|
|
769
|
+
const cancelled = buildCancelledFailure(cancelSignal);
|
|
770
|
+
return { ...item, success: false, payload: cancelled.payload };
|
|
771
|
+
}
|
|
772
|
+
const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
|
|
773
|
+
getChainFingerprint,
|
|
774
|
+
context.connection?.connectId ?? connectId,
|
|
775
|
+
chain,
|
|
776
|
+
context
|
|
777
|
+
);
|
|
640
778
|
if (!fingerprint) {
|
|
641
779
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
642
780
|
}
|
|
@@ -657,8 +795,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
657
795
|
}
|
|
658
796
|
};
|
|
659
797
|
}
|
|
660
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
661
|
-
const response = await getChainFingerprint(connectId, chain);
|
|
798
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
|
|
799
|
+
const response = await getChainFingerprint(connectId, chain, context);
|
|
662
800
|
return response.success ? response.payload : "";
|
|
663
801
|
}
|
|
664
802
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -762,9 +900,32 @@ function btcAccountIndexFromPath(path) {
|
|
|
762
900
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
763
901
|
constructor(connector, options) {
|
|
764
902
|
this.vendor = "ledger";
|
|
903
|
+
// A cancel releases the DMK device action and its intent-queue slot, so the
|
|
904
|
+
// next call is not blocked behind it. It still cannot retract a confirmation
|
|
905
|
+
// screen the device is already showing.
|
|
906
|
+
this.cancelCapability = "stops-waiting";
|
|
765
907
|
this.emitter = new import_hwk_adapter_core3.TypedEventEmitter();
|
|
908
|
+
this._operations = new import_hwk_adapter_core3.OperationRegistry({
|
|
909
|
+
vendor: "ledger",
|
|
910
|
+
onEnded: (operation, reason) => {
|
|
911
|
+
const binding = this._pendingOperationBindings.get(operation.operationId);
|
|
912
|
+
if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
|
|
913
|
+
this._finishBleBinding("cancelled");
|
|
914
|
+
}
|
|
915
|
+
this._pendingOperationBindings.delete(operation.operationId);
|
|
916
|
+
this.emitter.emit(import_hwk_adapter_core3.SDK.OPERATION_ENDED, {
|
|
917
|
+
type: import_hwk_adapter_core3.SDK.OPERATION_ENDED,
|
|
918
|
+
payload: { operationId: operation.operationId, reason }
|
|
919
|
+
});
|
|
920
|
+
if (reason === "timeout") {
|
|
921
|
+
void this._releaseOperationConnection(operation);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
});
|
|
766
925
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
767
926
|
this._sessions = /* @__PURE__ */ new Map();
|
|
927
|
+
this._pendingOperationBindings = /* @__PURE__ */ new Map();
|
|
928
|
+
this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
768
929
|
this._uiRegistry = new import_hwk_adapter_core3.UiRequestRegistry();
|
|
769
930
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
770
931
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -778,10 +939,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
778
939
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
779
940
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
780
941
|
this._doConnectAbortController = null;
|
|
942
|
+
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
943
|
+
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
944
|
+
this._resetPromise = null;
|
|
945
|
+
this._stateGeneration = 0;
|
|
946
|
+
this._connectorTeardownTail = Promise.resolve();
|
|
947
|
+
this._pendingConnectorTeardowns = 0;
|
|
948
|
+
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
949
|
+
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
781
950
|
this._installProgressLastEmittedValue = -Infinity;
|
|
782
951
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
783
952
|
callChain: this.callChain.bind(this),
|
|
784
|
-
getChainFingerprint: (connectId, chain) =>
|
|
953
|
+
getChainFingerprint: async (connectId, chain, context) => {
|
|
954
|
+
try {
|
|
955
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
956
|
+
chain,
|
|
957
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
958
|
+
);
|
|
959
|
+
return (0, import_hwk_adapter_core3.success)(fingerprint);
|
|
960
|
+
} catch (error) {
|
|
961
|
+
return this.errorToFailure(error);
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
965
|
+
errorToFailure: (error) => this.errorToFailure(error),
|
|
966
|
+
createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
|
|
785
967
|
});
|
|
786
968
|
// ---------------------------------------------------------------------------
|
|
787
969
|
// Private helpers
|
|
@@ -791,7 +973,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
791
973
|
*
|
|
792
974
|
* - If a session already exists for the given connectId, reuse it.
|
|
793
975
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
794
|
-
* - Otherwise: search →
|
|
976
|
+
* - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
|
|
795
977
|
*/
|
|
796
978
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
797
979
|
this._connectingPromise = null;
|
|
@@ -808,6 +990,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
808
990
|
});
|
|
809
991
|
};
|
|
810
992
|
this.deviceDisconnectHandler = (data) => {
|
|
993
|
+
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
994
|
+
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
995
|
+
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
996
|
+
} else {
|
|
997
|
+
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
998
|
+
}
|
|
811
999
|
this._discoveredDevices.delete(data.connectId);
|
|
812
1000
|
this._sessions.delete(data.connectId);
|
|
813
1001
|
this.emitter.emit(import_hwk_adapter_core3.DEVICE.DISCONNECT, {
|
|
@@ -864,11 +1052,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
864
1052
|
this._jobQueue = new import_hwk_adapter_core3.DeviceJobQueue();
|
|
865
1053
|
this.registerEventListeners();
|
|
866
1054
|
}
|
|
1055
|
+
_finishBleBinding(status) {
|
|
1056
|
+
const selectionRequestId = this._bindingSelectionRequestId;
|
|
1057
|
+
this._bindingSelectionRequestId = void 0;
|
|
1058
|
+
if (selectionRequestId)
|
|
1059
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
1060
|
+
type: import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
1061
|
+
payload: { selectionRequestId, status }
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
_isBleConnection() {
|
|
1065
|
+
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
1066
|
+
}
|
|
867
1067
|
// Transport
|
|
868
1068
|
get activeTransport() {
|
|
869
|
-
return
|
|
1069
|
+
return this._isBleConnection() ? "ble" : "hid";
|
|
870
1070
|
}
|
|
871
1071
|
getAvailableTransports() {
|
|
1072
|
+
if (this.connector.availableTransports) {
|
|
1073
|
+
return this.connector.availableTransports.map(
|
|
1074
|
+
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
872
1077
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
873
1078
|
}
|
|
874
1079
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -885,21 +1090,39 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
885
1090
|
* The next operation will re-discover and re-connect automatically.
|
|
886
1091
|
*/
|
|
887
1092
|
resetState() {
|
|
1093
|
+
void this._resetStateAndDisconnectSessions();
|
|
1094
|
+
}
|
|
1095
|
+
_resetStateAndDisconnectSessions() {
|
|
1096
|
+
if (this._resetPromise) return this._resetPromise;
|
|
1097
|
+
const sessionIds = new Set(this._sessions.values());
|
|
1098
|
+
this._stateGeneration += 1;
|
|
1099
|
+
this._finishBleBinding("cancelled");
|
|
1100
|
+
this._operations.endAll("runtime-reset");
|
|
1101
|
+
this._doConnectAbortController?.abort();
|
|
888
1102
|
this._discoveredDevices.clear();
|
|
889
1103
|
this._sessions.clear();
|
|
1104
|
+
this._verifiedBleReconnectTargets.clear();
|
|
890
1105
|
this._connectingPromise = null;
|
|
1106
|
+
this._doConnectAbortController = null;
|
|
891
1107
|
this._uiRegistry.reset();
|
|
892
1108
|
this._jobQueue.clear();
|
|
893
1109
|
this._btcHighIndexConfirmedThisSession = false;
|
|
1110
|
+
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1111
|
+
for (const sessionId of sessionIds) {
|
|
1112
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
this._resetPromise = resetPromise;
|
|
1116
|
+
return resetPromise.finally(() => {
|
|
1117
|
+
if (this._resetPromise === resetPromise) {
|
|
1118
|
+
this._resetPromise = null;
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
894
1121
|
}
|
|
895
1122
|
async dispose() {
|
|
896
|
-
this.
|
|
897
|
-
this._jobQueue.clear();
|
|
1123
|
+
await this._resetStateAndDisconnectSessions();
|
|
898
1124
|
this.unregisterEventListeners();
|
|
899
1125
|
this.connector.reset();
|
|
900
|
-
this._discoveredDevices.clear();
|
|
901
|
-
this._sessions.clear();
|
|
902
|
-
this._btcHighIndexConfirmedThisSession = false;
|
|
903
1126
|
this.emitter.removeAllListeners();
|
|
904
1127
|
}
|
|
905
1128
|
uiResponse(response) {
|
|
@@ -909,17 +1132,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
909
1132
|
// Device management
|
|
910
1133
|
// ---------------------------------------------------------------------------
|
|
911
1134
|
async searchDevices(options) {
|
|
1135
|
+
return this._searchDevices(options);
|
|
1136
|
+
}
|
|
1137
|
+
async _searchDevices(options, signal) {
|
|
912
1138
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
913
1139
|
try {
|
|
914
1140
|
if (options?.resetSession) {
|
|
915
|
-
this.
|
|
916
|
-
|
|
917
|
-
this.
|
|
918
|
-
|
|
919
|
-
|
|
1141
|
+
await this._resetStateAndDisconnectSessions();
|
|
1142
|
+
} else {
|
|
1143
|
+
await this._connectorTeardownTail;
|
|
1144
|
+
}
|
|
1145
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1146
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1147
|
+
const stateGeneration = this._stateGeneration;
|
|
1148
|
+
const devices = await this.connector.searchDevices(
|
|
1149
|
+
options?.transportType ? {
|
|
1150
|
+
transportType: options.transportType,
|
|
1151
|
+
waitForAll: options.waitForAllTransports
|
|
1152
|
+
} : void 0
|
|
1153
|
+
);
|
|
1154
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1155
|
+
if (stateGeneration !== this._stateGeneration) {
|
|
1156
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1157
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
1158
|
+
message: "Ledger discovery was reset"
|
|
1159
|
+
});
|
|
920
1160
|
}
|
|
921
|
-
await this._ensureDevicePermission();
|
|
922
|
-
const devices = await this.connector.searchDevices();
|
|
923
1161
|
this._discoveredDevices.clear();
|
|
924
1162
|
for (const d of devices) {
|
|
925
1163
|
if (d.connectId) {
|
|
@@ -927,7 +1165,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
927
1165
|
}
|
|
928
1166
|
}
|
|
929
1167
|
if (this._discoveredDevices.size === 0) {
|
|
930
|
-
await this._ensureDevicePermission();
|
|
1168
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
931
1169
|
}
|
|
932
1170
|
const result = Array.from(this._discoveredDevices.values());
|
|
933
1171
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -946,41 +1184,287 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
946
1184
|
throw err;
|
|
947
1185
|
}
|
|
948
1186
|
}
|
|
1187
|
+
async searchDeviceTargets(options) {
|
|
1188
|
+
const devices = await this.searchDevices(options);
|
|
1189
|
+
return devices.map((device) => ({
|
|
1190
|
+
searchTargetId: device.connectId,
|
|
1191
|
+
searchTargetReusePolicy: (0, import_hwk_adapter_core3.resolveSearchTargetReusePolicy)(device),
|
|
1192
|
+
vendor: "ledger",
|
|
1193
|
+
connectionType: device.connectionType,
|
|
1194
|
+
kind: "physical",
|
|
1195
|
+
label: device.label,
|
|
1196
|
+
model: device.model,
|
|
1197
|
+
modelName: device.modelName,
|
|
1198
|
+
serialNumber: device.serialNumber
|
|
1199
|
+
}));
|
|
1200
|
+
}
|
|
1201
|
+
async listConnectionTargets(options) {
|
|
1202
|
+
const targets = await this.searchDeviceTargets(options);
|
|
1203
|
+
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1204
|
+
...target,
|
|
1205
|
+
targetId: searchTargetId
|
|
1206
|
+
}));
|
|
1207
|
+
}
|
|
949
1208
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
950
|
-
async _evictAllSessions() {
|
|
1209
|
+
async _evictAllSessions(preserveOperationId) {
|
|
1210
|
+
this._operations.endAll("explicit", preserveOperationId);
|
|
951
1211
|
if (this._sessions.size === 0) return;
|
|
952
1212
|
const stale = [...this._sessions.values()];
|
|
953
1213
|
this._sessions.clear();
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1214
|
+
await this._runConnectorTeardown(async () => {
|
|
1215
|
+
for (const sid of stale) {
|
|
1216
|
+
try {
|
|
1217
|
+
await this.connector.disconnect(sid);
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
958
1220
|
}
|
|
959
|
-
}
|
|
1221
|
+
});
|
|
960
1222
|
}
|
|
961
1223
|
static _createDeviceBusyError(method) {
|
|
962
1224
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
963
1225
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceBusy
|
|
964
1226
|
});
|
|
965
1227
|
}
|
|
966
|
-
async connectDevice(
|
|
1228
|
+
async connectDevice(searchTargetId) {
|
|
1229
|
+
try {
|
|
1230
|
+
return await this._jobQueue.enqueue(
|
|
1231
|
+
searchTargetId || "__ledger_connect__",
|
|
1232
|
+
async () => {
|
|
1233
|
+
const connected = await this._connectTarget(searchTargetId);
|
|
1234
|
+
if (!connected.success) return connected;
|
|
1235
|
+
return this._createOperation(searchTargetId, connected.payload);
|
|
1236
|
+
},
|
|
1237
|
+
{
|
|
1238
|
+
label: "connectDevice",
|
|
1239
|
+
rejectIfBusy: true,
|
|
1240
|
+
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1241
|
+
}
|
|
1242
|
+
);
|
|
1243
|
+
} catch (error) {
|
|
1244
|
+
return this.errorToFailure(error);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
async bindBleDevice(params) {
|
|
1248
|
+
if (params.identity.vendor !== "ledger" || !params.identity.value) {
|
|
1249
|
+
return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "Ledger wallet identity is required");
|
|
1250
|
+
}
|
|
1251
|
+
const { chain, value: expectedFingerprint } = params.identity;
|
|
1252
|
+
try {
|
|
1253
|
+
return await this._jobQueue.enqueue(
|
|
1254
|
+
expectedFingerprint,
|
|
1255
|
+
async (signal) => {
|
|
1256
|
+
if (!this.getAvailableTransports().includes("ble")) {
|
|
1257
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1258
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
1259
|
+
message: "Ledger Bluetooth transport is not available"
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
this._activeConnectionType = "ble";
|
|
1263
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1264
|
+
const attempt = {
|
|
1265
|
+
extra: params.extra,
|
|
1266
|
+
bindingReason: "manual-rebind"
|
|
1267
|
+
};
|
|
1268
|
+
try {
|
|
1269
|
+
for (; ; ) {
|
|
1270
|
+
const connectId = await this._connectFirstOrSelect(
|
|
1271
|
+
[],
|
|
1272
|
+
void 0,
|
|
1273
|
+
true,
|
|
1274
|
+
void 0,
|
|
1275
|
+
attempt,
|
|
1276
|
+
signal
|
|
1277
|
+
);
|
|
1278
|
+
const sessionId = this._sessions.get(connectId);
|
|
1279
|
+
if (!sessionId) {
|
|
1280
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1281
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
1282
|
+
message: "Selected Ledger connection ended"
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
let saved = false;
|
|
1286
|
+
try {
|
|
1287
|
+
const installContext = {
|
|
1288
|
+
connection: { connectId, sessionId }
|
|
1289
|
+
};
|
|
1290
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1291
|
+
chain,
|
|
1292
|
+
(method, callParams) => this._runConnectorCall(
|
|
1293
|
+
connectId,
|
|
1294
|
+
method,
|
|
1295
|
+
callParams,
|
|
1296
|
+
signal,
|
|
1297
|
+
void 0,
|
|
1298
|
+
void 0,
|
|
1299
|
+
{ autoInstallApp: true },
|
|
1300
|
+
installContext
|
|
1301
|
+
)
|
|
1302
|
+
);
|
|
1303
|
+
if (fingerprint !== expectedFingerprint) {
|
|
1304
|
+
attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
1305
|
+
attempt.rejectedConnectIds.add(connectId);
|
|
1306
|
+
attempt.rejectedConnectId = connectId;
|
|
1307
|
+
} else {
|
|
1308
|
+
const persisted = await this._publishVerifiedBleBinding(
|
|
1309
|
+
connectId,
|
|
1310
|
+
chain,
|
|
1311
|
+
fingerprint,
|
|
1312
|
+
attempt,
|
|
1313
|
+
void 0,
|
|
1314
|
+
signal
|
|
1315
|
+
);
|
|
1316
|
+
if (!persisted) {
|
|
1317
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1318
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UnknownError,
|
|
1319
|
+
message: "Bluetooth binding could not be saved",
|
|
1320
|
+
origin: "host"
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
saved = true;
|
|
1324
|
+
return (0, import_hwk_adapter_core3.success)(connectId);
|
|
1325
|
+
}
|
|
1326
|
+
} finally {
|
|
1327
|
+
if (!saved && this._sessions.get(connectId) === sessionId) {
|
|
1328
|
+
this._sessions.delete(connectId);
|
|
1329
|
+
const teardown = this._runConnectorTeardown(
|
|
1330
|
+
() => this.connector.disconnect(sessionId)
|
|
1331
|
+
).catch(() => void 0);
|
|
1332
|
+
if (!signal.aborted) await teardown;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1338
|
+
throw error;
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
label: "bindBleDevice",
|
|
1343
|
+
rejectIfBusy: true,
|
|
1344
|
+
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
return this.errorToFailure(error);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async acquireOperation(connectId, context) {
|
|
1352
|
+
try {
|
|
1353
|
+
return await this._jobQueue.enqueue(
|
|
1354
|
+
connectId || "__ledger_acquire__",
|
|
1355
|
+
async (signal) => {
|
|
1356
|
+
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1357
|
+
const hint = context.knownConnections?.find(
|
|
1358
|
+
(connection) => connection.transport === transport
|
|
1359
|
+
);
|
|
1360
|
+
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1361
|
+
await this._ensureDevicePermission(target, void 0, signal);
|
|
1362
|
+
const attempt = {
|
|
1363
|
+
...context,
|
|
1364
|
+
extra: context.extra ? { ...context.extra } : void 0
|
|
1365
|
+
};
|
|
1366
|
+
try {
|
|
1367
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1368
|
+
target,
|
|
1369
|
+
signal,
|
|
1370
|
+
true,
|
|
1371
|
+
void 0,
|
|
1372
|
+
attempt
|
|
1373
|
+
);
|
|
1374
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1375
|
+
const result = this._createOperation(connectId, resolvedConnectId);
|
|
1376
|
+
if (result.success && attempt.selectedConnection) {
|
|
1377
|
+
this._pendingOperationBindings.set(result.payload, attempt);
|
|
1378
|
+
}
|
|
1379
|
+
return result;
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1382
|
+
throw error;
|
|
1383
|
+
}
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
label: "acquireOperation",
|
|
1387
|
+
rejectIfBusy: true,
|
|
1388
|
+
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1389
|
+
}
|
|
1390
|
+
);
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
return this.errorToFailure(error);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1396
|
+
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1397
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1398
|
+
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1399
|
+
const connectionType = this._isBleConnection() ? "ble" : "usb";
|
|
1400
|
+
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1401
|
+
vendor: "ledger",
|
|
1402
|
+
model: "unknown",
|
|
1403
|
+
firmwareVersion: "",
|
|
1404
|
+
deviceId: "",
|
|
1405
|
+
connectId: resolvedConnectId,
|
|
1406
|
+
connectionType
|
|
1407
|
+
};
|
|
1408
|
+
const operation = this._operations.create({
|
|
1409
|
+
searchTargetId,
|
|
1410
|
+
connectId: resolvedConnectId,
|
|
1411
|
+
device,
|
|
1412
|
+
connectionType,
|
|
1413
|
+
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1414
|
+
});
|
|
1415
|
+
return (0, import_hwk_adapter_core3.success)(operation.operationId);
|
|
1416
|
+
}
|
|
1417
|
+
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
967
1418
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
968
1419
|
try {
|
|
969
|
-
|
|
1420
|
+
this._assertConnectorReady("connectDevice");
|
|
1421
|
+
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1422
|
+
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1423
|
+
this._activeConnectionType = discoveredType;
|
|
1424
|
+
}
|
|
1425
|
+
if (this._isBleConnection() && !connectId) {
|
|
970
1426
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
971
1427
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
972
1428
|
});
|
|
973
1429
|
}
|
|
974
|
-
|
|
975
|
-
|
|
1430
|
+
await this._ensureDevicePermission(connectId, void 0, signal);
|
|
1431
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1432
|
+
if (this.connector.availableTransports && this.connector.availableTransports.length > 1) {
|
|
1433
|
+
await this._evictAllSessions(preserveOperationId);
|
|
1434
|
+
} else if (this._isBleConnection()) {
|
|
1435
|
+
const previousSessionId = this._sessions.get(connectId);
|
|
1436
|
+
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1437
|
+
if (previousSessionId) {
|
|
1438
|
+
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1439
|
+
this._sessions.delete(connectId);
|
|
1440
|
+
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1441
|
+
}
|
|
1442
|
+
} else {
|
|
1443
|
+
await this._evictAllSessions(preserveOperationId);
|
|
1444
|
+
}
|
|
1445
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1446
|
+
const stateGeneration = this._stateGeneration;
|
|
1447
|
+
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1448
|
+
let session;
|
|
1449
|
+
try {
|
|
1450
|
+
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1451
|
+
transportType: this._activeConnectionType ?? "usb"
|
|
1452
|
+
}) : await this.connector.connect(connectId);
|
|
1453
|
+
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1454
|
+
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1455
|
+
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1456
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
} finally {
|
|
1460
|
+
releaseOperation();
|
|
976
1461
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
this._sessions.set(connectId, session.sessionId);
|
|
1462
|
+
const resolvedConnectId = session.deviceInfo?.connectId || connectId;
|
|
1463
|
+
this._sessions.set(resolvedConnectId, session.sessionId);
|
|
980
1464
|
if (session.deviceInfo) {
|
|
981
|
-
this._discoveredDevices.set(
|
|
1465
|
+
this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
|
|
982
1466
|
}
|
|
983
|
-
const result = (0, import_hwk_adapter_core3.success)(
|
|
1467
|
+
const result = (0, import_hwk_adapter_core3.success)(resolvedConnectId);
|
|
984
1468
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
985
1469
|
return result;
|
|
986
1470
|
} catch (err) {
|
|
@@ -993,30 +1477,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
993
1477
|
return failureResult;
|
|
994
1478
|
}
|
|
995
1479
|
}
|
|
996
|
-
async
|
|
1480
|
+
async releaseOperation(operationId) {
|
|
1481
|
+
const operation = this._operations.find(operationId);
|
|
1482
|
+
if (!operation) {
|
|
1483
|
+
this._operations.resolve(operationId);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1487
|
+
if (!endedOperation) return;
|
|
1488
|
+
const { connectId } = operation;
|
|
997
1489
|
debugLog("[LedgerAdapter][REQ]", {
|
|
998
|
-
method: "
|
|
1490
|
+
method: "releaseOperation",
|
|
999
1491
|
connectId,
|
|
1000
1492
|
params: { connectId }
|
|
1001
1493
|
});
|
|
1002
1494
|
try {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
await this.connector.disconnect(sessionId);
|
|
1006
|
-
this._sessions.delete(connectId);
|
|
1007
|
-
}
|
|
1008
|
-
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1495
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1496
|
+
debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
|
|
1009
1497
|
} catch (err) {
|
|
1010
1498
|
const e = err;
|
|
1011
1499
|
debugLog("[LedgerAdapter][RES]", {
|
|
1012
|
-
method: "
|
|
1500
|
+
method: "releaseOperation",
|
|
1013
1501
|
success: false,
|
|
1014
1502
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1015
1503
|
});
|
|
1016
1504
|
throw err;
|
|
1017
1505
|
}
|
|
1018
1506
|
}
|
|
1019
|
-
async
|
|
1507
|
+
async _releaseOperationConnection(operation) {
|
|
1508
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
1509
|
+
for (const [connectId, sessionId] of this._sessions) {
|
|
1510
|
+
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1511
|
+
this._sessions.delete(connectId);
|
|
1512
|
+
sessionIds.add(sessionId);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
for (const sessionId of sessionIds) {
|
|
1516
|
+
await this._runConnectorTeardown(
|
|
1517
|
+
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
async _releaseLostOperationConnection(operationId) {
|
|
1522
|
+
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1523
|
+
if (!endedOperation) return;
|
|
1524
|
+
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1525
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1526
|
+
}
|
|
1527
|
+
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1528
|
+
let connectId;
|
|
1529
|
+
try {
|
|
1530
|
+
connectId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
return this.errorToFailure(error);
|
|
1533
|
+
}
|
|
1020
1534
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1021
1535
|
method: "getDeviceInfo",
|
|
1022
1536
|
connectId,
|
|
@@ -1054,9 +1568,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1054
1568
|
throw err;
|
|
1055
1569
|
}
|
|
1056
1570
|
}
|
|
1057
|
-
getSupportedChains() {
|
|
1058
|
-
return ["evm", "btc", "sol", "tron"];
|
|
1059
|
-
}
|
|
1060
1571
|
// ---------------------------------------------------------------------------
|
|
1061
1572
|
// Chain call helper
|
|
1062
1573
|
// ---------------------------------------------------------------------------
|
|
@@ -1091,13 +1602,21 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1091
1602
|
if (params && typeof params === "object") {
|
|
1092
1603
|
const {
|
|
1093
1604
|
autoInstallApp,
|
|
1605
|
+
operationId,
|
|
1094
1606
|
passphraseState: _passphraseState,
|
|
1095
1607
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1608
|
+
knownConnections,
|
|
1609
|
+
extra,
|
|
1610
|
+
allowDeviceSelection,
|
|
1096
1611
|
...rest
|
|
1097
1612
|
} = params;
|
|
1098
1613
|
return {
|
|
1099
1614
|
commonParams: {
|
|
1100
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1615
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
|
|
1616
|
+
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1617
|
+
knownConnections,
|
|
1618
|
+
extra,
|
|
1619
|
+
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0
|
|
1101
1620
|
},
|
|
1102
1621
|
rest
|
|
1103
1622
|
};
|
|
@@ -1273,6 +1792,27 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1273
1792
|
);
|
|
1274
1793
|
}
|
|
1275
1794
|
// ---------------------------------------------------------------------------
|
|
1795
|
+
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1796
|
+
// ---------------------------------------------------------------------------
|
|
1797
|
+
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
1798
|
+
return this.callChainWithMergedParams(
|
|
1799
|
+
connectId,
|
|
1800
|
+
deviceId,
|
|
1801
|
+
"zcash",
|
|
1802
|
+
"zcashGetFullViewingKey",
|
|
1803
|
+
params
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1807
|
+
return this.callChainWithMergedParams(
|
|
1808
|
+
connectId,
|
|
1809
|
+
deviceId,
|
|
1810
|
+
"zcash",
|
|
1811
|
+
"zcashGetShieldedAddress",
|
|
1812
|
+
params
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
// ---------------------------------------------------------------------------
|
|
1276
1816
|
// App management — OS-level Ledger app install / list. Bypasses fingerprint
|
|
1277
1817
|
// and chain-handler dispatch; installApp progress is forwarded to the adapter
|
|
1278
1818
|
// emitter via 'ui-event' AppInstallProgress events.
|
|
@@ -1361,7 +1901,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1361
1901
|
);
|
|
1362
1902
|
}
|
|
1363
1903
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1364
|
-
this.
|
|
1904
|
+
await this._resetStateAndDisconnectSessions();
|
|
1365
1905
|
}
|
|
1366
1906
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1367
1907
|
if (!result.isGenuine) {
|
|
@@ -1387,10 +1927,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1387
1927
|
if (relayUrl) {
|
|
1388
1928
|
try {
|
|
1389
1929
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1390
|
-
this.
|
|
1930
|
+
await this._resetStateAndDisconnectSessions();
|
|
1391
1931
|
} catch {
|
|
1392
1932
|
this.connector.reset();
|
|
1393
|
-
this.
|
|
1933
|
+
await this._resetStateAndDisconnectSessions();
|
|
1394
1934
|
}
|
|
1395
1935
|
}
|
|
1396
1936
|
}
|
|
@@ -1402,6 +1942,20 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1402
1942
|
this.emitter.off(event, listener);
|
|
1403
1943
|
}
|
|
1404
1944
|
cancel(connectId) {
|
|
1945
|
+
const namedOperationIsLive = (id) => {
|
|
1946
|
+
try {
|
|
1947
|
+
this._operations.resolve(id);
|
|
1948
|
+
return true;
|
|
1949
|
+
} catch {
|
|
1950
|
+
return false;
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) && !namedOperationIsLive(connectId)) {
|
|
1954
|
+
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1955
|
+
connectId
|
|
1956
|
+
});
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1405
1959
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1406
1960
|
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
1407
1961
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1412,14 +1966,50 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1412
1966
|
this._lastCancelReason = void 0;
|
|
1413
1967
|
}
|
|
1414
1968
|
}, 2e3);
|
|
1415
|
-
this.
|
|
1416
|
-
|
|
1417
|
-
|
|
1969
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1970
|
+
let operationId;
|
|
1971
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId)) {
|
|
1972
|
+
operationId = connectId;
|
|
1973
|
+
} else if (!connectId && (0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) {
|
|
1974
|
+
operationId = activeJobId;
|
|
1975
|
+
}
|
|
1976
|
+
let resolvedConnectId = connectId;
|
|
1977
|
+
if (operationId) {
|
|
1978
|
+
try {
|
|
1979
|
+
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1980
|
+
} catch {
|
|
1981
|
+
resolvedConnectId = void 0;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1985
|
+
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1986
|
+
if (!connectId) {
|
|
1987
|
+
this._uiRegistry.cancel();
|
|
1988
|
+
} else if (pendingOperationId) {
|
|
1989
|
+
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1990
|
+
}
|
|
1991
|
+
this._finishBleBinding("cancelled");
|
|
1992
|
+
if (!connectId) this._pendingOperationBindings.clear();
|
|
1993
|
+
else if (pendingOperationId) {
|
|
1994
|
+
this._pendingOperationBindings.delete(pendingOperationId);
|
|
1995
|
+
}
|
|
1996
|
+
const queueKeys = /* @__PURE__ */ new Set();
|
|
1997
|
+
if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
|
|
1998
|
+
if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
|
|
1999
|
+
if (queueKeys.size) {
|
|
2000
|
+
let cancelledAnyJob = false;
|
|
2001
|
+
for (const key of queueKeys) {
|
|
2002
|
+
cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
|
|
2003
|
+
}
|
|
2004
|
+
debugLog("[LedgerAdapter] cancel routed to queue keys", {
|
|
2005
|
+
queueKeys: [...queueKeys],
|
|
2006
|
+
cancelledAnyJob
|
|
2007
|
+
});
|
|
1418
2008
|
} else {
|
|
1419
2009
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1420
2010
|
}
|
|
1421
|
-
if (
|
|
1422
|
-
const sessionId = this._sessions.get(
|
|
2011
|
+
if (resolvedConnectId) {
|
|
2012
|
+
const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
|
|
1423
2013
|
void this.connector.cancel(sessionId);
|
|
1424
2014
|
} else {
|
|
1425
2015
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1431,44 +2021,102 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1431
2021
|
// ---------------------------------------------------------------------------
|
|
1432
2022
|
// Chain fingerprint
|
|
1433
2023
|
// ---------------------------------------------------------------------------
|
|
2024
|
+
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1434
2025
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1435
2026
|
try {
|
|
1436
2027
|
const fingerprint = await this._computeChainFingerprint(
|
|
1437
2028
|
chain,
|
|
1438
2029
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1439
2030
|
);
|
|
2031
|
+
if (deviceId) {
|
|
2032
|
+
if (fingerprint !== deviceId) {
|
|
2033
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
2034
|
+
import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
2035
|
+
formatDeviceMismatchError(deviceId, fingerprint)
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId)) {
|
|
2039
|
+
const operation = this._operations.resolve(connectId);
|
|
2040
|
+
await this._publishVerifiedBleBinding(
|
|
2041
|
+
operation.connectId,
|
|
2042
|
+
chain,
|
|
2043
|
+
fingerprint,
|
|
2044
|
+
void 0,
|
|
2045
|
+
connectId
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
1440
2049
|
return (0, import_hwk_adapter_core3.success)(fingerprint);
|
|
1441
2050
|
} catch (err) {
|
|
1442
2051
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
1443
2052
|
return this.errorToFailure(err);
|
|
1444
2053
|
}
|
|
1445
2054
|
}
|
|
1446
|
-
/**
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
2055
|
+
/** Discovery may select a BLE target before a later call verifies its wallet. */
|
|
2056
|
+
async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, operationId, signal) {
|
|
2057
|
+
const binding = operationId ? this._pendingOperationBindings.get(operationId) : attempt;
|
|
2058
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) {
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
1452
2061
|
try {
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
|
|
2062
|
+
const outcome = await (0, import_hwk_adapter_core3.requestSaveDeviceBinding)(
|
|
2063
|
+
this.emitter,
|
|
2064
|
+
this._uiRegistry,
|
|
2065
|
+
{
|
|
2066
|
+
selectionRequestId: binding.selectedConnection.requestId,
|
|
2067
|
+
connection: { transport: "ble", connectId },
|
|
2068
|
+
identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
|
|
2069
|
+
extra: binding.extra,
|
|
2070
|
+
operationId
|
|
2071
|
+
},
|
|
2072
|
+
signal
|
|
1456
2073
|
);
|
|
1457
|
-
if (
|
|
1458
|
-
|
|
2074
|
+
if (operationId) this._operations.resolve(operationId);
|
|
2075
|
+
if (!outcome.saved) {
|
|
2076
|
+
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
2077
|
+
connectId,
|
|
2078
|
+
reason: outcome.reason
|
|
2079
|
+
});
|
|
1459
2080
|
}
|
|
1460
|
-
return
|
|
1461
|
-
} catch (
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
2081
|
+
return outcome.saved;
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
if (operationId) {
|
|
2084
|
+
this._pendingOperationBindings.delete(operationId);
|
|
2085
|
+
this._operations.end(operationId, "explicit");
|
|
2086
|
+
}
|
|
2087
|
+
const sessionId = this._sessions.get(connectId);
|
|
2088
|
+
this._sessions.delete(connectId);
|
|
2089
|
+
if (sessionId) {
|
|
2090
|
+
const teardown = this._runConnectorTeardown(
|
|
2091
|
+
() => this.connector.disconnect(sessionId)
|
|
2092
|
+
).catch(() => void 0);
|
|
2093
|
+
if (!signal?.aborted) await teardown;
|
|
2094
|
+
}
|
|
2095
|
+
throw error;
|
|
2096
|
+
} finally {
|
|
2097
|
+
if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
|
|
2098
|
+
this._bindingSelectionRequestId = void 0;
|
|
2099
|
+
}
|
|
2100
|
+
if (operationId) {
|
|
2101
|
+
this._pendingOperationBindings.delete(operationId);
|
|
1465
2102
|
}
|
|
1466
|
-
throw err;
|
|
1467
2103
|
}
|
|
1468
2104
|
}
|
|
1469
|
-
/**
|
|
1470
|
-
|
|
1471
|
-
|
|
2105
|
+
/** Verify on the acquired session without re-entering the job queue. */
|
|
2106
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
2107
|
+
if (!deviceId) return { success: true };
|
|
2108
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
2109
|
+
chain,
|
|
2110
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
2111
|
+
);
|
|
2112
|
+
if (fingerprint === deviceId) {
|
|
2113
|
+
return { success: true };
|
|
2114
|
+
}
|
|
2115
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
2119
|
+
*
|
|
1472
2120
|
* Chains with a native device-side identity primitive (BTC → BIP32 master
|
|
1473
2121
|
* fingerprint) short-circuit at the top and return it verbatim, so the value
|
|
1474
2122
|
* stays reusable for higher-level use (BIP380 descriptors, PSBT signing).
|
|
@@ -1493,12 +2141,27 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1493
2141
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
1494
2142
|
} else if (chain === "tron") {
|
|
1495
2143
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2144
|
+
} else if (chain === "zcash") {
|
|
2145
|
+
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
1496
2146
|
} else {
|
|
1497
2147
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
1498
2148
|
}
|
|
1499
2149
|
return (0, import_hwk_adapter_core3.deriveDeviceFingerprint)(address);
|
|
1500
2150
|
}
|
|
1501
2151
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
2152
|
+
/**
|
|
2153
|
+
* The operation the running device job belongs to, so a UI request raised
|
|
2154
|
+
* mid-call can name it. A call pinned to an operation queues under its id
|
|
2155
|
+
* (`ledgerQueueKey`); a call without one queues under the connectId, and the
|
|
2156
|
+
* live operation on that connection is still the owner. Only work with no
|
|
2157
|
+
* operation at all (cold start) comes back undefined.
|
|
2158
|
+
*/
|
|
2159
|
+
_activeOperationId() {
|
|
2160
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
2161
|
+
if (!activeJobId) return void 0;
|
|
2162
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) return activeJobId;
|
|
2163
|
+
return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
|
|
2164
|
+
}
|
|
1502
2165
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
1503
2166
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
1504
2167
|
// registry slot is released and a stale RECEIVE_DEVICE_CONNECT won't land in
|
|
@@ -1507,15 +2170,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1507
2170
|
if (signal?.aborted) {
|
|
1508
2171
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1509
2172
|
}
|
|
2173
|
+
const operationId = this._activeOperationId();
|
|
1510
2174
|
const waitPromise = this._uiRegistry.wait(
|
|
1511
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2175
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2176
|
+
{ operationId }
|
|
1512
2177
|
);
|
|
1513
2178
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
1514
2179
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
1515
2180
|
payload: {
|
|
1516
2181
|
vendor: "ledger",
|
|
1517
2182
|
reason: "device-not-found",
|
|
1518
|
-
message: "Please connect and unlock your Ledger device"
|
|
2183
|
+
message: "Please connect and unlock your Ledger device",
|
|
2184
|
+
operationId
|
|
1519
2185
|
}
|
|
1520
2186
|
});
|
|
1521
2187
|
let payload;
|
|
@@ -1576,15 +2242,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1576
2242
|
return { ...params, showOnDevice: true };
|
|
1577
2243
|
}
|
|
1578
2244
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2245
|
+
const operationId = this._activeOperationId();
|
|
1579
2246
|
const waitPromise = this._uiRegistry.wait(
|
|
1580
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2247
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2248
|
+
{ operationId }
|
|
1581
2249
|
);
|
|
1582
2250
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
1583
2251
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
1584
2252
|
payload: {
|
|
1585
2253
|
vendor: "ledger",
|
|
1586
2254
|
path,
|
|
1587
|
-
accountIndex
|
|
2255
|
+
accountIndex,
|
|
2256
|
+
operationId
|
|
1588
2257
|
}
|
|
1589
2258
|
});
|
|
1590
2259
|
try {
|
|
@@ -1601,12 +2270,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1601
2270
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
1602
2271
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
1603
2272
|
async _waitForInstallAppConfirm(appName) {
|
|
2273
|
+
const operationId = this._activeOperationId();
|
|
1604
2274
|
const waitPromise = this._uiRegistry.wait(
|
|
1605
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP
|
|
2275
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2276
|
+
{ operationId }
|
|
1606
2277
|
);
|
|
1607
2278
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
1608
2279
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
|
|
1609
|
-
payload: { vendor: "ledger", appName }
|
|
2280
|
+
payload: { vendor: "ledger", appName, operationId }
|
|
1610
2281
|
});
|
|
1611
2282
|
try {
|
|
1612
2283
|
const payload = await waitPromise;
|
|
@@ -1622,16 +2293,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1622
2293
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
1623
2294
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
1624
2295
|
// doesn't kill caller B's await.
|
|
1625
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2296
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
1626
2297
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
1627
|
-
|
|
2298
|
+
this._assertConnectorReady("connectDevice");
|
|
2299
|
+
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1628
2300
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1629
2301
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1630
2302
|
});
|
|
1631
2303
|
}
|
|
1632
2304
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
1633
2305
|
if (!connectId && this._sessions.size > 0) {
|
|
1634
|
-
if (!
|
|
2306
|
+
if (!this._isBleConnection() && this._sessions.size > 1) {
|
|
1635
2307
|
throw Object.assign(
|
|
1636
2308
|
new Error(
|
|
1637
2309
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -1641,15 +2313,24 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1641
2313
|
}
|
|
1642
2314
|
return this._sessions.keys().next().value;
|
|
1643
2315
|
}
|
|
1644
|
-
if (!this._connectingPromise) {
|
|
1645
|
-
|
|
1646
|
-
|
|
2316
|
+
if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
|
|
2317
|
+
const controller = new AbortController();
|
|
2318
|
+
this._doConnectAbortController = controller;
|
|
2319
|
+
const innerSignal = controller.signal;
|
|
1647
2320
|
this._connectingPromise = (async () => {
|
|
1648
2321
|
try {
|
|
1649
|
-
return await this._doConnect(
|
|
2322
|
+
return await this._doConnect(
|
|
2323
|
+
innerSignal,
|
|
2324
|
+
connectId,
|
|
2325
|
+
allowUsbEphemeralFallback,
|
|
2326
|
+
preserveOperationId,
|
|
2327
|
+
context
|
|
2328
|
+
);
|
|
1650
2329
|
} finally {
|
|
1651
|
-
this.
|
|
1652
|
-
|
|
2330
|
+
if (this._doConnectAbortController === controller) {
|
|
2331
|
+
this._connectingPromise = null;
|
|
2332
|
+
this._doConnectAbortController = null;
|
|
2333
|
+
}
|
|
1653
2334
|
}
|
|
1654
2335
|
})();
|
|
1655
2336
|
}
|
|
@@ -1658,22 +2339,51 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1658
2339
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1659
2340
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1660
2341
|
// 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
|
-
|
|
2342
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
2343
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2344
|
+
if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
|
|
2345
|
+
this._activeConnectionType = "usb";
|
|
2346
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
|
|
2347
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2348
|
+
if (usbDevices.length > 0) {
|
|
2349
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2350
|
+
(connection) => connection.transport === "usb"
|
|
2351
|
+
);
|
|
2352
|
+
const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
|
|
2353
|
+
return this._connectFirstOrSelect(
|
|
2354
|
+
usbDevices,
|
|
2355
|
+
usbTarget,
|
|
2356
|
+
allowUsbEphemeralFallback,
|
|
2357
|
+
preserveOperationId,
|
|
2358
|
+
context,
|
|
2359
|
+
internalSignal
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
this._activeConnectionType = "ble";
|
|
2363
|
+
const knownBle = context?.knownConnections?.find(
|
|
2364
|
+
(connection) => connection.transport === "ble"
|
|
2365
|
+
);
|
|
2366
|
+
if (knownBle?.transport === "ble") {
|
|
2367
|
+
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2368
|
+
}
|
|
2369
|
+
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2370
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2371
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2372
|
+
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2373
|
+
});
|
|
1676
2374
|
}
|
|
2375
|
+
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2376
|
+
return this._connectFirstOrSelect(
|
|
2377
|
+
bleDevices,
|
|
2378
|
+
void 0,
|
|
2379
|
+
allowUsbEphemeralFallback,
|
|
2380
|
+
preserveOperationId,
|
|
2381
|
+
context,
|
|
2382
|
+
internalSignal
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
if (this._isBleConnection() && targetConnectId) {
|
|
2386
|
+
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
1677
2387
|
}
|
|
1678
2388
|
let confirms = 0;
|
|
1679
2389
|
while (!internalSignal.aborted) {
|
|
@@ -1681,22 +2391,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1681
2391
|
type: import_hwk_adapter_core3.EConnectorInteraction.Searching,
|
|
1682
2392
|
payload: { sessionId: "" }
|
|
1683
2393
|
});
|
|
1684
|
-
let devices = await this.
|
|
2394
|
+
let devices = await this._searchDevices(void 0, internalSignal);
|
|
2395
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1685
2396
|
if (devices.length === 0) {
|
|
1686
2397
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1687
2398
|
await new Promise((resolve) => {
|
|
1688
2399
|
setTimeout(resolve, import_hwk_adapter_core3.DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
1689
2400
|
});
|
|
1690
|
-
|
|
2401
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2402
|
+
devices = await this._searchDevices(void 0, internalSignal);
|
|
2403
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1691
2404
|
if (devices.length > 0) break;
|
|
1692
2405
|
}
|
|
1693
2406
|
}
|
|
1694
|
-
if (devices.length > 0) {
|
|
2407
|
+
if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
|
|
1695
2408
|
try {
|
|
1696
2409
|
return await this._connectFirstOrSelect(
|
|
1697
2410
|
devices,
|
|
1698
2411
|
targetConnectId,
|
|
1699
|
-
allowUsbEphemeralFallback
|
|
2412
|
+
allowUsbEphemeralFallback,
|
|
2413
|
+
preserveOperationId,
|
|
2414
|
+
context,
|
|
2415
|
+
internalSignal
|
|
1700
2416
|
);
|
|
1701
2417
|
} catch (err) {
|
|
1702
2418
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -1725,45 +2441,136 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1725
2441
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1726
2442
|
throw new Error("_doConnect aborted");
|
|
1727
2443
|
}
|
|
1728
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2444
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
|
|
2445
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1729
2446
|
if (targetConnectId) {
|
|
1730
2447
|
const target = devices.find(
|
|
1731
2448
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1732
2449
|
);
|
|
1733
2450
|
if (target) {
|
|
1734
|
-
return this._connectDeviceOrThrow(target.connectId);
|
|
2451
|
+
return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
|
|
1735
2452
|
}
|
|
1736
|
-
if (!
|
|
2453
|
+
if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
1737
2454
|
debugLog(
|
|
1738
2455
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
1739
2456
|
);
|
|
1740
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2457
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1741
2458
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2459
|
+
if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
|
|
2460
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
2461
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
2462
|
+
});
|
|
2463
|
+
if (this._isBleConnection()) {
|
|
2464
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2465
|
+
}
|
|
2466
|
+
throw err;
|
|
1747
2467
|
}
|
|
1748
|
-
throw err;
|
|
1749
2468
|
}
|
|
1750
|
-
|
|
2469
|
+
const requiresBleSelection = this._isBleConnection();
|
|
2470
|
+
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1751
2471
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1752
2472
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1753
2473
|
});
|
|
1754
2474
|
}
|
|
1755
|
-
if (
|
|
1756
|
-
|
|
2475
|
+
if (requiresBleSelection && context?.allowDeviceSelection !== false) {
|
|
2476
|
+
const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
|
|
2477
|
+
if (context) context.bindingSessionId = bindingSessionId;
|
|
2478
|
+
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2479
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2480
|
+
(connection) => connection.transport === "usb"
|
|
2481
|
+
);
|
|
2482
|
+
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2483
|
+
const { device, requestId } = await (0, import_hwk_adapter_core3.requestBleDeviceSelection)({
|
|
2484
|
+
emitter: this.emitter,
|
|
2485
|
+
registry: this._uiRegistry,
|
|
2486
|
+
signal,
|
|
2487
|
+
allowUsbFallback,
|
|
2488
|
+
scan: async () => {
|
|
2489
|
+
if (allowUsbFallback) {
|
|
2490
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2491
|
+
const candidate = usbDevices.find(
|
|
2492
|
+
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2493
|
+
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2494
|
+
if (candidate) return [candidate];
|
|
2495
|
+
}
|
|
2496
|
+
return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
|
|
2497
|
+
},
|
|
2498
|
+
request: {
|
|
2499
|
+
devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
|
|
2500
|
+
bindingSessionId,
|
|
2501
|
+
rejectedConnectId: context?.rejectedConnectId,
|
|
2502
|
+
context: {
|
|
2503
|
+
kind: "bind-connection",
|
|
2504
|
+
transport: "ble",
|
|
2505
|
+
reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
|
|
2506
|
+
},
|
|
2507
|
+
extra: context?.extra,
|
|
2508
|
+
operationId: preserveOperationId
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
if (device.connectionType === "usb") {
|
|
2512
|
+
this._activeConnectionType = "usb";
|
|
2513
|
+
if (context) context.selectedConnection = void 0;
|
|
2514
|
+
if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
|
|
2515
|
+
this._bindingSelectionRequestId = void 0;
|
|
2516
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
2517
|
+
type: import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
2518
|
+
payload: { selectionRequestId: requestId, status: "cancelled" }
|
|
2519
|
+
});
|
|
2520
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2521
|
+
}
|
|
2522
|
+
if (context) context.selectedConnection = { connectId: device.connectId, requestId };
|
|
2523
|
+
this._bindingSelectionRequestId = requestId;
|
|
2524
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2525
|
+
}
|
|
2526
|
+
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2527
|
+
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2528
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2529
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2530
|
+
message: "Select a Ledger device before continuing"
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
const requestId = this._uiRegistry.createRequestId();
|
|
2534
|
+
const operationId = this._activeOperationId();
|
|
2535
|
+
const waitPromise = this._uiRegistry.wait(
|
|
2536
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2537
|
+
{ requestId, operationId }
|
|
2538
|
+
);
|
|
2539
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2540
|
+
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2541
|
+
payload: {
|
|
2542
|
+
devices,
|
|
2543
|
+
requestId,
|
|
2544
|
+
operationId,
|
|
2545
|
+
context: requiresBleSelection ? {
|
|
2546
|
+
kind: "bind-connection",
|
|
2547
|
+
transport: "ble",
|
|
2548
|
+
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2549
|
+
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2550
|
+
extra: context?.extra
|
|
2551
|
+
}
|
|
2552
|
+
});
|
|
2553
|
+
const { sdkConnectId } = await (signal ? this._abortable(signal, waitPromise) : waitPromise);
|
|
2554
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
2555
|
+
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2556
|
+
if (!selected) {
|
|
2557
|
+
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2558
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
if (context && requiresBleSelection)
|
|
2562
|
+
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2563
|
+
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1757
2564
|
}
|
|
1758
2565
|
if (devices.length !== 1) {
|
|
1759
2566
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
1760
2567
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1761
2568
|
});
|
|
1762
2569
|
}
|
|
1763
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2570
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1764
2571
|
}
|
|
1765
|
-
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1766
|
-
const result = await this.
|
|
2572
|
+
async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
|
|
2573
|
+
const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
|
|
1767
2574
|
if (!result.success) {
|
|
1768
2575
|
const payload = result.payload;
|
|
1769
2576
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -1774,7 +2581,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1774
2581
|
}
|
|
1775
2582
|
throw rethrow;
|
|
1776
2583
|
}
|
|
1777
|
-
return
|
|
2584
|
+
return result.payload;
|
|
1778
2585
|
}
|
|
1779
2586
|
/**
|
|
1780
2587
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -1804,26 +2611,122 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1804
2611
|
* lives in one place.
|
|
1805
2612
|
*/
|
|
1806
2613
|
async _callConnector(sessionId, method, params, signal) {
|
|
1807
|
-
|
|
2614
|
+
this._assertConnectorReady(method);
|
|
2615
|
+
if (signal?.aborted) throw this._abortReason(signal);
|
|
2616
|
+
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2617
|
+
let promise;
|
|
2618
|
+
try {
|
|
2619
|
+
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2620
|
+
} catch (error) {
|
|
2621
|
+
releaseOperation();
|
|
2622
|
+
throw error;
|
|
2623
|
+
}
|
|
1808
2624
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
1809
2625
|
return this._unwrapConnectorResult(result);
|
|
1810
2626
|
}
|
|
2627
|
+
_assertConnectorReady(method) {
|
|
2628
|
+
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2629
|
+
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
_runConnectorTeardown(task) {
|
|
2633
|
+
const previous = this._connectorTeardownTail;
|
|
2634
|
+
let releaseTail = () => void 0;
|
|
2635
|
+
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2636
|
+
releaseTail = resolve;
|
|
2637
|
+
});
|
|
2638
|
+
this._pendingConnectorTeardowns += 1;
|
|
2639
|
+
return (async () => {
|
|
2640
|
+
try {
|
|
2641
|
+
await previous;
|
|
2642
|
+
await this._waitForConnectorOperationsToDrain();
|
|
2643
|
+
await task();
|
|
2644
|
+
} finally {
|
|
2645
|
+
this._pendingConnectorTeardowns -= 1;
|
|
2646
|
+
releaseTail();
|
|
2647
|
+
}
|
|
2648
|
+
})();
|
|
2649
|
+
}
|
|
2650
|
+
_waitForConnectorOperationsToDrain() {
|
|
2651
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2652
|
+
return Promise.resolve();
|
|
2653
|
+
}
|
|
2654
|
+
return new Promise((resolve) => {
|
|
2655
|
+
this._connectorIdleWaiters.add(resolve);
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
_retainConnectorOperation(key) {
|
|
2659
|
+
this._unsettledConnectorOperations.set(
|
|
2660
|
+
key,
|
|
2661
|
+
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2662
|
+
);
|
|
2663
|
+
let released = false;
|
|
2664
|
+
return () => {
|
|
2665
|
+
if (released) return;
|
|
2666
|
+
released = true;
|
|
2667
|
+
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2668
|
+
if (remaining > 0) {
|
|
2669
|
+
this._unsettledConnectorOperations.set(key, remaining);
|
|
2670
|
+
} else {
|
|
2671
|
+
this._unsettledConnectorOperations.delete(key);
|
|
2672
|
+
}
|
|
2673
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2674
|
+
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2675
|
+
this._connectorIdleWaiters.clear();
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
1811
2679
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
1812
|
-
|
|
1813
|
-
|
|
2680
|
+
const positionalOperationId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) ? connectId : void 0;
|
|
2681
|
+
if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
|
|
2682
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2683
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
2684
|
+
message: "Conflicting Ledger operation ids",
|
|
2685
|
+
params: {
|
|
2686
|
+
positionalOperationId,
|
|
2687
|
+
commonOperationId: commonParams.operationId
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2692
|
+
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2693
|
+
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2694
|
+
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2695
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
2696
|
+
method,
|
|
2697
|
+
connectId: effectiveConnectId || "(empty)",
|
|
2698
|
+
params
|
|
2699
|
+
});
|
|
2700
|
+
const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
|
|
1814
2701
|
try {
|
|
1815
2702
|
const result = await this._jobQueue.enqueue(
|
|
1816
2703
|
queueKey,
|
|
1817
|
-
async (signal) =>
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2704
|
+
async (signal) => {
|
|
2705
|
+
if (operationId) this._activeOperationJobs.add(operationId);
|
|
2706
|
+
try {
|
|
2707
|
+
return await this._runConnectorCall(
|
|
2708
|
+
effectiveConnectId,
|
|
2709
|
+
method,
|
|
2710
|
+
params,
|
|
2711
|
+
signal,
|
|
2712
|
+
fingerprint,
|
|
2713
|
+
permissionDeviceId,
|
|
2714
|
+
commonParams,
|
|
2715
|
+
installContext ?? {},
|
|
2716
|
+
operationId
|
|
2717
|
+
);
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
2720
|
+
throw error;
|
|
2721
|
+
} finally {
|
|
2722
|
+
if (operationId) {
|
|
2723
|
+
this._activeOperationJobs.delete(operationId);
|
|
2724
|
+
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2725
|
+
await this._releaseLostOperationConnection(operationId);
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
},
|
|
1827
2730
|
{
|
|
1828
2731
|
label: method,
|
|
1829
2732
|
rejectIfBusy: true,
|
|
@@ -1844,15 +2747,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1844
2747
|
}
|
|
1845
2748
|
});
|
|
1846
2749
|
throw err;
|
|
2750
|
+
} finally {
|
|
2751
|
+
releaseOperationRetention?.();
|
|
1847
2752
|
}
|
|
1848
2753
|
}
|
|
1849
2754
|
/**
|
|
1850
2755
|
* Race a promise against an abort signal. On abort, rejects with
|
|
1851
2756
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
1852
2757
|
*/
|
|
2758
|
+
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2759
|
+
_abortReason(signal) {
|
|
2760
|
+
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2761
|
+
}
|
|
1853
2762
|
_abortable(signal, promise) {
|
|
1854
|
-
const getAbortReason = () =>
|
|
2763
|
+
const getAbortReason = () => this._abortReason(signal);
|
|
1855
2764
|
if (signal.aborted) {
|
|
2765
|
+
void promise.catch(() => void 0);
|
|
1856
2766
|
return Promise.reject(getAbortReason());
|
|
1857
2767
|
}
|
|
1858
2768
|
return new Promise((resolve, reject) => {
|
|
@@ -1879,13 +2789,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1879
2789
|
}
|
|
1880
2790
|
}
|
|
1881
2791
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
1882
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2792
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
|
|
1883
2793
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2794
|
+
if (!operationId) {
|
|
2795
|
+
await this._ensureDevicePermission(
|
|
2796
|
+
connectId,
|
|
2797
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
2798
|
+
signal
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
1889
2801
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1890
2802
|
let effectiveParams = params;
|
|
1891
2803
|
if (method === "btcGetPublicKey") {
|
|
@@ -1899,36 +2811,135 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1899
2811
|
effectiveParams = gatedParams;
|
|
1900
2812
|
}
|
|
1901
2813
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
1902
|
-
|
|
1903
|
-
|
|
2814
|
+
let businessCallStarted = false;
|
|
2815
|
+
const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
|
|
2816
|
+
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2817
|
+
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2818
|
+
(connection) => connection.transport === knownTransport
|
|
2819
|
+
);
|
|
2820
|
+
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2821
|
+
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
|
|
2822
|
+
const connectionAttempt = { ...commonParams };
|
|
2823
|
+
const bundleConnection = installContext?.connection;
|
|
2824
|
+
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2825
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2826
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2827
|
+
message: "Ledger all-network connection ended"
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2831
|
+
preferredConnectId,
|
|
1904
2832
|
signal,
|
|
1905
|
-
allowUsbEphemeralFallback
|
|
2833
|
+
allowUsbEphemeralFallback,
|
|
2834
|
+
void 0,
|
|
2835
|
+
connectionAttempt
|
|
1906
2836
|
);
|
|
1907
|
-
|
|
2837
|
+
let sessionId = this._sessions.get(resolvedConnectId);
|
|
2838
|
+
if (sessionId && installContext && !installContext.connection) {
|
|
2839
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2840
|
+
}
|
|
1908
2841
|
if (!sessionId) {
|
|
2842
|
+
if (operationId) {
|
|
2843
|
+
this._operations.end(operationId, "disconnect");
|
|
2844
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2845
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.OperationEnded,
|
|
2846
|
+
message: "Ledger operation connection is no longer active",
|
|
2847
|
+
params: { operationId, reason: "disconnect" }
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
1909
2850
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
1910
2851
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
1911
2852
|
});
|
|
1912
2853
|
}
|
|
1913
2854
|
try {
|
|
1914
2855
|
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2856
|
+
for (; ; ) {
|
|
2857
|
+
const fp = await this._abortable(
|
|
2858
|
+
signal,
|
|
2859
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2860
|
+
sessionId,
|
|
2861
|
+
fingerprint.deviceId,
|
|
2862
|
+
fingerprint.chain
|
|
2863
|
+
)
|
|
2864
|
+
);
|
|
2865
|
+
if (fp.success) break;
|
|
2866
|
+
const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
|
|
2867
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
|
|
2868
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2869
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
2873
|
+
binding.rejectedConnectIds.add(resolvedConnectId);
|
|
2874
|
+
binding.rejectedConnectId = resolvedConnectId;
|
|
2875
|
+
this._sessions.delete(resolvedConnectId);
|
|
2876
|
+
await this.connector.disconnect(sessionId);
|
|
2877
|
+
if (operationId) this._pendingOperationDisconnects.delete(operationId);
|
|
2878
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2879
|
+
resolvedConnectId = await this._connectFirstOrSelect(
|
|
2880
|
+
[],
|
|
2881
|
+
void 0,
|
|
2882
|
+
true,
|
|
2883
|
+
operationId,
|
|
2884
|
+
binding,
|
|
2885
|
+
signal
|
|
2886
|
+
);
|
|
2887
|
+
const selectedSession = this._sessions.get(resolvedConnectId);
|
|
2888
|
+
const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
|
|
2889
|
+
if (!selectedSession || !selectedDevice)
|
|
2890
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2891
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2892
|
+
message: "Selected Ledger connection ended"
|
|
2893
|
+
});
|
|
2894
|
+
sessionId = selectedSession;
|
|
2895
|
+
if (operationId)
|
|
2896
|
+
this._operations.rebind(operationId, {
|
|
2897
|
+
connectId: resolvedConnectId,
|
|
2898
|
+
device: selectedDevice,
|
|
2899
|
+
// Same source as `_createOperation`: the selected transport, not
|
|
2900
|
+
// the device snapshot a session connect overwrote.
|
|
2901
|
+
connectionType: this._isBleConnection() ? "ble" : "usb",
|
|
2902
|
+
connectionKeys: [sessionId]
|
|
2903
|
+
});
|
|
2904
|
+
if (installContext)
|
|
2905
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2906
|
+
}
|
|
2907
|
+
await this._publishVerifiedBleBinding(
|
|
2908
|
+
resolvedConnectId,
|
|
2909
|
+
fingerprint.chain,
|
|
2910
|
+
fingerprint.deviceId,
|
|
2911
|
+
connectionAttempt,
|
|
2912
|
+
operationId,
|
|
2913
|
+
signal
|
|
1922
2914
|
);
|
|
1923
|
-
if (!
|
|
1924
|
-
|
|
1925
|
-
|
|
2915
|
+
if (!operationId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
|
|
2916
|
+
this._verifiedBleReconnectTargets.set(connectId, {
|
|
2917
|
+
connectId: resolvedConnectId,
|
|
2918
|
+
chain: fingerprint.chain,
|
|
2919
|
+
fingerprint: fingerprint.deviceId
|
|
1926
2920
|
});
|
|
1927
2921
|
}
|
|
1928
2922
|
}
|
|
2923
|
+
businessCallStarted = true;
|
|
1929
2924
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
1930
2925
|
} catch (err) {
|
|
1931
2926
|
if (signal.aborted) throw err;
|
|
2927
|
+
if (isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err)) {
|
|
2928
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2929
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2930
|
+
else {
|
|
2931
|
+
this._sessions.delete(resolvedConnectId);
|
|
2932
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2933
|
+
}
|
|
2934
|
+
const ambiguous = businessCallStarted && !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method);
|
|
2935
|
+
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2936
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2937
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(err).code,
|
|
2938
|
+
message: "Ledger operation connection was lost; start a new operation",
|
|
2939
|
+
params: ambiguous ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, { operationId }) : interactionParams,
|
|
2940
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
1932
2943
|
const errObj = err;
|
|
1933
2944
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1934
2945
|
message: errObj?.message,
|
|
@@ -1940,6 +2951,65 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1940
2951
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1941
2952
|
isStuckApp: isStuckAppStateError(err)
|
|
1942
2953
|
});
|
|
2954
|
+
if ((isDeviceLockedError(err) || errObj?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2955
|
+
await this._waitForDeviceConnect(signal);
|
|
2956
|
+
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2957
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2958
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2959
|
+
message: "Ledger connection ended while waiting for unlock"
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
return this._runConnectorCall(
|
|
2963
|
+
resolvedConnectId,
|
|
2964
|
+
method,
|
|
2965
|
+
effectiveParams,
|
|
2966
|
+
signal,
|
|
2967
|
+
fingerprint,
|
|
2968
|
+
permissionDeviceId,
|
|
2969
|
+
commonParams,
|
|
2970
|
+
installContext,
|
|
2971
|
+
operationId,
|
|
2972
|
+
lockedRetryBudget - 1
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2976
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2977
|
+
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2978
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2979
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2980
|
+
message: "Ledger connection ended during the app transition"
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
try {
|
|
2984
|
+
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2985
|
+
} catch (retryErr) {
|
|
2986
|
+
if (isStuckAppStateError(retryErr)) throw err;
|
|
2987
|
+
if (isDeviceDisconnectedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isTimeoutError(retryErr) || isConnectionLevelError(retryErr)) {
|
|
2988
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2989
|
+
else {
|
|
2990
|
+
this._sessions.delete(resolvedConnectId);
|
|
2991
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2992
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2993
|
+
}
|
|
2994
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2995
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(retryErr).code,
|
|
2996
|
+
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2997
|
+
params: !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method) ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, {
|
|
2998
|
+
operationId,
|
|
2999
|
+
reason: "disconnect"
|
|
3000
|
+
}) : { operationId, reason: "disconnect" },
|
|
3001
|
+
recovery: !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method) ? { scope: "unknown" } : void 0
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
throw retryErr;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
if (!operationId && err?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch) {
|
|
3008
|
+
this._sessions.delete(resolvedConnectId);
|
|
3009
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
3010
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
3011
|
+
throw err;
|
|
3012
|
+
}
|
|
1943
3013
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
1944
3014
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === import_hwk_adapter_core3.HardwareErrorCode.AppNotInstalled;
|
|
1945
3015
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -1995,157 +3065,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1995
3065
|
fingerprint,
|
|
1996
3066
|
permissionDeviceId,
|
|
1997
3067
|
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
|
|
3068
|
+
installContext,
|
|
3069
|
+
operationId
|
|
2039
3070
|
);
|
|
2040
|
-
throw retryErr;
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
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
3071
|
}
|
|
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
3072
|
}
|
|
2112
3073
|
throw err;
|
|
2113
3074
|
}
|
|
2114
3075
|
}
|
|
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
3076
|
_sleepAbortable(ms, signal) {
|
|
2150
3077
|
return new Promise((resolve, reject) => {
|
|
2151
3078
|
if (signal.aborted) {
|
|
@@ -2164,116 +3091,35 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2164
3091
|
});
|
|
2165
3092
|
}
|
|
2166
3093
|
/**
|
|
2167
|
-
*
|
|
3094
|
+
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
3095
|
+
*
|
|
3096
|
+
* Emits `REQUEST_DEVICE_PERMISSION` and awaits the consumer's
|
|
3097
|
+
* `RECEIVE_DEVICE_PERMISSION` reply (60s budget covers "probe → system
|
|
3098
|
+
* prompt → user tap" plus a generous margin). If the consumer never wires
|
|
3099
|
+
* a handler or never replies, the wait times out and the operation fails
|
|
3100
|
+
* fast so scanners/callers don't hang silently.
|
|
2168
3101
|
*
|
|
2169
|
-
*
|
|
2170
|
-
*
|
|
2171
|
-
* on the same DMK can poison the next chain switch.
|
|
3102
|
+
* - No connectId (searchDevices): environment-level permission
|
|
3103
|
+
* - With connectId (business methods): device-level permission
|
|
2172
3104
|
*/
|
|
2173
|
-
async
|
|
2174
|
-
|
|
2175
|
-
|
|
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;
|
|
3105
|
+
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
3106
|
+
if (signal?.aborted) {
|
|
3107
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2187
3108
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
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
|
|
2259
|
-
*/
|
|
2260
|
-
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
2261
|
-
if (signal?.aborted) {
|
|
2262
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
2263
|
-
}
|
|
2264
|
-
const transportType = this.activeTransport ?? "hid";
|
|
2265
|
-
const waitPromise = this._uiRegistry.wait(
|
|
2266
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2267
|
-
{ timeoutMs: 6e4 }
|
|
2268
|
-
);
|
|
2269
|
-
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
2270
|
-
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2271
|
-
payload: { transportType, connectId, deviceId }
|
|
2272
|
-
});
|
|
2273
|
-
let response;
|
|
2274
|
-
const onAbort = () => {
|
|
2275
|
-
this._uiRegistry.cancel(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION);
|
|
2276
|
-
};
|
|
3109
|
+
const transportType = this.activeTransport ?? "hid";
|
|
3110
|
+
const operationId = this._activeOperationId();
|
|
3111
|
+
const waitPromise = this._uiRegistry.wait(
|
|
3112
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3113
|
+
{ timeoutMs: 6e4, operationId }
|
|
3114
|
+
);
|
|
3115
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
3116
|
+
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3117
|
+
payload: { transportType, connectId, deviceId, operationId }
|
|
3118
|
+
});
|
|
3119
|
+
let response;
|
|
3120
|
+
const onAbort = () => {
|
|
3121
|
+
this._uiRegistry.cancel(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION);
|
|
3122
|
+
};
|
|
2277
3123
|
try {
|
|
2278
3124
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2279
3125
|
response = await waitPromise;
|
|
@@ -2314,7 +3160,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2314
3160
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
2315
3161
|
const e = err;
|
|
2316
3162
|
const params = e.code === import_hwk_adapter_core3.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
2317
|
-
return ledgerFailure(
|
|
3163
|
+
return ledgerFailure(
|
|
3164
|
+
e.code,
|
|
3165
|
+
e.message ?? "Unknown error",
|
|
3166
|
+
e.appName,
|
|
3167
|
+
tag,
|
|
3168
|
+
params,
|
|
3169
|
+
void 0,
|
|
3170
|
+
(0, import_hwk_adapter_core3.isHwkRecoveryHint)(e.recovery) ? e.recovery : void 0
|
|
3171
|
+
);
|
|
2318
3172
|
}
|
|
2319
3173
|
const mapped = mapLedgerError(err);
|
|
2320
3174
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -2341,7 +3195,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2341
3195
|
deviceId: device.deviceId,
|
|
2342
3196
|
connectId: device.connectId,
|
|
2343
3197
|
label: device.name,
|
|
2344
|
-
connectionType: this.connector.connectionType,
|
|
3198
|
+
connectionType: device.connectionType ?? this.connector.connectionType,
|
|
2345
3199
|
rssi: device.rssi,
|
|
2346
3200
|
isConnectable: device.isConnectable,
|
|
2347
3201
|
serialNumber: device.serialNumber,
|
|
@@ -2366,7 +3220,7 @@ _LedgerAdapter.APP_INSTALL_PROGRESS_MIN_DELTA = 0.05;
|
|
|
2366
3220
|
var LedgerAdapter = _LedgerAdapter;
|
|
2367
3221
|
|
|
2368
3222
|
// src/connector/LedgerConnectorBase.ts
|
|
2369
|
-
var
|
|
3223
|
+
var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
|
|
2370
3224
|
|
|
2371
3225
|
// src/device/LedgerDeviceManager.ts
|
|
2372
3226
|
var LedgerDeviceManager = class {
|
|
@@ -2902,7 +3756,7 @@ var SignerManager = class _SignerManager {
|
|
|
2902
3756
|
return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
|
|
2903
3757
|
}
|
|
2904
3758
|
static _createContextModule() {
|
|
2905
|
-
const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3759
|
+
const contextModule = new import_context_module.ContextModuleBuilder({}).setChain(import_context_module.ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
|
|
2906
3760
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2907
3761
|
}
|
|
2908
3762
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -3373,6 +4227,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
3373
4227
|
|
|
3374
4228
|
// src/connector/chains/sol.ts
|
|
3375
4229
|
var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
|
|
4230
|
+
var import_bs58 = __toESM(require("bs58"));
|
|
3376
4231
|
|
|
3377
4232
|
// src/signer/SignerSol.ts
|
|
3378
4233
|
var SignerSol = class {
|
|
@@ -3456,8 +4311,21 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3456
4311
|
const path = normalizePath(params.path);
|
|
3457
4312
|
const messageBytes = (0, import_hwk_adapter_core9.hexToBytes)(params.message);
|
|
3458
4313
|
try {
|
|
4314
|
+
if (params.messageVersion === 1) {
|
|
4315
|
+
const preparedMessage = (0, import_hwk_adapter_core9.prepareSolanaOffchainMessageV1)({
|
|
4316
|
+
message: messageBytes,
|
|
4317
|
+
requiredSigners: params.requiredSigners
|
|
4318
|
+
});
|
|
4319
|
+
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4320
|
+
"@ledgerhq/device-signer-kit-solana"
|
|
4321
|
+
);
|
|
4322
|
+
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4323
|
+
version: SignMessageVersion.Raw
|
|
4324
|
+
});
|
|
4325
|
+
return { signature: decodeBase58Signature(result2.signature) };
|
|
4326
|
+
}
|
|
3459
4327
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
3460
|
-
return { signature: result.signature };
|
|
4328
|
+
return { signature: decodeBase58EnvelopeSignature(result.signature) };
|
|
3461
4329
|
} catch (err) {
|
|
3462
4330
|
ctx.invalidateSession(sessionId);
|
|
3463
4331
|
throw ctx.wrapError(err);
|
|
@@ -3465,11 +4333,27 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3465
4333
|
ctx.clearCanceller(sessionId);
|
|
3466
4334
|
}
|
|
3467
4335
|
}
|
|
4336
|
+
function decodeBase58Signature(signature) {
|
|
4337
|
+
const bytes = import_bs58.default.decode(signature);
|
|
4338
|
+
if (bytes.length !== 64) {
|
|
4339
|
+
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4340
|
+
}
|
|
4341
|
+
return (0, import_hwk_adapter_core9.bytesToHex)(bytes);
|
|
4342
|
+
}
|
|
4343
|
+
function decodeBase58EnvelopeSignature(envelope) {
|
|
4344
|
+
const bytes = import_bs58.default.decode(envelope);
|
|
4345
|
+
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4346
|
+
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4347
|
+
}
|
|
4348
|
+
return (0, import_hwk_adapter_core9.bytesToHex)(bytes.subarray(1, 65));
|
|
4349
|
+
}
|
|
3468
4350
|
async function _createSolSigner(ctx, sessionId) {
|
|
3469
4351
|
const dmk = await ctx.getOrCreateDmk();
|
|
3470
|
-
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit(
|
|
4352
|
+
const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
|
|
4353
|
+
"@ledgerhq/context-module"
|
|
4354
|
+
);
|
|
3471
4355
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
3472
|
-
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4356
|
+
const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
|
|
3473
4357
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
3474
4358
|
const signer = new SignerSol(sdkSigner);
|
|
3475
4359
|
signer.onInteraction = (interaction) => {
|
|
@@ -3485,360 +4369,196 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
3485
4369
|
return signer;
|
|
3486
4370
|
}
|
|
3487
4371
|
|
|
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");
|
|
4372
|
+
// src/connector/chains/zcash.ts
|
|
3497
4373
|
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);
|
|
4374
|
+
|
|
4375
|
+
// src/signer/SignerZcash.ts
|
|
4376
|
+
var SignerZcash = class {
|
|
4377
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4378
|
+
constructor(_sdk) {
|
|
4379
|
+
this._sdk = _sdk;
|
|
3556
4380
|
}
|
|
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
|
-
}
|
|
4381
|
+
/** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
|
|
4382
|
+
async getFullViewingKey(derivationPath, options) {
|
|
4383
|
+
const action = this._sdk.getFullViewingKey(derivationPath, options);
|
|
4384
|
+
return deviceActionToPromise(
|
|
4385
|
+
action,
|
|
4386
|
+
this.onInteraction,
|
|
4387
|
+
void 0,
|
|
4388
|
+
this.onRegisterCanceller
|
|
3593
4389
|
);
|
|
3594
4390
|
}
|
|
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})`
|
|
4391
|
+
/** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
|
|
4392
|
+
async getShieldedAddress(derivationPath, options) {
|
|
4393
|
+
const action = this._sdk.getShieldedAddress(derivationPath, options);
|
|
4394
|
+
return deviceActionToPromise(
|
|
4395
|
+
action,
|
|
4396
|
+
this.onInteraction,
|
|
4397
|
+
void 0,
|
|
4398
|
+
this.onRegisterCanceller
|
|
3657
4399
|
);
|
|
3658
4400
|
}
|
|
3659
|
-
_isDashboard(appName) {
|
|
3660
|
-
return appName === DASHBOARD_APP_NAME;
|
|
3661
|
-
}
|
|
3662
|
-
_wait() {
|
|
3663
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3664
|
-
}
|
|
3665
4401
|
};
|
|
3666
4402
|
|
|
3667
|
-
// src/connector/chains/
|
|
3668
|
-
function
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
const { appName, needsConfirmation } = options;
|
|
3673
|
-
let openAppPromptShown = false;
|
|
3674
|
-
const onAppOpenPrompt = () => {
|
|
3675
|
-
openAppPromptShown = true;
|
|
3676
|
-
ctx.emit("ui-event", {
|
|
3677
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.ConfirmOpenApp,
|
|
3678
|
-
payload: { sessionId }
|
|
3679
|
-
});
|
|
3680
|
-
};
|
|
3681
|
-
const closeOpenAppUiIfShown = () => {
|
|
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
|
-
};
|
|
4403
|
+
// src/connector/chains/zcash.ts
|
|
4404
|
+
async function zcashGetFullViewingKey(ctx, sessionId, params) {
|
|
4405
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4406
|
+
const path = normalizePath(params.path);
|
|
4407
|
+
const mode = params.mode ?? "ufvk";
|
|
3690
4408
|
try {
|
|
3691
|
-
await
|
|
3692
|
-
|
|
3693
|
-
|
|
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
|
-
}
|
|
4409
|
+
const result = await signer.getFullViewingKey(path, { mode });
|
|
4410
|
+
if (result.mode === "ufvk") {
|
|
4411
|
+
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
3720
4412
|
}
|
|
3721
|
-
|
|
4413
|
+
return { path: params.path, mode, orchardFvk: (0, import_hwk_adapter_core10.bytesToHex)(result.fullViewingKey) };
|
|
4414
|
+
} catch (err) {
|
|
4415
|
+
ctx.invalidateSession(sessionId);
|
|
4416
|
+
throw ctx.wrapError(err);
|
|
4417
|
+
} finally {
|
|
4418
|
+
ctx.clearCanceller(sessionId);
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
async function zcashGetShieldedAddress(ctx, sessionId, params) {
|
|
4422
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4423
|
+
const path = normalizePath(params.path);
|
|
3722
4424
|
try {
|
|
3723
|
-
|
|
4425
|
+
const result = await signer.getShieldedAddress(path, {
|
|
4426
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4427
|
+
});
|
|
4428
|
+
return { address: result.address, path: params.path };
|
|
3724
4429
|
} catch (err) {
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
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;
|
|
4430
|
+
ctx.invalidateSession(sessionId);
|
|
4431
|
+
throw ctx.wrapError(err);
|
|
4432
|
+
} finally {
|
|
4433
|
+
ctx.clearCanceller(sessionId);
|
|
3746
4434
|
}
|
|
3747
4435
|
}
|
|
3748
|
-
async function
|
|
4436
|
+
async function _createZcashSigner(ctx, sessionId) {
|
|
3749
4437
|
const dmk = await ctx.getOrCreateDmk();
|
|
3750
|
-
const
|
|
3751
|
-
|
|
4438
|
+
const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
|
|
4439
|
+
const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
|
|
4440
|
+
const signer = new SignerZcash(sdkSigner);
|
|
4441
|
+
signer.onInteraction = (interaction) => {
|
|
4442
|
+
debugLog("[LedgerConnector] zcash.onInteraction:", interaction);
|
|
4443
|
+
ctx.emit("ui-event", {
|
|
4444
|
+
type: collapseSignerInteraction(interaction),
|
|
4445
|
+
payload: { sessionId }
|
|
4446
|
+
});
|
|
4447
|
+
};
|
|
4448
|
+
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
4449
|
+
return signer;
|
|
3752
4450
|
}
|
|
3753
4451
|
|
|
3754
|
-
// src/
|
|
3755
|
-
var
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
4452
|
+
// src/connector/chains/tron.ts
|
|
4453
|
+
var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
|
|
4454
|
+
|
|
4455
|
+
// src/signer/SignerTron.ts
|
|
4456
|
+
var SignerTron = class {
|
|
4457
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4458
|
+
constructor(_sdk) {
|
|
4459
|
+
this._sdk = _sdk;
|
|
3761
4460
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
}
|
|
3772
|
-
|
|
3773
|
-
|
|
4461
|
+
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4462
|
+
async getAddress(derivationPath, options) {
|
|
4463
|
+
const action = this._sdk.getAddress(derivationPath, options);
|
|
4464
|
+
const result = await deviceActionToPromise(
|
|
4465
|
+
action,
|
|
4466
|
+
this.onInteraction,
|
|
4467
|
+
void 0,
|
|
4468
|
+
this.onRegisterCanceller
|
|
4469
|
+
);
|
|
4470
|
+
return { address: result.address, publicKey: result.publicKey };
|
|
4471
|
+
}
|
|
4472
|
+
/** Sign a protobuf-encoded raw transaction. */
|
|
4473
|
+
async signTransaction(derivationPath, transaction, options) {
|
|
4474
|
+
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
4475
|
+
return deviceActionToPromise(
|
|
4476
|
+
action,
|
|
4477
|
+
this.onInteraction,
|
|
4478
|
+
void 0,
|
|
4479
|
+
this.onRegisterCanceller
|
|
4480
|
+
);
|
|
3774
4481
|
}
|
|
3775
|
-
|
|
4482
|
+
/** Sign a personal message (TIP-191). */
|
|
4483
|
+
async signPersonalMessage(derivationPath, message, options) {
|
|
4484
|
+
const action = this._sdk.signPersonalMessage(derivationPath, message, options);
|
|
4485
|
+
return deviceActionToPromise(
|
|
4486
|
+
action,
|
|
4487
|
+
this.onInteraction,
|
|
4488
|
+
void 0,
|
|
4489
|
+
this.onRegisterCanceller
|
|
4490
|
+
);
|
|
3776
4491
|
}
|
|
3777
4492
|
};
|
|
3778
4493
|
|
|
3779
4494
|
// src/connector/chains/tron.ts
|
|
3780
4495
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4496
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3781
4497
|
const path = normalizePath(params.path);
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
{
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
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
|
-
);
|
|
4498
|
+
try {
|
|
4499
|
+
const result = await tronSigner.getAddress(path, {
|
|
4500
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4501
|
+
});
|
|
4502
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
4503
|
+
} catch (err) {
|
|
4504
|
+
ctx.invalidateSession(sessionId);
|
|
4505
|
+
throw ctx.wrapError(err);
|
|
4506
|
+
} finally {
|
|
4507
|
+
ctx.clearCanceller(sessionId);
|
|
4508
|
+
}
|
|
3798
4509
|
}
|
|
3799
4510
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
3800
4511
|
if (!params.rawTxHex) {
|
|
3801
4512
|
throw Object.assign(
|
|
3802
4513
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
3803
|
-
{ code:
|
|
4514
|
+
{ code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
|
|
3804
4515
|
);
|
|
3805
4516
|
}
|
|
4517
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3806
4518
|
const path = normalizePath(params.path);
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
params.tokenSignatures ?? []
|
|
3817
|
-
);
|
|
3818
|
-
return { signature };
|
|
3819
|
-
}
|
|
3820
|
-
);
|
|
4519
|
+
try {
|
|
4520
|
+
const signature = await tronSigner.signTransaction(path, (0, import_hwk_adapter_core11.hexToBytes)(params.rawTxHex));
|
|
4521
|
+
return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
|
|
4522
|
+
} catch (err) {
|
|
4523
|
+
ctx.invalidateSession(sessionId);
|
|
4524
|
+
throw ctx.wrapError(err);
|
|
4525
|
+
} finally {
|
|
4526
|
+
ctx.clearCanceller(sessionId);
|
|
4527
|
+
}
|
|
3821
4528
|
}
|
|
3822
4529
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4530
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3823
4531
|
const path = normalizePath(params.path);
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
);
|
|
4532
|
+
try {
|
|
4533
|
+
const signature = await tronSigner.signPersonalMessage(path, (0, import_hwk_adapter_core11.hexToBytes)(params.messageHex));
|
|
4534
|
+
return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
|
|
4535
|
+
} catch (err) {
|
|
4536
|
+
ctx.invalidateSession(sessionId);
|
|
4537
|
+
throw ctx.wrapError(err);
|
|
4538
|
+
} finally {
|
|
4539
|
+
ctx.clearCanceller(sessionId);
|
|
4540
|
+
}
|
|
3834
4541
|
}
|
|
3835
|
-
async function
|
|
4542
|
+
async function _createTronSigner(ctx, sessionId) {
|
|
3836
4543
|
const dmk = await ctx.getOrCreateDmk();
|
|
3837
|
-
|
|
4544
|
+
const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
|
|
4545
|
+
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4546
|
+
const signer = new SignerTron(sdkSigner);
|
|
4547
|
+
signer.onInteraction = (interaction) => {
|
|
4548
|
+
debugLog("[LedgerConnector] tron.onInteraction:", interaction);
|
|
4549
|
+
ctx.emit("ui-event", {
|
|
4550
|
+
type: collapseSignerInteraction(interaction),
|
|
4551
|
+
payload: { sessionId }
|
|
4552
|
+
});
|
|
4553
|
+
};
|
|
4554
|
+
signer.onRegisterCanceller = (cancel) => {
|
|
4555
|
+
ctx.registerCanceller(sessionId, cancel);
|
|
4556
|
+
};
|
|
4557
|
+
return signer;
|
|
3838
4558
|
}
|
|
3839
4559
|
|
|
3840
4560
|
// src/device-apps/customActions.ts
|
|
3841
|
-
var
|
|
4561
|
+
var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
|
|
3842
4562
|
var import_rxjs = require("rxjs");
|
|
3843
4563
|
var GetOsVersionDeviceAction = class {
|
|
3844
4564
|
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
@@ -3852,7 +4572,7 @@ var GetOsVersionDeviceAction = class {
|
|
|
3852
4572
|
(async () => {
|
|
3853
4573
|
try {
|
|
3854
4574
|
subject.next({
|
|
3855
|
-
status:
|
|
4575
|
+
status: import_device_management_kit2.DeviceActionStatus.Pending,
|
|
3856
4576
|
intermediateValue: { requiredUserInteraction: "none" }
|
|
3857
4577
|
});
|
|
3858
4578
|
const result = await internalApi.sendCommand(new this._deps.GetOsVersionCommand());
|
|
@@ -3861,11 +4581,11 @@ var GetOsVersionDeviceAction = class {
|
|
|
3861
4581
|
const errObj = result?.error;
|
|
3862
4582
|
throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
|
|
3863
4583
|
}
|
|
3864
|
-
subject.next({ status:
|
|
4584
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: result.data });
|
|
3865
4585
|
subject.complete();
|
|
3866
4586
|
} catch (err) {
|
|
3867
4587
|
if (cancelled) return;
|
|
3868
|
-
subject.next({ status:
|
|
4588
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
|
|
3869
4589
|
subject.complete();
|
|
3870
4590
|
}
|
|
3871
4591
|
})();
|
|
@@ -3873,7 +4593,7 @@ var GetOsVersionDeviceAction = class {
|
|
|
3873
4593
|
observable: subject.asObservable(),
|
|
3874
4594
|
cancel: () => {
|
|
3875
4595
|
cancelled = true;
|
|
3876
|
-
subject.next({ status:
|
|
4596
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
|
|
3877
4597
|
subject.complete();
|
|
3878
4598
|
}
|
|
3879
4599
|
};
|
|
@@ -3891,7 +4611,7 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3891
4611
|
(async () => {
|
|
3892
4612
|
try {
|
|
3893
4613
|
subject.next({
|
|
3894
|
-
status:
|
|
4614
|
+
status: import_device_management_kit2.DeviceActionStatus.Pending,
|
|
3895
4615
|
intermediateValue: { requiredUserInteraction: "none" }
|
|
3896
4616
|
});
|
|
3897
4617
|
const osVersionResult = await internalApi.sendCommand(new this._GetOsVersionCommand());
|
|
@@ -3908,11 +4628,11 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3908
4628
|
throw new Error(httpErr?.message ?? "Manager API getAppList failed");
|
|
3909
4629
|
}
|
|
3910
4630
|
const apps = either.extract();
|
|
3911
|
-
subject.next({ status:
|
|
4631
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: apps });
|
|
3912
4632
|
subject.complete();
|
|
3913
4633
|
} catch (err) {
|
|
3914
4634
|
if (cancelled) return;
|
|
3915
|
-
subject.next({ status:
|
|
4635
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
|
|
3916
4636
|
subject.complete();
|
|
3917
4637
|
}
|
|
3918
4638
|
})();
|
|
@@ -3920,7 +4640,7 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3920
4640
|
observable: subject.asObservable(),
|
|
3921
4641
|
cancel: () => {
|
|
3922
4642
|
cancelled = true;
|
|
3923
|
-
subject.next({ status:
|
|
4643
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
|
|
3924
4644
|
subject.complete();
|
|
3925
4645
|
}
|
|
3926
4646
|
};
|
|
@@ -4013,7 +4733,7 @@ var DeviceApps = class {
|
|
|
4013
4733
|
seTargetId: v.seTargetId,
|
|
4014
4734
|
mcuTargetId: v.mcuTargetId,
|
|
4015
4735
|
seVersion: v.seVersion,
|
|
4016
|
-
seFlagsHex:
|
|
4736
|
+
seFlagsHex: bytesToHex4(v.seFlags),
|
|
4017
4737
|
mcuSephVersion: v.mcuSephVersion,
|
|
4018
4738
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
4019
4739
|
hwVersion: v.hwVersion
|
|
@@ -4087,7 +4807,7 @@ var DeviceApps = class {
|
|
|
4087
4807
|
}
|
|
4088
4808
|
}
|
|
4089
4809
|
};
|
|
4090
|
-
function
|
|
4810
|
+
function bytesToHex4(bytes) {
|
|
4091
4811
|
if (!bytes) return "";
|
|
4092
4812
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4093
4813
|
}
|
|
@@ -4130,10 +4850,11 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4130
4850
|
evm: "Ethereum",
|
|
4131
4851
|
btc: "Bitcoin",
|
|
4132
4852
|
sol: "Solana",
|
|
4133
|
-
tron: "Tron"
|
|
4853
|
+
tron: "Tron",
|
|
4854
|
+
zcash: "Zcash"
|
|
4134
4855
|
};
|
|
4135
4856
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4136
|
-
Object.values(
|
|
4857
|
+
Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
|
|
4137
4858
|
);
|
|
4138
4859
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4139
4860
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4170,6 +4891,10 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4170
4891
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4171
4892
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4172
4893
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4894
|
+
case "@ledgerhq/device-signer-kit-tron":
|
|
4895
|
+
return import("@ledgerhq/device-signer-kit-tron");
|
|
4896
|
+
case "@ledgerhq/device-signer-kit-zcash":
|
|
4897
|
+
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4173
4898
|
case "@ledgerhq/context-module":
|
|
4174
4899
|
return import("@ledgerhq/context-module");
|
|
4175
4900
|
default:
|
|
@@ -4229,6 +4954,7 @@ var LedgerConnectorBase = class {
|
|
|
4229
4954
|
this._ctx = {
|
|
4230
4955
|
emit: (event, data) => this._emit(event, data),
|
|
4231
4956
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4957
|
+
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4232
4958
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4233
4959
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4234
4960
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -4280,7 +5006,7 @@ var LedgerConnectorBase = class {
|
|
|
4280
5006
|
async searchDevices() {
|
|
4281
5007
|
const dm = await this._getDeviceManager();
|
|
4282
5008
|
const descriptors = await this._discoverDescriptors(dm);
|
|
4283
|
-
const resolvedDescriptors = descriptors.filter((d) => !(0,
|
|
5009
|
+
const resolvedDescriptors = descriptors.filter((d) => !(0, import_hwk_adapter_core12.isKnownNonTargetHardwareVendor)(d, "ledger")).map((d) => ({
|
|
4284
5010
|
descriptor: d,
|
|
4285
5011
|
connectId: this._resolveConnectId(d)
|
|
4286
5012
|
}));
|
|
@@ -4328,7 +5054,7 @@ var LedgerConnectorBase = class {
|
|
|
4328
5054
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
4329
5055
|
);
|
|
4330
5056
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
4331
|
-
err.code =
|
|
5057
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
4332
5058
|
reject(err);
|
|
4333
5059
|
}, HANG_CEILING_MS);
|
|
4334
5060
|
});
|
|
@@ -4361,7 +5087,7 @@ var LedgerConnectorBase = class {
|
|
|
4361
5087
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
4362
5088
|
);
|
|
4363
5089
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
4364
|
-
err.code =
|
|
5090
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
|
|
4365
5091
|
throw err;
|
|
4366
5092
|
};
|
|
4367
5093
|
const doConnect = async (path) => {
|
|
@@ -4418,14 +5144,14 @@ var LedgerConnectorBase = class {
|
|
|
4418
5144
|
this._resetSignersAndSessions();
|
|
4419
5145
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
4420
5146
|
const tag = err?._tag;
|
|
4421
|
-
if (isKnownConnectionTag(tag)) {
|
|
5147
|
+
if (isKnownConnectionTag(tag) && !isConnectionOpeningTag(tag)) {
|
|
4422
5148
|
throw err;
|
|
4423
5149
|
}
|
|
4424
5150
|
const wrapped = new Error(
|
|
4425
5151
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
4426
5152
|
);
|
|
4427
5153
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
4428
|
-
wrapped.code =
|
|
5154
|
+
wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
4429
5155
|
wrapped.originalError = err;
|
|
4430
5156
|
throw wrapped;
|
|
4431
5157
|
}
|
|
@@ -4503,7 +5229,7 @@ var LedgerConnectorBase = class {
|
|
|
4503
5229
|
this._unwatchSessionState(sessionId);
|
|
4504
5230
|
this._signerManager?.invalidate(sessionId);
|
|
4505
5231
|
this._cancellers.get(sessionId)?.({
|
|
4506
|
-
code:
|
|
5232
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
|
|
4507
5233
|
tag: "DeviceDisconnected",
|
|
4508
5234
|
message: "Device disconnected"
|
|
4509
5235
|
});
|
|
@@ -4527,9 +5253,9 @@ var LedgerConnectorBase = class {
|
|
|
4527
5253
|
if (isAppStuckByApdu(err)) {
|
|
4528
5254
|
return {
|
|
4529
5255
|
success: false,
|
|
4530
|
-
error: (0,
|
|
5256
|
+
error: (0, import_hwk_adapter_core12.serializeConnectorError)(
|
|
4531
5257
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
4532
|
-
code:
|
|
5258
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
|
|
4533
5259
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
4534
5260
|
originalError: err
|
|
4535
5261
|
})
|
|
@@ -4539,16 +5265,16 @@ var LedgerConnectorBase = class {
|
|
|
4539
5265
|
if (isTransportStuck(err)) {
|
|
4540
5266
|
return {
|
|
4541
5267
|
success: false,
|
|
4542
|
-
error: (0,
|
|
5268
|
+
error: (0, import_hwk_adapter_core12.serializeConnectorError)(
|
|
4543
5269
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
4544
|
-
code:
|
|
5270
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
|
|
4545
5271
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
4546
5272
|
originalError: err
|
|
4547
5273
|
})
|
|
4548
5274
|
)
|
|
4549
5275
|
};
|
|
4550
5276
|
}
|
|
4551
|
-
return { success: false, error: (0,
|
|
5277
|
+
return { success: false, error: (0, import_hwk_adapter_core12.serializeConnectorError)(err) };
|
|
4552
5278
|
}
|
|
4553
5279
|
}
|
|
4554
5280
|
async _dispatch(sessionId, method, params) {
|
|
@@ -4587,6 +5313,11 @@ var LedgerConnectorBase = class {
|
|
|
4587
5313
|
return solSignTransaction(ctx, sessionId, params);
|
|
4588
5314
|
case "solSignMessage":
|
|
4589
5315
|
return solSignMessage(ctx, sessionId, params);
|
|
5316
|
+
// ZCASH
|
|
5317
|
+
case "zcashGetFullViewingKey":
|
|
5318
|
+
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5319
|
+
case "zcashGetShieldedAddress":
|
|
5320
|
+
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
4590
5321
|
// TRON
|
|
4591
5322
|
case "tronGetAddress":
|
|
4592
5323
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -4611,7 +5342,7 @@ var LedgerConnectorBase = class {
|
|
|
4611
5342
|
try {
|
|
4612
5343
|
return await apps.install(p.appName, ({ progress }) => {
|
|
4613
5344
|
ctx.emit("ui-event", {
|
|
4614
|
-
type:
|
|
5345
|
+
type: import_hwk_adapter_core12.EConnectorInteraction.AppInstallProgress,
|
|
4615
5346
|
payload: {
|
|
4616
5347
|
sessionId,
|
|
4617
5348
|
appName: p.appName,
|
|
@@ -4620,7 +5351,7 @@ var LedgerConnectorBase = class {
|
|
|
4620
5351
|
});
|
|
4621
5352
|
});
|
|
4622
5353
|
} catch (err) {
|
|
4623
|
-
ctx.
|
|
5354
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4624
5355
|
throw ctx.wrapError(err);
|
|
4625
5356
|
} finally {
|
|
4626
5357
|
ctx.clearCanceller(sessionId);
|
|
@@ -4710,7 +5441,7 @@ var LedgerConnectorBase = class {
|
|
|
4710
5441
|
);
|
|
4711
5442
|
return { isGenuine: output.isGenuine, deviceId };
|
|
4712
5443
|
} catch (err) {
|
|
4713
|
-
ctx.
|
|
5444
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4714
5445
|
throw ctx.wrapError(err);
|
|
4715
5446
|
} finally {
|
|
4716
5447
|
ctx.clearCanceller(sessionId);
|
|
@@ -4829,9 +5560,34 @@ var LedgerConnectorBase = class {
|
|
|
4829
5560
|
}
|
|
4830
5561
|
return this._deviceAppsManager;
|
|
4831
5562
|
}
|
|
5563
|
+
// DeviceAppsManager is intentionally absent here: it is a per-call factory
|
|
5564
|
+
// with no cached session state, so there is nothing to invalidate. Cancelling
|
|
5565
|
+
// the device action is what closes the secure channel.
|
|
4832
5566
|
_invalidateSession(sessionId) {
|
|
4833
5567
|
this._signerManager?.invalidate(sessionId);
|
|
4834
5568
|
}
|
|
5569
|
+
/**
|
|
5570
|
+
* Teardown for the OS-level actions that open a manager-api secure channel
|
|
5571
|
+
* (install, uninstall, genuine check). Cancelling the device action is what
|
|
5572
|
+
* stops DMK's xstate actor, which unsubscribes the secure-channel observable
|
|
5573
|
+
* and closes its WebSocket; dropping the canceller without firing it leaves
|
|
5574
|
+
* that teardown to run only if the action settled on its own.
|
|
5575
|
+
*
|
|
5576
|
+
* The DMK device session is deliberately kept: the secure channel is a
|
|
5577
|
+
* separate WebSocket per call, and disconnecting here would break the bounded
|
|
5578
|
+
* unlock/retry recovery the adapter runs on the original session.
|
|
5579
|
+
*/
|
|
5580
|
+
_teardownSecureChannelSession(sessionId) {
|
|
5581
|
+
const cancel = this._cancellers.get(sessionId);
|
|
5582
|
+
this._cancellers.delete(sessionId);
|
|
5583
|
+
if (cancel) {
|
|
5584
|
+
try {
|
|
5585
|
+
cancel();
|
|
5586
|
+
} catch {
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
this._invalidateSession(sessionId);
|
|
5590
|
+
}
|
|
4835
5591
|
/**
|
|
4836
5592
|
* Replace an old session with a new one after app switch.
|
|
4837
5593
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -4916,7 +5672,7 @@ var LedgerConnectorBase = class {
|
|
|
4916
5672
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
4917
5673
|
*/
|
|
4918
5674
|
_ctxForMethod(method) {
|
|
4919
|
-
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5675
|
+
const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
|
|
4920
5676
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
4921
5677
|
if (!defaultAppName) return this._ctx;
|
|
4922
5678
|
return {
|
|
@@ -4950,6 +5706,204 @@ var LedgerConnectorBase = class {
|
|
|
4950
5706
|
return error;
|
|
4951
5707
|
}
|
|
4952
5708
|
};
|
|
5709
|
+
|
|
5710
|
+
// src/transport/DmkTransport.ts
|
|
5711
|
+
var import_hw_transport = __toESM(require("@ledgerhq/hw-transport"));
|
|
5712
|
+
var DmkTransport = class extends import_hw_transport.default {
|
|
5713
|
+
constructor(dmk, sessionId) {
|
|
5714
|
+
super();
|
|
5715
|
+
this._dmk = dmk;
|
|
5716
|
+
this._sessionId = sessionId;
|
|
5717
|
+
}
|
|
5718
|
+
async exchange(apdu) {
|
|
5719
|
+
const response = await this._dmk.sendApdu({
|
|
5720
|
+
sessionId: this._sessionId,
|
|
5721
|
+
apdu: new Uint8Array(apdu)
|
|
5722
|
+
});
|
|
5723
|
+
const { data, statusCode } = response;
|
|
5724
|
+
const result = Buffer.alloc(data.length + 2);
|
|
5725
|
+
if (data.length > 0) {
|
|
5726
|
+
result.set(data, 0);
|
|
5727
|
+
}
|
|
5728
|
+
result.set(statusCode, data.length);
|
|
5729
|
+
return result;
|
|
5730
|
+
}
|
|
5731
|
+
async close() {
|
|
5732
|
+
}
|
|
5733
|
+
};
|
|
5734
|
+
|
|
5735
|
+
// src/app/AppManager.ts
|
|
5736
|
+
var import_device_management_kit3 = require("@ledgerhq/device-management-kit");
|
|
5737
|
+
var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
|
|
5738
|
+
var APP_NAME_MAP = {
|
|
5739
|
+
ETH: "Ethereum",
|
|
5740
|
+
BTC: "Bitcoin",
|
|
5741
|
+
SOL: "Solana",
|
|
5742
|
+
TRX: "Tron",
|
|
5743
|
+
ZEC: "Zcash",
|
|
5744
|
+
XRP: "XRP",
|
|
5745
|
+
ADA: "Cardano",
|
|
5746
|
+
DOT: "Polkadot",
|
|
5747
|
+
ATOM: "Cosmos"
|
|
5748
|
+
};
|
|
5749
|
+
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5750
|
+
var AppManager = class {
|
|
5751
|
+
constructor(dmk, options) {
|
|
5752
|
+
this._dmk = dmk;
|
|
5753
|
+
this._waitMs = options?.waitMs ?? 1e3;
|
|
5754
|
+
this._maxRetries = options?.maxRetries ?? 10;
|
|
5755
|
+
}
|
|
5756
|
+
/**
|
|
5757
|
+
* Return the Ledger app name for a given chain ticker,
|
|
5758
|
+
* or undefined if the chain is not supported.
|
|
5759
|
+
*/
|
|
5760
|
+
static getAppName(chain) {
|
|
5761
|
+
return APP_NAME_MAP[chain];
|
|
5762
|
+
}
|
|
5763
|
+
/**
|
|
5764
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5765
|
+
*
|
|
5766
|
+
* Flow:
|
|
5767
|
+
* 1. Check the currently running app.
|
|
5768
|
+
* 2. If it is already the target, return immediately.
|
|
5769
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
5770
|
+
* 4. Open the target app.
|
|
5771
|
+
* 5. Poll until the device confirms the target app is running.
|
|
5772
|
+
*/
|
|
5773
|
+
/**
|
|
5774
|
+
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5775
|
+
* device is about to display "Open <app>" on screen and wait for the
|
|
5776
|
+
* user's button press. UI consumers should show their "open app" prompt
|
|
5777
|
+
* in response. NOT called when the target app is already open (no user
|
|
5778
|
+
* interaction needed in that case).
|
|
5779
|
+
*
|
|
5780
|
+
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5781
|
+
* has physically confirmed on the device, so anything that runs AFTER
|
|
5782
|
+
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5783
|
+
* Hence the callback must fire BEFORE that await.
|
|
5784
|
+
*/
|
|
5785
|
+
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5786
|
+
const currentApp = await this._getCurrentApp(sessionId);
|
|
5787
|
+
if (currentApp === targetAppName) {
|
|
5788
|
+
return;
|
|
5789
|
+
}
|
|
5790
|
+
if (!this._isDashboard(currentApp)) {
|
|
5791
|
+
await this._closeCurrentApp(sessionId);
|
|
5792
|
+
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5793
|
+
}
|
|
5794
|
+
onConfirmOnDevice?.();
|
|
5795
|
+
await this._openApp(sessionId, targetAppName);
|
|
5796
|
+
await this._waitForApp(sessionId, targetAppName);
|
|
5797
|
+
}
|
|
5798
|
+
// ---------------------------------------------------------------------------
|
|
5799
|
+
// Private helpers
|
|
5800
|
+
// ---------------------------------------------------------------------------
|
|
5801
|
+
async _getCurrentApp(sessionId) {
|
|
5802
|
+
const result = await this._dmk.sendCommand({
|
|
5803
|
+
sessionId,
|
|
5804
|
+
command: new import_device_management_kit3.GetAppAndVersionCommand()
|
|
5805
|
+
});
|
|
5806
|
+
if ((0, import_device_management_kit3.isSuccessCommandResult)(result)) {
|
|
5807
|
+
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5808
|
+
return result.data.name;
|
|
5809
|
+
}
|
|
5810
|
+
const errResult = result;
|
|
5811
|
+
const dmkErr = errResult.error ?? {};
|
|
5812
|
+
const original = dmkErr.originalError;
|
|
5813
|
+
debugLog(
|
|
5814
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5815
|
+
sessionId,
|
|
5816
|
+
"tag=",
|
|
5817
|
+
dmkErr._tag,
|
|
5818
|
+
"errorCode=",
|
|
5819
|
+
dmkErr.errorCode,
|
|
5820
|
+
"message=",
|
|
5821
|
+
dmkErr.message,
|
|
5822
|
+
"originalErrorMessage=",
|
|
5823
|
+
original?.message ?? String(original ?? "")
|
|
5824
|
+
);
|
|
5825
|
+
throw Object.assign(
|
|
5826
|
+
new Error(
|
|
5827
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
5828
|
+
),
|
|
5829
|
+
{
|
|
5830
|
+
_tag: dmkErr._tag,
|
|
5831
|
+
errorCode: dmkErr.errorCode,
|
|
5832
|
+
originalError: original
|
|
5833
|
+
}
|
|
5834
|
+
);
|
|
5835
|
+
}
|
|
5836
|
+
async _openApp(sessionId, appName) {
|
|
5837
|
+
const result = await this._dmk.sendCommand({
|
|
5838
|
+
sessionId,
|
|
5839
|
+
command: new import_device_management_kit3.OpenAppCommand({ appName })
|
|
5840
|
+
});
|
|
5841
|
+
if (!(0, import_device_management_kit3.isSuccessCommandResult)(result)) {
|
|
5842
|
+
const dmkErr = result.error;
|
|
5843
|
+
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5844
|
+
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5845
|
+
debugLog(
|
|
5846
|
+
"[AppManager] openApp failed:",
|
|
5847
|
+
appName,
|
|
5848
|
+
"errorCode:",
|
|
5849
|
+
errorCode,
|
|
5850
|
+
"tag:",
|
|
5851
|
+
dmkErr._tag
|
|
5852
|
+
);
|
|
5853
|
+
let code;
|
|
5854
|
+
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5855
|
+
code = import_hwk_adapter_core13.HardwareErrorCode.AppNotInstalled;
|
|
5856
|
+
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5857
|
+
code = import_hwk_adapter_core13.HardwareErrorCode.UserRejected;
|
|
5858
|
+
}
|
|
5859
|
+
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5860
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
5861
|
+
code,
|
|
5862
|
+
errorCode,
|
|
5863
|
+
statusCode: errorCode,
|
|
5864
|
+
appName,
|
|
5865
|
+
originalError: dmkErr
|
|
5866
|
+
});
|
|
5867
|
+
}
|
|
5868
|
+
}
|
|
5869
|
+
async _closeCurrentApp(sessionId) {
|
|
5870
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
5871
|
+
await this._dmk.sendCommand({
|
|
5872
|
+
sessionId,
|
|
5873
|
+
command: new import_device_management_kit3.CloseAppCommand()
|
|
5874
|
+
});
|
|
5875
|
+
}
|
|
5876
|
+
/**
|
|
5877
|
+
* Poll the device until the expected app is reported as running,
|
|
5878
|
+
* or throw after `_maxRetries` attempts.
|
|
5879
|
+
*/
|
|
5880
|
+
async _waitForApp(sessionId, expectedAppName) {
|
|
5881
|
+
let lastSeen = "";
|
|
5882
|
+
for (let i = 0; i < this._maxRetries; i++) {
|
|
5883
|
+
await this._wait();
|
|
5884
|
+
const current = await this._getCurrentApp(sessionId);
|
|
5885
|
+
lastSeen = current;
|
|
5886
|
+
if (current === expectedAppName) {
|
|
5887
|
+
return;
|
|
5888
|
+
}
|
|
5889
|
+
}
|
|
5890
|
+
debugLog(
|
|
5891
|
+
"[AppManager] waitForApp exhausted: expected=",
|
|
5892
|
+
expectedAppName,
|
|
5893
|
+
"lastSeen=",
|
|
5894
|
+
lastSeen
|
|
5895
|
+
);
|
|
5896
|
+
throw new Error(
|
|
5897
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5898
|
+
);
|
|
5899
|
+
}
|
|
5900
|
+
_isDashboard(appName) {
|
|
5901
|
+
return appName === DASHBOARD_APP_NAME;
|
|
5902
|
+
}
|
|
5903
|
+
_wait() {
|
|
5904
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5905
|
+
}
|
|
5906
|
+
};
|
|
4953
5907
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4954
5908
|
0 && (module.exports = {
|
|
4955
5909
|
AppManager,
|
|
@@ -4961,6 +5915,7 @@ var LedgerConnectorBase = class {
|
|
|
4961
5915
|
SignerEth,
|
|
4962
5916
|
SignerManager,
|
|
4963
5917
|
SignerSol,
|
|
5918
|
+
SignerZcash,
|
|
4964
5919
|
debugLog,
|
|
4965
5920
|
deviceActionToPromise,
|
|
4966
5921
|
isDeviceLockedError,
|