@onekeyfe/hwk-ledger-adapter 1.2.5 → 1.2.6-alpha.0
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 +84 -39
- package/dist/index.d.ts +84 -39
- package/dist/index.js +1733 -836
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1745 -828
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -11
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
SignerEth: () => SignerEth,
|
|
40
40
|
SignerManager: () => SignerManager,
|
|
41
41
|
SignerSol: () => SignerSol,
|
|
42
|
+
SignerZcash: () => SignerZcash,
|
|
42
43
|
debugLog: () => debugLog,
|
|
43
44
|
deviceActionToPromise: () => deviceActionToPromise,
|
|
44
45
|
isDeviceLockedError: () => isDeviceLockedError,
|
|
@@ -57,17 +58,14 @@ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
|
|
|
57
58
|
|
|
58
59
|
// src/errors.ts
|
|
59
60
|
var import_hwk_adapter_core = require("@onekeyfe/hwk-adapter-core");
|
|
60
|
-
|
|
61
|
-
function createMultipleUsbLedgerDevicesError() {
|
|
62
|
-
return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
|
|
63
|
-
code: import_hwk_adapter_core.HardwareErrorCode.DeviceOneDeviceOnly
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
function ledgerFailure(code, error, appName, tag, params) {
|
|
61
|
+
function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
|
|
67
62
|
const payload = { error, code };
|
|
68
63
|
if (appName !== void 0) payload.appName = appName;
|
|
69
64
|
if (tag !== void 0) payload._tag = tag;
|
|
70
65
|
if (params !== void 0) payload.params = params;
|
|
66
|
+
const resolvedOrigin = origin ?? (0, import_hwk_adapter_core.defaultOriginForCode)(code);
|
|
67
|
+
if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
|
|
68
|
+
payload.recovery = recovery ?? (0, import_hwk_adapter_core.defaultRecoveryForCode)(code);
|
|
71
69
|
return { success: false, payload };
|
|
72
70
|
}
|
|
73
71
|
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
@@ -208,7 +206,10 @@ var ERROR_TAG = {
|
|
|
208
206
|
UnknownDevice: "UnknownDeviceError",
|
|
209
207
|
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
210
208
|
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
211
|
-
|
|
209
|
+
// DMK class OpeningConnectionError carries `_tag` "ConnectionOpeningError"; both
|
|
210
|
+
// spellings are kept so a DMK release that aligns them still classifies.
|
|
211
|
+
OpeningConnection: "ConnectionOpeningError",
|
|
212
|
+
OpeningConnectionLegacy: "OpeningConnectionError",
|
|
212
213
|
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
213
214
|
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
214
215
|
Disconnect: "DisconnectError",
|
|
@@ -221,7 +222,16 @@ var ERROR_TAG = {
|
|
|
221
222
|
// DMK remote-network failures (manager-api HTTP / secure-channel WS).
|
|
222
223
|
WebSocketConnection: "WebSocketConnectionError",
|
|
223
224
|
HttpFetch: "FetchError",
|
|
224
|
-
|
|
225
|
+
NetworkDA: "NetworkDAError",
|
|
226
|
+
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
|
|
227
|
+
ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
|
|
228
|
+
// DMK OS device actions. SecureChannelError is what mapInstallDAErrors() leaves
|
|
229
|
+
// after splitting out device answers, i.e. the relay itself broke.
|
|
230
|
+
SecureChannel: "SecureChannelError",
|
|
231
|
+
RefusedByUserDA: "RefusedByUserDAError",
|
|
232
|
+
AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
|
|
233
|
+
OutOfMemoryDA: "OutOfMemoryDAError",
|
|
234
|
+
DeviceNotOnboarded: "DeviceNotOnboardedError"
|
|
225
235
|
};
|
|
226
236
|
function isDeviceLockedError(err) {
|
|
227
237
|
if (!err || typeof err !== "object") return false;
|
|
@@ -251,7 +261,6 @@ function isBlePairingFailureError(err) {
|
|
|
251
261
|
return false;
|
|
252
262
|
}
|
|
253
263
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
254
|
-
ERROR_TAG.DeviceLocked,
|
|
255
264
|
ERROR_TAG.DeviceNotAdvertising,
|
|
256
265
|
ERROR_TAG.BlePairingTimeout,
|
|
257
266
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -263,6 +272,7 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
|
263
272
|
ERROR_TAG.DeviceSessionRefresher,
|
|
264
273
|
ERROR_TAG.DeviceNotInitialized,
|
|
265
274
|
ERROR_TAG.OpeningConnection,
|
|
275
|
+
ERROR_TAG.OpeningConnectionLegacy,
|
|
266
276
|
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
267
277
|
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
268
278
|
ERROR_TAG.Disconnect,
|
|
@@ -277,7 +287,10 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
|
277
287
|
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
278
288
|
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
279
289
|
]);
|
|
280
|
-
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
290
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
291
|
+
ERROR_TAG.OpeningConnection,
|
|
292
|
+
ERROR_TAG.OpeningConnectionLegacy
|
|
293
|
+
]);
|
|
281
294
|
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
282
295
|
ERROR_TAG.DeviceNotRecognized,
|
|
283
296
|
ERROR_TAG.DeviceSessionNotFound,
|
|
@@ -289,17 +302,18 @@ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
|
289
302
|
ERROR_TAG.WebHIDDisconnect
|
|
290
303
|
]);
|
|
291
304
|
function isConnectionLevelError(err) {
|
|
292
|
-
|
|
293
|
-
const tag = err._tag;
|
|
294
|
-
if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
|
|
295
|
-
const e = err;
|
|
296
|
-
if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
|
|
297
|
-
if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
|
|
298
|
-
return false;
|
|
305
|
+
return hasErrorTag(err, CONNECTION_LEVEL_TAGS);
|
|
299
306
|
}
|
|
300
307
|
function isKnownConnectionTag(tag) {
|
|
301
308
|
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
302
309
|
}
|
|
310
|
+
var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
|
|
311
|
+
ERROR_TAG.OpeningConnection,
|
|
312
|
+
ERROR_TAG.OpeningConnectionLegacy
|
|
313
|
+
]);
|
|
314
|
+
function isConnectionOpeningTag(tag) {
|
|
315
|
+
return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
|
|
316
|
+
}
|
|
303
317
|
function hasStatusCode(err, codeSet) {
|
|
304
318
|
if (!err || typeof err !== "object") return false;
|
|
305
319
|
const e = err;
|
|
@@ -335,28 +349,28 @@ function hasInvalidArgumentCode(err) {
|
|
|
335
349
|
if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
|
|
336
350
|
return false;
|
|
337
351
|
}
|
|
338
|
-
function
|
|
352
|
+
function hasErrorTag(err, tags) {
|
|
339
353
|
if (!err || typeof err !== "object") return false;
|
|
340
|
-
const tag = err._tag;
|
|
341
|
-
if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
|
|
342
354
|
const e = err;
|
|
343
|
-
if (e.
|
|
344
|
-
if (e.
|
|
355
|
+
if (typeof e._tag === "string" && tags.has(e._tag)) return true;
|
|
356
|
+
if (e.originalError != null && hasErrorTag(e.originalError, tags)) return true;
|
|
357
|
+
if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
|
|
345
358
|
return false;
|
|
346
359
|
}
|
|
360
|
+
function isDeviceNotFoundError(err) {
|
|
361
|
+
return hasErrorTag(err, DEVICE_NOT_FOUND_TAGS);
|
|
362
|
+
}
|
|
347
363
|
function isDeviceBusyError(err) {
|
|
348
|
-
|
|
349
|
-
const tag = err._tag;
|
|
350
|
-
if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
|
|
351
|
-
const e = err;
|
|
352
|
-
if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
|
|
353
|
-
if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
|
|
354
|
-
return false;
|
|
364
|
+
return hasErrorTag(err, DEVICE_BUSY_TAGS);
|
|
355
365
|
}
|
|
366
|
+
var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
|
|
367
|
+
ERROR_TAG.UserRefusedOnDevice,
|
|
368
|
+
ERROR_TAG.RefusedByUserDA
|
|
369
|
+
]);
|
|
356
370
|
function isUserRejectedError(err) {
|
|
357
371
|
if (!err || typeof err !== "object") return false;
|
|
358
372
|
const e = err;
|
|
359
|
-
if (
|
|
373
|
+
if (hasErrorTag(err, USER_REJECTED_TAGS)) return true;
|
|
360
374
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
361
375
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
362
376
|
return false;
|
|
@@ -387,15 +401,33 @@ function isAppNotInstalledError(err) {
|
|
|
387
401
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
388
402
|
return false;
|
|
389
403
|
}
|
|
404
|
+
var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
|
|
390
405
|
function isOutOfMemoryError(err) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
406
|
+
return hasErrorTag(err, OUT_OF_MEMORY_TAGS);
|
|
407
|
+
}
|
|
408
|
+
var APP_ALREADY_INSTALLED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.AppAlreadyInstalledDA]);
|
|
409
|
+
function isAppAlreadyInstalledError(err) {
|
|
410
|
+
return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
|
|
411
|
+
}
|
|
412
|
+
var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.DeviceNotOnboarded]);
|
|
413
|
+
function isDeviceNotOnboardedError(err) {
|
|
414
|
+
return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
|
|
415
|
+
}
|
|
416
|
+
var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
|
|
417
|
+
ERROR_TAG.InvalidFirmwareMetadataResponse,
|
|
418
|
+
ERROR_TAG.ApplicationsMetadataTask
|
|
419
|
+
]);
|
|
420
|
+
function isFirmwareMetadataError(err) {
|
|
421
|
+
return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
|
|
422
|
+
}
|
|
423
|
+
var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
|
|
424
|
+
function isSecureChannelError(err) {
|
|
425
|
+
return hasErrorTag(err, SECURE_CHANNEL_TAGS);
|
|
394
426
|
}
|
|
395
427
|
var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
|
|
396
428
|
ERROR_TAG.WebSocketConnection,
|
|
397
429
|
ERROR_TAG.HttpFetch,
|
|
398
|
-
ERROR_TAG.
|
|
430
|
+
ERROR_TAG.NetworkDA
|
|
399
431
|
]);
|
|
400
432
|
function isNetworkError(err) {
|
|
401
433
|
if (!err || typeof err !== "object") return false;
|
|
@@ -445,23 +477,34 @@ function isTransportStuck(err) {
|
|
|
445
477
|
function isStuckAppStateError(err) {
|
|
446
478
|
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
447
479
|
}
|
|
480
|
+
var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
|
|
481
|
+
function nestedErrorMessage(nested) {
|
|
482
|
+
if (!nested || typeof nested !== "object") return void 0;
|
|
483
|
+
const { message } = nested;
|
|
484
|
+
if (typeof message !== "string") return void 0;
|
|
485
|
+
const trimmed = message.trim();
|
|
486
|
+
if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
|
|
487
|
+
return message;
|
|
488
|
+
}
|
|
448
489
|
function mapLedgerError(err, opts) {
|
|
449
490
|
let originalMessage = "Unknown Ledger error";
|
|
450
491
|
if (err instanceof Error) {
|
|
451
492
|
originalMessage = err.message;
|
|
452
493
|
} else if (err && typeof err === "object") {
|
|
453
494
|
const e = err;
|
|
454
|
-
originalMessage = String(
|
|
495
|
+
originalMessage = String(
|
|
496
|
+
e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
|
|
497
|
+
);
|
|
455
498
|
}
|
|
456
499
|
let code;
|
|
457
500
|
if (isDeviceLockedError(err)) {
|
|
458
501
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceLocked;
|
|
459
502
|
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
460
503
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotFound;
|
|
461
|
-
} else if (isDeviceBusyError(err)) {
|
|
462
|
-
code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
|
|
463
504
|
} else if (isBlePairingFailureError(err)) {
|
|
464
505
|
code = import_hwk_adapter_core.HardwareErrorCode.BlePairingTimeout;
|
|
506
|
+
} else if (isDeviceBusyError(err)) {
|
|
507
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceBusy;
|
|
465
508
|
} else if (isUserAbortedError(err)) {
|
|
466
509
|
code = import_hwk_adapter_core.HardwareErrorCode.UserAborted;
|
|
467
510
|
} else if (isUserRejectedError(err)) {
|
|
@@ -470,10 +513,18 @@ function mapLedgerError(err, opts) {
|
|
|
470
513
|
code = import_hwk_adapter_core.HardwareErrorCode.WrongApp;
|
|
471
514
|
} else if (isAppNotInstalledError(err)) {
|
|
472
515
|
code = import_hwk_adapter_core.HardwareErrorCode.AppNotInstalled;
|
|
516
|
+
} else if (isAppAlreadyInstalledError(err)) {
|
|
517
|
+
code = import_hwk_adapter_core.HardwareErrorCode.AppAlreadyInstalled;
|
|
473
518
|
} else if (isOutOfMemoryError(err)) {
|
|
474
519
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceOutOfMemory;
|
|
520
|
+
} else if (isDeviceNotOnboardedError(err)) {
|
|
521
|
+
code = import_hwk_adapter_core.HardwareErrorCode.DeviceNotInitialized;
|
|
522
|
+
} else if (isFirmwareMetadataError(err)) {
|
|
523
|
+
code = import_hwk_adapter_core.HardwareErrorCode.LedgerFirmwareMetadataError;
|
|
475
524
|
} else if (isNetworkError(err)) {
|
|
476
525
|
code = import_hwk_adapter_core.HardwareErrorCode.NetworkError;
|
|
526
|
+
} else if (isSecureChannelError(err)) {
|
|
527
|
+
code = import_hwk_adapter_core.HardwareErrorCode.LedgerSecureChannelError;
|
|
477
528
|
} else if (isDeviceDisconnectedError(err)) {
|
|
478
529
|
code = import_hwk_adapter_core.HardwareErrorCode.DeviceDisconnected;
|
|
479
530
|
} else if (isTimeoutError(err)) {
|
|
@@ -486,7 +537,21 @@ function mapLedgerError(err, opts) {
|
|
|
486
537
|
}
|
|
487
538
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
488
539
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
489
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
code,
|
|
542
|
+
message: (0, import_hwk_adapter_core.enrichErrorMessage)(code, originalMessage),
|
|
543
|
+
origin: (0, import_hwk_adapter_core.defaultOriginForCode)(code),
|
|
544
|
+
appName
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/utils/queueKey.ts
|
|
549
|
+
var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
|
|
550
|
+
function ledgerQueueKey({
|
|
551
|
+
operationId,
|
|
552
|
+
connectId
|
|
553
|
+
}) {
|
|
554
|
+
return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
|
|
490
555
|
}
|
|
491
556
|
|
|
492
557
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
@@ -547,65 +612,103 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
547
612
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
548
613
|
function createAllNetworkGetAddress({
|
|
549
614
|
callChain,
|
|
550
|
-
getChainFingerprint
|
|
615
|
+
getChainFingerprint,
|
|
616
|
+
retainOperation,
|
|
617
|
+
errorToFailure,
|
|
618
|
+
createCancelScope
|
|
551
619
|
}) {
|
|
552
620
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
553
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
621
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
622
|
+
method: "allNetworkGetAddress",
|
|
623
|
+
connectId,
|
|
624
|
+
itemCount: params.bundle.length
|
|
625
|
+
});
|
|
626
|
+
const target = (0, import_hwk_adapter_core2.resolveHardwareOperationTarget)(connectId, params.operationId, "ledger");
|
|
627
|
+
if (!target.success) return target;
|
|
628
|
+
const effectiveTargetId = target.payload.targetId ?? "";
|
|
629
|
+
let releaseOperationRetention;
|
|
630
|
+
try {
|
|
631
|
+
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
632
|
+
} catch (error) {
|
|
633
|
+
return errorToFailure(error);
|
|
634
|
+
}
|
|
635
|
+
const cancelScope = createCancelScope(
|
|
636
|
+
ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
637
|
+
);
|
|
554
638
|
const installContext = {};
|
|
555
639
|
const commonParams = {
|
|
556
|
-
autoInstallApp: params.autoInstallApp
|
|
640
|
+
autoInstallApp: params.autoInstallApp,
|
|
641
|
+
operationId: target.payload.operationId,
|
|
642
|
+
knownConnections: params.knownConnections,
|
|
643
|
+
extra: params.extra,
|
|
644
|
+
allowDeviceSelection: params.allowDeviceSelection,
|
|
645
|
+
supportedTransports: params.supportedTransports
|
|
557
646
|
};
|
|
558
647
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
648
|
+
try {
|
|
649
|
+
const result = await (0, import_hwk_adapter_core2.runAllNetworkGetAddress)({
|
|
650
|
+
connectId: effectiveTargetId,
|
|
651
|
+
deviceId: _deviceId,
|
|
652
|
+
params,
|
|
653
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
654
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
655
|
+
callItem: async ({ method, chain, item }) => {
|
|
656
|
+
if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
|
|
657
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
658
|
+
return callAllNetworkMethod(
|
|
659
|
+
callChain,
|
|
660
|
+
effectiveTargetId,
|
|
661
|
+
itemDeviceId,
|
|
662
|
+
method,
|
|
663
|
+
item,
|
|
664
|
+
commonParams,
|
|
665
|
+
installContext
|
|
666
|
+
);
|
|
667
|
+
},
|
|
668
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
669
|
+
getChainFingerprint,
|
|
670
|
+
effectiveTargetId,
|
|
572
671
|
item,
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
});
|
|
600
|
-
return result;
|
|
672
|
+
chain,
|
|
673
|
+
payload,
|
|
674
|
+
chainFingerprints,
|
|
675
|
+
installContext,
|
|
676
|
+
cancelScope.signal
|
|
677
|
+
),
|
|
678
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
679
|
+
buildTopLevelFailure: (response) => {
|
|
680
|
+
const code = response.payload?.code ?? import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch;
|
|
681
|
+
return (0, import_hwk_adapter_core2.failure)(
|
|
682
|
+
code,
|
|
683
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
684
|
+
response.payload?.params
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
689
|
+
method: "allNetworkGetAddress",
|
|
690
|
+
success: result.success,
|
|
691
|
+
payload: result
|
|
692
|
+
});
|
|
693
|
+
return result;
|
|
694
|
+
} finally {
|
|
695
|
+
cancelScope.release();
|
|
696
|
+
releaseOperationRetention?.();
|
|
697
|
+
}
|
|
601
698
|
};
|
|
602
699
|
}
|
|
700
|
+
function buildCancelledFailure(signal) {
|
|
701
|
+
const reason = signal.reason;
|
|
702
|
+
const code = typeof reason?.code === "number" ? reason.code : import_hwk_adapter_core2.HardwareErrorCode.UserAborted;
|
|
703
|
+
const message = typeof reason?.message === "string" ? reason.message : "";
|
|
704
|
+
return (0, import_hwk_adapter_core2.failure)(code, message || "All-network get-address cancelled");
|
|
705
|
+
}
|
|
603
706
|
function isTopLevelAllNetworkFailure(response) {
|
|
604
707
|
if (response.success) {
|
|
605
708
|
return false;
|
|
606
709
|
}
|
|
607
710
|
const code = response.payload?.code;
|
|
608
|
-
return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch ||
|
|
711
|
+
return code === import_hwk_adapter_core2.HardwareErrorCode.DeviceMismatch || (0, import_hwk_adapter_core2.isUserRefusal)(code) || (0, import_hwk_adapter_core2.isConnectionLost)(code);
|
|
609
712
|
}
|
|
610
713
|
function getItemDeviceId(item) {
|
|
611
714
|
const { deviceId } = item;
|
|
@@ -635,8 +738,18 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
635
738
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
636
739
|
return coin ? { ...item, coin } : item;
|
|
637
740
|
}
|
|
638
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
639
|
-
const
|
|
741
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
|
|
742
|
+
const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
|
|
743
|
+
if (!knownFingerprint && cancelSignal.aborted) {
|
|
744
|
+
const cancelled = buildCancelledFailure(cancelSignal);
|
|
745
|
+
return { ...item, success: false, payload: cancelled.payload };
|
|
746
|
+
}
|
|
747
|
+
const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
|
|
748
|
+
getChainFingerprint,
|
|
749
|
+
context.connection?.connectId ?? connectId,
|
|
750
|
+
chain,
|
|
751
|
+
context
|
|
752
|
+
);
|
|
640
753
|
if (!fingerprint) {
|
|
641
754
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
642
755
|
}
|
|
@@ -657,8 +770,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
657
770
|
}
|
|
658
771
|
};
|
|
659
772
|
}
|
|
660
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
661
|
-
const response = await getChainFingerprint(connectId, chain);
|
|
773
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
|
|
774
|
+
const response = await getChainFingerprint(connectId, chain, context);
|
|
662
775
|
return response.success ? response.payload : "";
|
|
663
776
|
}
|
|
664
777
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -751,6 +864,16 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
|
|
|
751
864
|
function formatDeviceMismatchError(expected, actual) {
|
|
752
865
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
753
866
|
}
|
|
867
|
+
var LOG_REDACTED_RESULT_KEYS = ["ufvk", "orchardFvk"];
|
|
868
|
+
function redactResultForLog(result) {
|
|
869
|
+
if (!result || typeof result !== "object") return result;
|
|
870
|
+
const keys = LOG_REDACTED_RESULT_KEYS.filter((key) => key in result);
|
|
871
|
+
if (!keys.length) return result;
|
|
872
|
+
return { ...result, ...Object.fromEntries(keys.map((key) => [key, "[redacted]"])) };
|
|
873
|
+
}
|
|
874
|
+
function isLostConnectionError(err) {
|
|
875
|
+
return isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err);
|
|
876
|
+
}
|
|
754
877
|
var BTC_HIGH_INDEX_THRESHOLD = 100;
|
|
755
878
|
function btcAccountIndexFromPath(path) {
|
|
756
879
|
const segments = path.replace(/^m\//, "").split("/");
|
|
@@ -762,9 +885,35 @@ function btcAccountIndexFromPath(path) {
|
|
|
762
885
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
763
886
|
constructor(connector, options) {
|
|
764
887
|
this.vendor = "ledger";
|
|
888
|
+
// Cancel frees the DMK action and its intent-queue slot, but cannot retract a
|
|
889
|
+
// confirmation screen the device is already showing.
|
|
890
|
+
this.cancelCapability = "stops-waiting";
|
|
765
891
|
this.emitter = new import_hwk_adapter_core3.TypedEventEmitter();
|
|
892
|
+
this._operations = new import_hwk_adapter_core3.OperationRegistry({
|
|
893
|
+
vendor: "ledger",
|
|
894
|
+
onEnded: (operation, reason) => {
|
|
895
|
+
this._discardPendingBinding(this._pendingOperationBindings.get(operation.operationId));
|
|
896
|
+
this._pendingOperationBindings.delete(operation.operationId);
|
|
897
|
+
this.emitter.emit(import_hwk_adapter_core3.SDK.OPERATION_ENDED, {
|
|
898
|
+
type: import_hwk_adapter_core3.SDK.OPERATION_ENDED,
|
|
899
|
+
payload: { operationId: operation.operationId, reason }
|
|
900
|
+
});
|
|
901
|
+
if (reason === "timeout") {
|
|
902
|
+
void this._releaseOperationConnection(operation);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
});
|
|
766
906
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
767
907
|
this._sessions = /* @__PURE__ */ new Map();
|
|
908
|
+
// An operation's BLE pick is saved by its first call that gets past the wallet check.
|
|
909
|
+
this._pendingOperationBindings = /* @__PURE__ */ new Map();
|
|
910
|
+
// A one-shot call's unchecked BLE pick, by connectId. It outlives the call only
|
|
911
|
+
// through an unlock retry; any other failure drops it with its session.
|
|
912
|
+
this._pendingPickBindings = /* @__PURE__ */ new Map();
|
|
913
|
+
// Transport each session was opened on, by sessionId. The adapter-wide
|
|
914
|
+
// `_activeConnectionType` moves with every discovery and can go stale.
|
|
915
|
+
this._sessionTransports = /* @__PURE__ */ new Map();
|
|
916
|
+
this._selectedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
768
917
|
this._uiRegistry = new import_hwk_adapter_core3.UiRequestRegistry();
|
|
769
918
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
770
919
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -778,10 +927,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
778
927
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
779
928
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
780
929
|
this._doConnectAbortController = null;
|
|
930
|
+
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
931
|
+
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
932
|
+
this._resetPromise = null;
|
|
933
|
+
this._stateGeneration = 0;
|
|
934
|
+
this._connectorTeardownTail = Promise.resolve();
|
|
935
|
+
this._pendingConnectorTeardowns = 0;
|
|
936
|
+
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
937
|
+
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
781
938
|
this._installProgressLastEmittedValue = -Infinity;
|
|
782
939
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
783
940
|
callChain: this.callChain.bind(this),
|
|
784
|
-
getChainFingerprint: (connectId, chain) =>
|
|
941
|
+
getChainFingerprint: async (connectId, chain, context) => {
|
|
942
|
+
try {
|
|
943
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
944
|
+
chain,
|
|
945
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
946
|
+
);
|
|
947
|
+
return (0, import_hwk_adapter_core3.success)(fingerprint);
|
|
948
|
+
} catch (error) {
|
|
949
|
+
return this.errorToFailure(error);
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
953
|
+
errorToFailure: (error) => this.errorToFailure(error),
|
|
954
|
+
createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
|
|
785
955
|
});
|
|
786
956
|
// ---------------------------------------------------------------------------
|
|
787
957
|
// Private helpers
|
|
@@ -791,7 +961,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
791
961
|
*
|
|
792
962
|
* - If a session already exists for the given connectId, reuse it.
|
|
793
963
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
794
|
-
* - Otherwise: search →
|
|
964
|
+
* - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
|
|
795
965
|
*/
|
|
796
966
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
797
967
|
this._connectingPromise = null;
|
|
@@ -808,6 +978,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
808
978
|
});
|
|
809
979
|
};
|
|
810
980
|
this.deviceDisconnectHandler = (data) => {
|
|
981
|
+
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
982
|
+
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
983
|
+
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
984
|
+
} else {
|
|
985
|
+
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
986
|
+
}
|
|
811
987
|
this._discoveredDevices.delete(data.connectId);
|
|
812
988
|
this._sessions.delete(data.connectId);
|
|
813
989
|
this.emitter.emit(import_hwk_adapter_core3.DEVICE.DISCONNECT, {
|
|
@@ -816,10 +992,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
816
992
|
});
|
|
817
993
|
};
|
|
818
994
|
// Forward connector `ui-event` to the public hw.emitter so consumers only
|
|
819
|
-
// need to subscribe in one place.
|
|
820
|
-
//
|
|
821
|
-
// exists (race during teardown) we drop. All other variants pass through
|
|
822
|
-
// unchanged.
|
|
995
|
+
// need to subscribe in one place. The AppInstallProgress variant re-keys
|
|
996
|
+
// sessionId to connectId via `_sessions`, and drops if no mapping exists.
|
|
823
997
|
this.uiEventForwarder = (event) => {
|
|
824
998
|
if (event.type === import_hwk_adapter_core3.EConnectorInteraction.AppInstallProgress) {
|
|
825
999
|
let connectId;
|
|
@@ -864,11 +1038,34 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
864
1038
|
this._jobQueue = new import_hwk_adapter_core3.DeviceJobQueue();
|
|
865
1039
|
this.registerEventListeners();
|
|
866
1040
|
}
|
|
1041
|
+
_discardPendingBinding(owner, status = "cancelled") {
|
|
1042
|
+
const pending = owner?.pendingBinding;
|
|
1043
|
+
if (!owner || !pending) return;
|
|
1044
|
+
owner.pendingBinding = void 0;
|
|
1045
|
+
if (pending.requestId === this._bindingSelectionRequestId) this._finishBleBinding(status);
|
|
1046
|
+
}
|
|
1047
|
+
_finishBleBinding(status) {
|
|
1048
|
+
const selectionRequestId = this._bindingSelectionRequestId;
|
|
1049
|
+
this._bindingSelectionRequestId = void 0;
|
|
1050
|
+
if (selectionRequestId)
|
|
1051
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
1052
|
+
type: import_hwk_adapter_core3.UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
1053
|
+
payload: { selectionRequestId, status }
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
_isBleConnection() {
|
|
1057
|
+
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
1058
|
+
}
|
|
867
1059
|
// Transport
|
|
868
1060
|
get activeTransport() {
|
|
869
|
-
return
|
|
1061
|
+
return this._isBleConnection() ? "ble" : "hid";
|
|
870
1062
|
}
|
|
871
1063
|
getAvailableTransports() {
|
|
1064
|
+
if (this.connector.availableTransports) {
|
|
1065
|
+
return this.connector.availableTransports.map(
|
|
1066
|
+
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
872
1069
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
873
1070
|
}
|
|
874
1071
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -885,21 +1082,42 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
885
1082
|
* The next operation will re-discover and re-connect automatically.
|
|
886
1083
|
*/
|
|
887
1084
|
resetState() {
|
|
1085
|
+
void this._resetStateAndDisconnectSessions();
|
|
1086
|
+
}
|
|
1087
|
+
_resetStateAndDisconnectSessions() {
|
|
1088
|
+
if (this._resetPromise) return this._resetPromise;
|
|
1089
|
+
const sessionIds = new Set(this._sessions.values());
|
|
1090
|
+
this._stateGeneration += 1;
|
|
1091
|
+
this._finishBleBinding("cancelled");
|
|
1092
|
+
this._operations.endAll("runtime-reset");
|
|
1093
|
+
this._doConnectAbortController?.abort();
|
|
888
1094
|
this._discoveredDevices.clear();
|
|
889
1095
|
this._sessions.clear();
|
|
1096
|
+
this._sessionTransports.clear();
|
|
1097
|
+
this._pendingOperationBindings.clear();
|
|
1098
|
+
this._pendingPickBindings.clear();
|
|
1099
|
+
this._selectedBleReconnectTargets.clear();
|
|
890
1100
|
this._connectingPromise = null;
|
|
1101
|
+
this._doConnectAbortController = null;
|
|
891
1102
|
this._uiRegistry.reset();
|
|
892
1103
|
this._jobQueue.clear();
|
|
893
1104
|
this._btcHighIndexConfirmedThisSession = false;
|
|
1105
|
+
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1106
|
+
for (const sessionId of sessionIds) {
|
|
1107
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
this._resetPromise = resetPromise;
|
|
1111
|
+
return resetPromise.finally(() => {
|
|
1112
|
+
if (this._resetPromise === resetPromise) {
|
|
1113
|
+
this._resetPromise = null;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
894
1116
|
}
|
|
895
1117
|
async dispose() {
|
|
896
|
-
this.
|
|
897
|
-
this._jobQueue.clear();
|
|
1118
|
+
await this._resetStateAndDisconnectSessions();
|
|
898
1119
|
this.unregisterEventListeners();
|
|
899
1120
|
this.connector.reset();
|
|
900
|
-
this._discoveredDevices.clear();
|
|
901
|
-
this._sessions.clear();
|
|
902
|
-
this._btcHighIndexConfirmedThisSession = false;
|
|
903
1121
|
this.emitter.removeAllListeners();
|
|
904
1122
|
}
|
|
905
1123
|
uiResponse(response) {
|
|
@@ -909,17 +1127,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
909
1127
|
// Device management
|
|
910
1128
|
// ---------------------------------------------------------------------------
|
|
911
1129
|
async searchDevices(options) {
|
|
1130
|
+
return this._searchDevices(options);
|
|
1131
|
+
}
|
|
1132
|
+
async _searchDevices(options, signal) {
|
|
912
1133
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
913
1134
|
try {
|
|
914
1135
|
if (options?.resetSession) {
|
|
915
|
-
this.
|
|
916
|
-
|
|
917
|
-
this.
|
|
918
|
-
|
|
919
|
-
|
|
1136
|
+
await this._resetStateAndDisconnectSessions();
|
|
1137
|
+
} else {
|
|
1138
|
+
await this._connectorTeardownTail;
|
|
1139
|
+
}
|
|
1140
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1141
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1142
|
+
const stateGeneration = this._stateGeneration;
|
|
1143
|
+
const devices = await this.connector.searchDevices(
|
|
1144
|
+
options?.transportType ? {
|
|
1145
|
+
transportType: options.transportType,
|
|
1146
|
+
waitForAll: options.waitForAllTransports
|
|
1147
|
+
} : void 0
|
|
1148
|
+
);
|
|
1149
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1150
|
+
if (stateGeneration !== this._stateGeneration) {
|
|
1151
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1152
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
1153
|
+
message: "Ledger discovery was reset"
|
|
1154
|
+
});
|
|
920
1155
|
}
|
|
921
|
-
await this._ensureDevicePermission();
|
|
922
|
-
const devices = await this.connector.searchDevices();
|
|
923
1156
|
this._discoveredDevices.clear();
|
|
924
1157
|
for (const d of devices) {
|
|
925
1158
|
if (d.connectId) {
|
|
@@ -927,7 +1160,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
927
1160
|
}
|
|
928
1161
|
}
|
|
929
1162
|
if (this._discoveredDevices.size === 0) {
|
|
930
|
-
await this._ensureDevicePermission();
|
|
1163
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
931
1164
|
}
|
|
932
1165
|
const result = Array.from(this._discoveredDevices.values());
|
|
933
1166
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -946,41 +1179,280 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
946
1179
|
throw err;
|
|
947
1180
|
}
|
|
948
1181
|
}
|
|
1182
|
+
async searchDeviceTargets(options) {
|
|
1183
|
+
const devices = await this.searchDevices(options);
|
|
1184
|
+
return devices.map((device) => ({
|
|
1185
|
+
searchTargetId: device.connectId,
|
|
1186
|
+
searchTargetReusePolicy: (0, import_hwk_adapter_core3.resolveSearchTargetReusePolicy)(device),
|
|
1187
|
+
vendor: "ledger",
|
|
1188
|
+
connectionType: device.connectionType,
|
|
1189
|
+
kind: "physical",
|
|
1190
|
+
label: device.label,
|
|
1191
|
+
model: device.model,
|
|
1192
|
+
modelName: device.modelName,
|
|
1193
|
+
serialNumber: device.serialNumber
|
|
1194
|
+
}));
|
|
1195
|
+
}
|
|
1196
|
+
async listConnectionTargets(options) {
|
|
1197
|
+
const targets = await this.searchDeviceTargets(options);
|
|
1198
|
+
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1199
|
+
...target,
|
|
1200
|
+
targetId: searchTargetId
|
|
1201
|
+
}));
|
|
1202
|
+
}
|
|
949
1203
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
950
|
-
async _evictAllSessions() {
|
|
1204
|
+
async _evictAllSessions(preserveOperationId) {
|
|
1205
|
+
this._operations.endAll("explicit", preserveOperationId);
|
|
951
1206
|
if (this._sessions.size === 0) return;
|
|
952
1207
|
const stale = [...this._sessions.values()];
|
|
953
1208
|
this._sessions.clear();
|
|
954
|
-
for (const sid of stale)
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1209
|
+
for (const sid of stale) this._sessionTransports.delete(sid);
|
|
1210
|
+
await this._runConnectorTeardown(async () => {
|
|
1211
|
+
for (const sid of stale) {
|
|
1212
|
+
try {
|
|
1213
|
+
await this.connector.disconnect(sid);
|
|
1214
|
+
} catch {
|
|
1215
|
+
}
|
|
958
1216
|
}
|
|
959
|
-
}
|
|
1217
|
+
});
|
|
960
1218
|
}
|
|
961
1219
|
static _createDeviceBusyError(method) {
|
|
962
1220
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
963
1221
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceBusy
|
|
964
1222
|
});
|
|
965
1223
|
}
|
|
966
|
-
async connectDevice(
|
|
1224
|
+
async connectDevice(searchTargetId) {
|
|
1225
|
+
try {
|
|
1226
|
+
return await this._jobQueue.enqueue(
|
|
1227
|
+
searchTargetId || "__ledger_connect__",
|
|
1228
|
+
async (signal) => {
|
|
1229
|
+
const connected = await this._connectTarget(searchTargetId, void 0, signal);
|
|
1230
|
+
if (!connected.success) return connected;
|
|
1231
|
+
return this._createOperation(searchTargetId, connected.payload);
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
label: "connectDevice",
|
|
1235
|
+
rejectIfBusy: true,
|
|
1236
|
+
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1237
|
+
}
|
|
1238
|
+
);
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
return this.errorToFailure(error);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
/** Binds the picked BLE device; with a recorded wallet identity it must match first. */
|
|
1244
|
+
async bindBleDevice(params) {
|
|
1245
|
+
const identity = params.identity?.vendor === "ledger" ? params.identity : void 0;
|
|
1246
|
+
try {
|
|
1247
|
+
return await this._jobQueue.enqueue(
|
|
1248
|
+
"__ledger_bind_ble__",
|
|
1249
|
+
async (signal) => {
|
|
1250
|
+
if (!this.getAvailableTransports().includes("ble")) {
|
|
1251
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1252
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
|
|
1253
|
+
message: "Ledger Bluetooth transport is not available"
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
this._activeConnectionType = "ble";
|
|
1257
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1258
|
+
const attempt = {
|
|
1259
|
+
extra: params.extra,
|
|
1260
|
+
bindingReason: "manual-rebind",
|
|
1261
|
+
deferBindingSave: Boolean(identity)
|
|
1262
|
+
};
|
|
1263
|
+
try {
|
|
1264
|
+
const connectId = await this._connectFirstOrSelect(
|
|
1265
|
+
[],
|
|
1266
|
+
void 0,
|
|
1267
|
+
true,
|
|
1268
|
+
void 0,
|
|
1269
|
+
attempt,
|
|
1270
|
+
signal
|
|
1271
|
+
);
|
|
1272
|
+
const pending = attempt.pendingBinding;
|
|
1273
|
+
if (identity && pending) {
|
|
1274
|
+
attempt.pendingBinding = void 0;
|
|
1275
|
+
const sessionId = this._sessions.get(connectId);
|
|
1276
|
+
if (!sessionId) {
|
|
1277
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1278
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
1279
|
+
message: "Selected Ledger connection ended"
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1283
|
+
identity.chain,
|
|
1284
|
+
(method, callParams) => this._runConnectorCall(
|
|
1285
|
+
connectId,
|
|
1286
|
+
method,
|
|
1287
|
+
callParams,
|
|
1288
|
+
signal,
|
|
1289
|
+
void 0,
|
|
1290
|
+
void 0,
|
|
1291
|
+
{ autoInstallApp: true },
|
|
1292
|
+
{ connection: { connectId, sessionId } }
|
|
1293
|
+
)
|
|
1294
|
+
);
|
|
1295
|
+
if (fingerprint !== identity.value) {
|
|
1296
|
+
await this._dropSession(connectId, signal);
|
|
1297
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1298
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
1299
|
+
message: formatDeviceMismatchError(identity.value, fingerprint)
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
await this._saveSelectedBleBinding(
|
|
1303
|
+
connectId,
|
|
1304
|
+
pending.requestId,
|
|
1305
|
+
attempt,
|
|
1306
|
+
void 0,
|
|
1307
|
+
signal,
|
|
1308
|
+
identity
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
if (!attempt.bindingSaved) {
|
|
1312
|
+
await this._dropSession(connectId, signal);
|
|
1313
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
1314
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UnknownError,
|
|
1315
|
+
message: "Bluetooth binding could not be saved",
|
|
1316
|
+
origin: "host"
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
return (0, import_hwk_adapter_core3.success)(connectId);
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1322
|
+
throw error;
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
label: "bindBleDevice",
|
|
1327
|
+
rejectIfBusy: true,
|
|
1328
|
+
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1329
|
+
}
|
|
1330
|
+
);
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
return this.errorToFailure(error);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
async acquireOperation(connectId, context) {
|
|
1336
|
+
try {
|
|
1337
|
+
return await this._jobQueue.enqueue(
|
|
1338
|
+
connectId || "__ledger_acquire__",
|
|
1339
|
+
async (signal) => {
|
|
1340
|
+
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1341
|
+
const hint = context.knownConnections?.find(
|
|
1342
|
+
(connection) => connection.transport === transport
|
|
1343
|
+
);
|
|
1344
|
+
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1345
|
+
await this._ensureDevicePermission(target, void 0, signal);
|
|
1346
|
+
const attempt = {
|
|
1347
|
+
...context,
|
|
1348
|
+
extra: context.extra ? { ...context.extra } : void 0,
|
|
1349
|
+
// The operation's calls decide whether the pick is the right wallet.
|
|
1350
|
+
deferBindingSave: true
|
|
1351
|
+
};
|
|
1352
|
+
try {
|
|
1353
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1354
|
+
target,
|
|
1355
|
+
signal,
|
|
1356
|
+
true,
|
|
1357
|
+
void 0,
|
|
1358
|
+
attempt
|
|
1359
|
+
);
|
|
1360
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1361
|
+
const result = this._createOperation(connectId, resolvedConnectId);
|
|
1362
|
+
if (result.success && attempt.pendingBinding) {
|
|
1363
|
+
this._pendingOperationBindings.set(result.payload, attempt);
|
|
1364
|
+
}
|
|
1365
|
+
return result;
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1368
|
+
throw error;
|
|
1369
|
+
}
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
label: "acquireOperation",
|
|
1373
|
+
rejectIfBusy: true,
|
|
1374
|
+
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1375
|
+
}
|
|
1376
|
+
);
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
return this.errorToFailure(error);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1382
|
+
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1383
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1384
|
+
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1385
|
+
const connectionType = sessionId && this._sessionTransports.get(sessionId) || (this._isBleConnection() ? "ble" : "usb");
|
|
1386
|
+
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1387
|
+
vendor: "ledger",
|
|
1388
|
+
model: "unknown",
|
|
1389
|
+
firmwareVersion: "",
|
|
1390
|
+
deviceId: "",
|
|
1391
|
+
connectId: resolvedConnectId,
|
|
1392
|
+
connectionType
|
|
1393
|
+
};
|
|
1394
|
+
const operation = this._operations.create({
|
|
1395
|
+
searchTargetId,
|
|
1396
|
+
connectId: resolvedConnectId,
|
|
1397
|
+
device,
|
|
1398
|
+
connectionType,
|
|
1399
|
+
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1400
|
+
});
|
|
1401
|
+
return (0, import_hwk_adapter_core3.success)(operation.operationId);
|
|
1402
|
+
}
|
|
1403
|
+
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
967
1404
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
968
1405
|
try {
|
|
969
|
-
|
|
1406
|
+
this._assertConnectorReady("connectDevice");
|
|
1407
|
+
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1408
|
+
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1409
|
+
this._activeConnectionType = discoveredType;
|
|
1410
|
+
}
|
|
1411
|
+
if (this._isBleConnection() && !connectId) {
|
|
970
1412
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
971
1413
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
972
1414
|
});
|
|
973
1415
|
}
|
|
974
|
-
|
|
975
|
-
|
|
1416
|
+
await this._ensureDevicePermission(connectId, void 0, signal);
|
|
1417
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1418
|
+
const isMultiTransport = (this.connector.availableTransports?.length ?? 0) > 1;
|
|
1419
|
+
if (!isMultiTransport && this._isBleConnection()) {
|
|
1420
|
+
const previousSessionId = this._sessions.get(connectId);
|
|
1421
|
+
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1422
|
+
if (previousSessionId) {
|
|
1423
|
+
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1424
|
+
this._sessions.delete(connectId);
|
|
1425
|
+
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1426
|
+
}
|
|
1427
|
+
} else {
|
|
1428
|
+
await this._evictAllSessions(preserveOperationId);
|
|
1429
|
+
}
|
|
1430
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1431
|
+
const stateGeneration = this._stateGeneration;
|
|
1432
|
+
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1433
|
+
let session;
|
|
1434
|
+
this._connectingConnectId = connectId;
|
|
1435
|
+
try {
|
|
1436
|
+
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1437
|
+
transportType: this._activeConnectionType ?? "usb"
|
|
1438
|
+
}) : await this.connector.connect(connectId);
|
|
1439
|
+
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1440
|
+
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1441
|
+
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1442
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
} finally {
|
|
1446
|
+
if (this._connectingConnectId === connectId) this._connectingConnectId = void 0;
|
|
1447
|
+
releaseOperation();
|
|
976
1448
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
this.
|
|
1449
|
+
const resolvedConnectId = session.deviceInfo?.connectId || connectId;
|
|
1450
|
+
this._sessions.set(resolvedConnectId, session.sessionId);
|
|
1451
|
+
this._sessionTransports.set(session.sessionId, this._isBleConnection() ? "ble" : "usb");
|
|
980
1452
|
if (session.deviceInfo) {
|
|
981
|
-
this._discoveredDevices.set(
|
|
1453
|
+
this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
|
|
982
1454
|
}
|
|
983
|
-
const result = (0, import_hwk_adapter_core3.success)(
|
|
1455
|
+
const result = (0, import_hwk_adapter_core3.success)(resolvedConnectId);
|
|
984
1456
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
985
1457
|
return result;
|
|
986
1458
|
} catch (err) {
|
|
@@ -993,30 +1465,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
993
1465
|
return failureResult;
|
|
994
1466
|
}
|
|
995
1467
|
}
|
|
996
|
-
async
|
|
1468
|
+
async releaseOperation(operationId) {
|
|
1469
|
+
const operation = this._operations.find(operationId);
|
|
1470
|
+
if (!operation) {
|
|
1471
|
+
this._operations.resolve(operationId);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1475
|
+
if (!endedOperation) return;
|
|
1476
|
+
const { connectId } = operation;
|
|
997
1477
|
debugLog("[LedgerAdapter][REQ]", {
|
|
998
|
-
method: "
|
|
1478
|
+
method: "releaseOperation",
|
|
999
1479
|
connectId,
|
|
1000
1480
|
params: { connectId }
|
|
1001
1481
|
});
|
|
1002
1482
|
try {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
await this.connector.disconnect(sessionId);
|
|
1006
|
-
this._sessions.delete(connectId);
|
|
1007
|
-
}
|
|
1008
|
-
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1483
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1484
|
+
debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
|
|
1009
1485
|
} catch (err) {
|
|
1010
1486
|
const e = err;
|
|
1011
1487
|
debugLog("[LedgerAdapter][RES]", {
|
|
1012
|
-
method: "
|
|
1488
|
+
method: "releaseOperation",
|
|
1013
1489
|
success: false,
|
|
1014
1490
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1015
1491
|
});
|
|
1016
1492
|
throw err;
|
|
1017
1493
|
}
|
|
1018
1494
|
}
|
|
1019
|
-
async
|
|
1495
|
+
async _releaseOperationConnection(operation) {
|
|
1496
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
1497
|
+
for (const [connectId, sessionId] of this._sessions) {
|
|
1498
|
+
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1499
|
+
this._sessions.delete(connectId);
|
|
1500
|
+
sessionIds.add(sessionId);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
for (const sessionId of sessionIds) {
|
|
1504
|
+
await this._runConnectorTeardown(
|
|
1505
|
+
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
async _releaseLostOperationConnection(operationId) {
|
|
1510
|
+
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1511
|
+
if (!endedOperation) return;
|
|
1512
|
+
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1513
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1514
|
+
}
|
|
1515
|
+
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1516
|
+
let connectId;
|
|
1517
|
+
try {
|
|
1518
|
+
connectId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
return this.errorToFailure(error);
|
|
1521
|
+
}
|
|
1020
1522
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1021
1523
|
method: "getDeviceInfo",
|
|
1022
1524
|
connectId,
|
|
@@ -1051,12 +1553,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1051
1553
|
success: false,
|
|
1052
1554
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1053
1555
|
});
|
|
1054
|
-
|
|
1556
|
+
return this.errorToFailure(err);
|
|
1055
1557
|
}
|
|
1056
1558
|
}
|
|
1057
|
-
getSupportedChains() {
|
|
1058
|
-
return ["evm", "btc", "sol", "tron"];
|
|
1059
|
-
}
|
|
1060
1559
|
// ---------------------------------------------------------------------------
|
|
1061
1560
|
// Chain call helper
|
|
1062
1561
|
// ---------------------------------------------------------------------------
|
|
@@ -1091,13 +1590,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1091
1590
|
if (params && typeof params === "object") {
|
|
1092
1591
|
const {
|
|
1093
1592
|
autoInstallApp,
|
|
1593
|
+
operationId,
|
|
1094
1594
|
passphraseState: _passphraseState,
|
|
1095
1595
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1596
|
+
knownConnections,
|
|
1597
|
+
extra,
|
|
1598
|
+
allowDeviceSelection,
|
|
1599
|
+
supportedTransports,
|
|
1096
1600
|
...rest
|
|
1097
1601
|
} = params;
|
|
1098
1602
|
return {
|
|
1099
1603
|
commonParams: {
|
|
1100
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1604
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
|
|
1605
|
+
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1606
|
+
knownConnections,
|
|
1607
|
+
extra,
|
|
1608
|
+
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0,
|
|
1609
|
+
supportedTransports
|
|
1101
1610
|
},
|
|
1102
1611
|
rest
|
|
1103
1612
|
};
|
|
@@ -1273,9 +1782,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1273
1782
|
);
|
|
1274
1783
|
}
|
|
1275
1784
|
// ---------------------------------------------------------------------------
|
|
1276
|
-
//
|
|
1277
|
-
//
|
|
1278
|
-
|
|
1785
|
+
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1786
|
+
// ---------------------------------------------------------------------------
|
|
1787
|
+
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
1788
|
+
return this.callChainWithMergedParams(
|
|
1789
|
+
connectId,
|
|
1790
|
+
deviceId,
|
|
1791
|
+
"zcash",
|
|
1792
|
+
"zcashGetFullViewingKey",
|
|
1793
|
+
params
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1797
|
+
return this.callChainWithMergedParams(
|
|
1798
|
+
connectId,
|
|
1799
|
+
deviceId,
|
|
1800
|
+
"zcash",
|
|
1801
|
+
"zcashGetShieldedAddress",
|
|
1802
|
+
params
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
// ---------------------------------------------------------------------------
|
|
1806
|
+
// App management: OS-level app install/list, no fingerprint or chain dispatch.
|
|
1279
1807
|
// ---------------------------------------------------------------------------
|
|
1280
1808
|
async installApp(connectId, appName) {
|
|
1281
1809
|
try {
|
|
@@ -1361,7 +1889,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1361
1889
|
);
|
|
1362
1890
|
}
|
|
1363
1891
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1364
|
-
this.
|
|
1892
|
+
await this._resetStateAndDisconnectSessions();
|
|
1365
1893
|
}
|
|
1366
1894
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1367
1895
|
if (!result.isGenuine) {
|
|
@@ -1387,10 +1915,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1387
1915
|
if (relayUrl) {
|
|
1388
1916
|
try {
|
|
1389
1917
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1390
|
-
this.
|
|
1918
|
+
await this._resetStateAndDisconnectSessions();
|
|
1391
1919
|
} catch {
|
|
1392
1920
|
this.connector.reset();
|
|
1393
|
-
this.
|
|
1921
|
+
await this._resetStateAndDisconnectSessions();
|
|
1394
1922
|
}
|
|
1395
1923
|
}
|
|
1396
1924
|
}
|
|
@@ -1402,6 +1930,20 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1402
1930
|
this.emitter.off(event, listener);
|
|
1403
1931
|
}
|
|
1404
1932
|
cancel(connectId) {
|
|
1933
|
+
const namedOperationIsLive = (id) => {
|
|
1934
|
+
try {
|
|
1935
|
+
this._operations.resolve(id);
|
|
1936
|
+
return true;
|
|
1937
|
+
} catch {
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) && !namedOperationIsLive(connectId)) {
|
|
1942
|
+
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1943
|
+
connectId
|
|
1944
|
+
});
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1405
1947
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1406
1948
|
code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
|
|
1407
1949
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1412,14 +1954,48 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1412
1954
|
this._lastCancelReason = void 0;
|
|
1413
1955
|
}
|
|
1414
1956
|
}, 2e3);
|
|
1415
|
-
this.
|
|
1416
|
-
|
|
1417
|
-
|
|
1957
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1958
|
+
let operationId;
|
|
1959
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId)) {
|
|
1960
|
+
operationId = connectId;
|
|
1961
|
+
} else if (!connectId && (0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) {
|
|
1962
|
+
operationId = activeJobId;
|
|
1963
|
+
}
|
|
1964
|
+
let resolvedConnectId = connectId;
|
|
1965
|
+
if (operationId) {
|
|
1966
|
+
try {
|
|
1967
|
+
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1968
|
+
} catch {
|
|
1969
|
+
resolvedConnectId = void 0;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1973
|
+
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1974
|
+
if (!connectId) {
|
|
1975
|
+
this._uiRegistry.cancel();
|
|
1976
|
+
} else if (pendingOperationId) {
|
|
1977
|
+
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1978
|
+
}
|
|
1979
|
+
this._finishBleBinding("cancelled");
|
|
1980
|
+
if (!connectId) this._pendingOperationBindings.clear();
|
|
1981
|
+
else if (pendingOperationId) this._pendingOperationBindings.delete(pendingOperationId);
|
|
1982
|
+
const queueKeys = /* @__PURE__ */ new Set();
|
|
1983
|
+
if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
|
|
1984
|
+
if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
|
|
1985
|
+
if (queueKeys.size) {
|
|
1986
|
+
let cancelledAnyJob = false;
|
|
1987
|
+
for (const key of queueKeys) {
|
|
1988
|
+
cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
|
|
1989
|
+
}
|
|
1990
|
+
debugLog("[LedgerAdapter] cancel routed to queue keys", {
|
|
1991
|
+
queueKeys: [...queueKeys],
|
|
1992
|
+
cancelledAnyJob
|
|
1993
|
+
});
|
|
1418
1994
|
} else {
|
|
1419
1995
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1420
1996
|
}
|
|
1421
|
-
if (
|
|
1422
|
-
const sessionId = this._sessions.get(
|
|
1997
|
+
if (resolvedConnectId) {
|
|
1998
|
+
const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
|
|
1423
1999
|
void this.connector.cancel(sessionId);
|
|
1424
2000
|
} else {
|
|
1425
2001
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1427,48 +2003,89 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1427
2003
|
if (this._connectingPromise) {
|
|
1428
2004
|
this._doConnectAbortController?.abort(userAbortReason);
|
|
1429
2005
|
}
|
|
2006
|
+
const connectingConnectId = this._connectingConnectId;
|
|
2007
|
+
if (connectingConnectId) void this.connector.cancel(connectingConnectId);
|
|
1430
2008
|
}
|
|
1431
2009
|
// ---------------------------------------------------------------------------
|
|
1432
2010
|
// Chain fingerprint
|
|
1433
2011
|
// ---------------------------------------------------------------------------
|
|
2012
|
+
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1434
2013
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1435
2014
|
try {
|
|
1436
2015
|
const fingerprint = await this._computeChainFingerprint(
|
|
1437
2016
|
chain,
|
|
1438
2017
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1439
2018
|
);
|
|
2019
|
+
if (deviceId) {
|
|
2020
|
+
if (fingerprint !== deviceId) {
|
|
2021
|
+
return (0, import_hwk_adapter_core3.failure)(
|
|
2022
|
+
import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
|
|
2023
|
+
formatDeviceMismatchError(deviceId, fingerprint)
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1440
2027
|
return (0, import_hwk_adapter_core3.success)(fingerprint);
|
|
1441
2028
|
} catch (err) {
|
|
1442
2029
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
1443
2030
|
return this.errorToFailure(err);
|
|
1444
2031
|
}
|
|
1445
2032
|
}
|
|
1446
|
-
/**
|
|
1447
|
-
|
|
1448
|
-
* Safe to call inside connectorCall without causing queue deadlock.
|
|
1449
|
-
*/
|
|
1450
|
-
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
1451
|
-
if (!deviceId) return { success: true };
|
|
2033
|
+
/** Saves a picked BLE endpoint; `identity` is set when its wallet was just checked. */
|
|
2034
|
+
async _saveSelectedBleBinding(connectId, selectionRequestId, context, operationId, signal, identity) {
|
|
1452
2035
|
try {
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
|
|
2036
|
+
const outcome = await (0, import_hwk_adapter_core3.requestSaveDeviceBinding)(
|
|
2037
|
+
this.emitter,
|
|
2038
|
+
this._uiRegistry,
|
|
2039
|
+
{
|
|
2040
|
+
selectionRequestId,
|
|
2041
|
+
connection: { transport: "ble", connectId },
|
|
2042
|
+
...identity ? { identity } : {},
|
|
2043
|
+
extra: context?.extra,
|
|
2044
|
+
operationId
|
|
2045
|
+
},
|
|
2046
|
+
signal
|
|
1456
2047
|
);
|
|
1457
|
-
if (
|
|
1458
|
-
|
|
2048
|
+
if (context) context.bindingSaved = outcome.saved;
|
|
2049
|
+
if (!outcome.saved) {
|
|
2050
|
+
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
2051
|
+
connectId,
|
|
2052
|
+
reason: outcome.reason
|
|
2053
|
+
});
|
|
1459
2054
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
2055
|
+
} catch (error) {
|
|
2056
|
+
await this._dropSession(connectId, signal);
|
|
2057
|
+
throw error;
|
|
2058
|
+
} finally {
|
|
2059
|
+
if (this._bindingSelectionRequestId === selectionRequestId) {
|
|
2060
|
+
this._bindingSelectionRequestId = void 0;
|
|
1465
2061
|
}
|
|
1466
|
-
throw err;
|
|
1467
2062
|
}
|
|
1468
2063
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
2064
|
+
async _dropSession(connectId, signal) {
|
|
2065
|
+
const sessionId = this._sessions.get(connectId);
|
|
2066
|
+
this._sessions.delete(connectId);
|
|
2067
|
+
if (!sessionId) return;
|
|
2068
|
+
this._sessionTransports.delete(sessionId);
|
|
2069
|
+
const teardown = this._runConnectorTeardown(() => this.connector.disconnect(sessionId)).catch(
|
|
2070
|
+
() => void 0
|
|
2071
|
+
);
|
|
2072
|
+
if (!signal.aborted) await teardown;
|
|
2073
|
+
}
|
|
2074
|
+
/** Verify on the acquired session without re-entering the job queue. */
|
|
2075
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
2076
|
+
if (!deviceId) return { success: true };
|
|
2077
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
2078
|
+
chain,
|
|
2079
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
2080
|
+
);
|
|
2081
|
+
if (fingerprint === deviceId) {
|
|
2082
|
+
return { success: true };
|
|
2083
|
+
}
|
|
2084
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
2085
|
+
}
|
|
2086
|
+
/**
|
|
2087
|
+
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
2088
|
+
*
|
|
1472
2089
|
* Chains with a native device-side identity primitive (BTC → BIP32 master
|
|
1473
2090
|
* fingerprint) short-circuit at the top and return it verbatim, so the value
|
|
1474
2091
|
* stays reusable for higher-level use (BIP380 descriptors, PSBT signing).
|
|
@@ -1493,11 +2110,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1493
2110
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
1494
2111
|
} else if (chain === "tron") {
|
|
1495
2112
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2113
|
+
} else if (chain === "zcash") {
|
|
2114
|
+
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
1496
2115
|
} else {
|
|
1497
2116
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
1498
2117
|
}
|
|
1499
2118
|
return (0, import_hwk_adapter_core3.deriveDeviceFingerprint)(address);
|
|
1500
2119
|
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Operation owning the running job, so a mid-call UI request can name it.
|
|
2122
|
+
* Undefined at cold start when no operation owns the job.
|
|
2123
|
+
*/
|
|
2124
|
+
_activeOperationId() {
|
|
2125
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
2126
|
+
if (!activeJobId) return void 0;
|
|
2127
|
+
if ((0, import_hwk_adapter_core3.isHardwareOperationId)(activeJobId)) return activeJobId;
|
|
2128
|
+
return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
|
|
2129
|
+
}
|
|
1501
2130
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
1502
2131
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
1503
2132
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
@@ -1507,15 +2136,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1507
2136
|
if (signal?.aborted) {
|
|
1508
2137
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1509
2138
|
}
|
|
2139
|
+
const operationId = this._activeOperationId();
|
|
1510
2140
|
const waitPromise = this._uiRegistry.wait(
|
|
1511
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2141
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2142
|
+
{ operationId }
|
|
1512
2143
|
);
|
|
1513
2144
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
1514
2145
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
1515
2146
|
payload: {
|
|
1516
2147
|
vendor: "ledger",
|
|
1517
2148
|
reason: "device-not-found",
|
|
1518
|
-
message: "Please connect and unlock your Ledger device"
|
|
2149
|
+
message: "Please connect and unlock your Ledger device",
|
|
2150
|
+
operationId
|
|
1519
2151
|
}
|
|
1520
2152
|
});
|
|
1521
2153
|
let payload;
|
|
@@ -1576,15 +2208,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1576
2208
|
return { ...params, showOnDevice: true };
|
|
1577
2209
|
}
|
|
1578
2210
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2211
|
+
const operationId = this._activeOperationId();
|
|
1579
2212
|
const waitPromise = this._uiRegistry.wait(
|
|
1580
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2213
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2214
|
+
{ operationId }
|
|
1581
2215
|
);
|
|
1582
2216
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
1583
2217
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
1584
2218
|
payload: {
|
|
1585
2219
|
vendor: "ledger",
|
|
1586
2220
|
path,
|
|
1587
|
-
accountIndex
|
|
2221
|
+
accountIndex,
|
|
2222
|
+
operationId
|
|
1588
2223
|
}
|
|
1589
2224
|
});
|
|
1590
2225
|
try {
|
|
@@ -1601,12 +2236,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1601
2236
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
1602
2237
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
1603
2238
|
async _waitForInstallAppConfirm(appName) {
|
|
2239
|
+
const operationId = this._activeOperationId();
|
|
1604
2240
|
const waitPromise = this._uiRegistry.wait(
|
|
1605
|
-
import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP
|
|
2241
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2242
|
+
{ operationId }
|
|
1606
2243
|
);
|
|
1607
2244
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
1608
2245
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_INSTALL_APP,
|
|
1609
|
-
payload: { vendor: "ledger", appName }
|
|
2246
|
+
payload: { vendor: "ledger", appName, operationId }
|
|
1610
2247
|
});
|
|
1611
2248
|
try {
|
|
1612
2249
|
const payload = await waitPromise;
|
|
@@ -1622,16 +2259,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1622
2259
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
1623
2260
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
1624
2261
|
// doesn't kill caller B's await.
|
|
1625
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2262
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
1626
2263
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
1627
|
-
|
|
2264
|
+
this._assertConnectorReady("connectDevice");
|
|
2265
|
+
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1628
2266
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1629
2267
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1630
2268
|
});
|
|
1631
2269
|
}
|
|
1632
2270
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
1633
2271
|
if (!connectId && this._sessions.size > 0) {
|
|
1634
|
-
if (!
|
|
2272
|
+
if (!this._isBleConnection() && this._sessions.size > 1) {
|
|
1635
2273
|
throw Object.assign(
|
|
1636
2274
|
new Error(
|
|
1637
2275
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -1641,15 +2279,24 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1641
2279
|
}
|
|
1642
2280
|
return this._sessions.keys().next().value;
|
|
1643
2281
|
}
|
|
1644
|
-
if (!this._connectingPromise) {
|
|
1645
|
-
|
|
1646
|
-
|
|
2282
|
+
if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
|
|
2283
|
+
const controller = new AbortController();
|
|
2284
|
+
this._doConnectAbortController = controller;
|
|
2285
|
+
const innerSignal = controller.signal;
|
|
1647
2286
|
this._connectingPromise = (async () => {
|
|
1648
2287
|
try {
|
|
1649
|
-
return await this._doConnect(
|
|
2288
|
+
return await this._doConnect(
|
|
2289
|
+
innerSignal,
|
|
2290
|
+
connectId,
|
|
2291
|
+
allowUsbEphemeralFallback,
|
|
2292
|
+
preserveOperationId,
|
|
2293
|
+
context
|
|
2294
|
+
);
|
|
1650
2295
|
} finally {
|
|
1651
|
-
this.
|
|
1652
|
-
|
|
2296
|
+
if (this._doConnectAbortController === controller) {
|
|
2297
|
+
this._connectingPromise = null;
|
|
2298
|
+
this._doConnectAbortController = null;
|
|
2299
|
+
}
|
|
1653
2300
|
}
|
|
1654
2301
|
})();
|
|
1655
2302
|
}
|
|
@@ -1658,22 +2305,57 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1658
2305
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1659
2306
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1660
2307
|
// 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
|
-
|
|
2308
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
2309
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2310
|
+
if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
|
|
2311
|
+
this._activeConnectionType = "usb";
|
|
2312
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
|
|
2313
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2314
|
+
if (usbDevices.length > 0) {
|
|
2315
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2316
|
+
(connection) => connection.transport === "usb"
|
|
2317
|
+
);
|
|
2318
|
+
const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
|
|
2319
|
+
return this._connectFirstOrSelect(
|
|
2320
|
+
usbDevices,
|
|
2321
|
+
usbTarget,
|
|
2322
|
+
allowUsbEphemeralFallback,
|
|
2323
|
+
preserveOperationId,
|
|
2324
|
+
context,
|
|
2325
|
+
internalSignal
|
|
2326
|
+
);
|
|
1676
2327
|
}
|
|
2328
|
+
if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
|
|
2329
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2330
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2331
|
+
message: "This Ledger model has no Bluetooth transport; connect it over USB"
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
this._activeConnectionType = "ble";
|
|
2335
|
+
const knownBle = context?.knownConnections?.find(
|
|
2336
|
+
(connection) => connection.transport === "ble"
|
|
2337
|
+
);
|
|
2338
|
+
if (knownBle?.transport === "ble") {
|
|
2339
|
+
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2340
|
+
}
|
|
2341
|
+
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2342
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2343
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2344
|
+
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2348
|
+
return this._connectFirstOrSelect(
|
|
2349
|
+
bleDevices,
|
|
2350
|
+
void 0,
|
|
2351
|
+
allowUsbEphemeralFallback,
|
|
2352
|
+
preserveOperationId,
|
|
2353
|
+
context,
|
|
2354
|
+
internalSignal
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
if (this._isBleConnection() && targetConnectId) {
|
|
2358
|
+
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
1677
2359
|
}
|
|
1678
2360
|
let confirms = 0;
|
|
1679
2361
|
while (!internalSignal.aborted) {
|
|
@@ -1681,22 +2363,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1681
2363
|
type: import_hwk_adapter_core3.EConnectorInteraction.Searching,
|
|
1682
2364
|
payload: { sessionId: "" }
|
|
1683
2365
|
});
|
|
1684
|
-
let devices = await this.
|
|
2366
|
+
let devices = await this._searchDevices(void 0, internalSignal);
|
|
2367
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1685
2368
|
if (devices.length === 0) {
|
|
1686
2369
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1687
2370
|
await new Promise((resolve) => {
|
|
1688
2371
|
setTimeout(resolve, import_hwk_adapter_core3.DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
1689
2372
|
});
|
|
1690
|
-
|
|
2373
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2374
|
+
devices = await this._searchDevices(void 0, internalSignal);
|
|
2375
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1691
2376
|
if (devices.length > 0) break;
|
|
1692
2377
|
}
|
|
1693
2378
|
}
|
|
1694
|
-
if (devices.length > 0) {
|
|
2379
|
+
if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
|
|
1695
2380
|
try {
|
|
1696
2381
|
return await this._connectFirstOrSelect(
|
|
1697
2382
|
devices,
|
|
1698
2383
|
targetConnectId,
|
|
1699
|
-
allowUsbEphemeralFallback
|
|
2384
|
+
allowUsbEphemeralFallback,
|
|
2385
|
+
preserveOperationId,
|
|
2386
|
+
context,
|
|
2387
|
+
internalSignal
|
|
1700
2388
|
);
|
|
1701
2389
|
} catch (err) {
|
|
1702
2390
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -1725,45 +2413,147 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1725
2413
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1726
2414
|
throw new Error("_doConnect aborted");
|
|
1727
2415
|
}
|
|
1728
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2416
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
|
|
2417
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1729
2418
|
if (targetConnectId) {
|
|
1730
2419
|
const target = devices.find(
|
|
1731
2420
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1732
2421
|
);
|
|
1733
2422
|
if (target) {
|
|
1734
|
-
return this._connectDeviceOrThrow(target.connectId);
|
|
2423
|
+
return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
|
|
1735
2424
|
}
|
|
1736
|
-
if (!
|
|
2425
|
+
if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
1737
2426
|
debugLog(
|
|
1738
2427
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
1739
2428
|
);
|
|
1740
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2429
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1741
2430
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2431
|
+
if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
|
|
2432
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
2433
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
2434
|
+
});
|
|
2435
|
+
if (this._isBleConnection()) {
|
|
2436
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2437
|
+
}
|
|
2438
|
+
throw err;
|
|
1747
2439
|
}
|
|
1748
|
-
throw err;
|
|
1749
2440
|
}
|
|
1750
|
-
|
|
2441
|
+
const requiresBleSelection = this._isBleConnection();
|
|
2442
|
+
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1751
2443
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1752
2444
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1753
2445
|
});
|
|
1754
2446
|
}
|
|
1755
|
-
if (
|
|
1756
|
-
|
|
2447
|
+
if (requiresBleSelection && context?.allowDeviceSelection !== false) {
|
|
2448
|
+
const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
|
|
2449
|
+
if (context) context.bindingSessionId = bindingSessionId;
|
|
2450
|
+
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2451
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2452
|
+
(connection) => connection.transport === "usb"
|
|
2453
|
+
);
|
|
2454
|
+
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2455
|
+
const { device, requestId } = await (0, import_hwk_adapter_core3.requestBleDeviceSelection)({
|
|
2456
|
+
emitter: this.emitter,
|
|
2457
|
+
registry: this._uiRegistry,
|
|
2458
|
+
signal,
|
|
2459
|
+
allowUsbFallback,
|
|
2460
|
+
scan: async () => {
|
|
2461
|
+
if (allowUsbFallback) {
|
|
2462
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2463
|
+
const candidate = usbDevices.find(
|
|
2464
|
+
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2465
|
+
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2466
|
+
if (candidate) return [candidate];
|
|
2467
|
+
}
|
|
2468
|
+
return this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal);
|
|
2469
|
+
},
|
|
2470
|
+
request: {
|
|
2471
|
+
devices,
|
|
2472
|
+
bindingSessionId,
|
|
2473
|
+
context: {
|
|
2474
|
+
kind: "bind-connection",
|
|
2475
|
+
transport: "ble",
|
|
2476
|
+
reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
|
|
2477
|
+
},
|
|
2478
|
+
extra: context?.extra,
|
|
2479
|
+
operationId: preserveOperationId
|
|
2480
|
+
}
|
|
2481
|
+
});
|
|
2482
|
+
if (device.connectionType === "usb") {
|
|
2483
|
+
this._activeConnectionType = "usb";
|
|
2484
|
+
if (context) context.selectedConnection = void 0;
|
|
2485
|
+
this._bindingSelectionRequestId = requestId;
|
|
2486
|
+
this._finishBleBinding("cancelled");
|
|
2487
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2488
|
+
}
|
|
2489
|
+
this._bindingSelectionRequestId = requestId;
|
|
2490
|
+
const connected = await this._connectDeviceOrThrow(
|
|
2491
|
+
device.connectId,
|
|
2492
|
+
preserveOperationId,
|
|
2493
|
+
signal
|
|
2494
|
+
);
|
|
2495
|
+
if (context) context.selectedConnection = { connectId: connected, requestId };
|
|
2496
|
+
if (context?.deferBindingSave) {
|
|
2497
|
+
context.pendingBinding = { connectId: connected, requestId };
|
|
2498
|
+
} else {
|
|
2499
|
+
await this._saveSelectedBleBinding(
|
|
2500
|
+
connected,
|
|
2501
|
+
requestId,
|
|
2502
|
+
context,
|
|
2503
|
+
preserveOperationId,
|
|
2504
|
+
signal
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
return connected;
|
|
2508
|
+
}
|
|
2509
|
+
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2510
|
+
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2511
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2512
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
|
|
2513
|
+
message: "Select a Ledger device before continuing"
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
const requestId = this._uiRegistry.createRequestId();
|
|
2517
|
+
const operationId = this._activeOperationId();
|
|
2518
|
+
const waitPromise = this._uiRegistry.wait(
|
|
2519
|
+
import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2520
|
+
{ requestId, operationId }
|
|
2521
|
+
);
|
|
2522
|
+
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2523
|
+
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2524
|
+
payload: {
|
|
2525
|
+
devices,
|
|
2526
|
+
requestId,
|
|
2527
|
+
operationId,
|
|
2528
|
+
context: requiresBleSelection ? {
|
|
2529
|
+
kind: "bind-connection",
|
|
2530
|
+
transport: "ble",
|
|
2531
|
+
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2532
|
+
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2533
|
+
extra: context?.extra
|
|
2534
|
+
}
|
|
2535
|
+
});
|
|
2536
|
+
const { sdkConnectId } = await this._abortable(signal, waitPromise);
|
|
2537
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2538
|
+
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2539
|
+
if (!selected) {
|
|
2540
|
+
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2541
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
if (context && requiresBleSelection)
|
|
2545
|
+
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2546
|
+
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1757
2547
|
}
|
|
1758
2548
|
if (devices.length !== 1) {
|
|
1759
2549
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
1760
2550
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound
|
|
1761
2551
|
});
|
|
1762
2552
|
}
|
|
1763
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2553
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1764
2554
|
}
|
|
1765
|
-
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1766
|
-
const result = await this.
|
|
2555
|
+
async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
|
|
2556
|
+
const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
|
|
1767
2557
|
if (!result.success) {
|
|
1768
2558
|
const payload = result.payload;
|
|
1769
2559
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -1774,7 +2564,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1774
2564
|
}
|
|
1775
2565
|
throw rethrow;
|
|
1776
2566
|
}
|
|
1777
|
-
return
|
|
2567
|
+
return result.payload;
|
|
1778
2568
|
}
|
|
1779
2569
|
/**
|
|
1780
2570
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -1804,33 +2594,139 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1804
2594
|
* lives in one place.
|
|
1805
2595
|
*/
|
|
1806
2596
|
async _callConnector(sessionId, method, params, signal) {
|
|
1807
|
-
|
|
2597
|
+
this._assertConnectorReady(method);
|
|
2598
|
+
if (signal?.aborted) throw this._abortReason(signal);
|
|
2599
|
+
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2600
|
+
let promise;
|
|
2601
|
+
try {
|
|
2602
|
+
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2603
|
+
} catch (error) {
|
|
2604
|
+
releaseOperation();
|
|
2605
|
+
throw error;
|
|
2606
|
+
}
|
|
1808
2607
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
1809
2608
|
return this._unwrapConnectorResult(result);
|
|
1810
2609
|
}
|
|
2610
|
+
_assertConnectorReady(method) {
|
|
2611
|
+
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2612
|
+
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
_runConnectorTeardown(task) {
|
|
2616
|
+
const previous = this._connectorTeardownTail;
|
|
2617
|
+
let releaseTail = () => void 0;
|
|
2618
|
+
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2619
|
+
releaseTail = resolve;
|
|
2620
|
+
});
|
|
2621
|
+
this._pendingConnectorTeardowns += 1;
|
|
2622
|
+
return (async () => {
|
|
2623
|
+
try {
|
|
2624
|
+
await previous;
|
|
2625
|
+
await this._waitForConnectorOperationsToDrain();
|
|
2626
|
+
await task();
|
|
2627
|
+
} finally {
|
|
2628
|
+
this._pendingConnectorTeardowns -= 1;
|
|
2629
|
+
releaseTail();
|
|
2630
|
+
}
|
|
2631
|
+
})();
|
|
2632
|
+
}
|
|
2633
|
+
_waitForConnectorOperationsToDrain() {
|
|
2634
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2635
|
+
return Promise.resolve();
|
|
2636
|
+
}
|
|
2637
|
+
return new Promise((resolve) => {
|
|
2638
|
+
this._connectorIdleWaiters.add(resolve);
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
_retainConnectorOperation(key) {
|
|
2642
|
+
this._unsettledConnectorOperations.set(
|
|
2643
|
+
key,
|
|
2644
|
+
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2645
|
+
);
|
|
2646
|
+
let released = false;
|
|
2647
|
+
return () => {
|
|
2648
|
+
if (released) return;
|
|
2649
|
+
released = true;
|
|
2650
|
+
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2651
|
+
if (remaining > 0) {
|
|
2652
|
+
this._unsettledConnectorOperations.set(key, remaining);
|
|
2653
|
+
} else {
|
|
2654
|
+
this._unsettledConnectorOperations.delete(key);
|
|
2655
|
+
}
|
|
2656
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2657
|
+
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2658
|
+
this._connectorIdleWaiters.clear();
|
|
2659
|
+
}
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
1811
2662
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
1812
|
-
|
|
1813
|
-
|
|
2663
|
+
const positionalOperationId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) ? connectId : void 0;
|
|
2664
|
+
if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
|
|
2665
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2666
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
|
|
2667
|
+
message: "Conflicting Ledger operation ids",
|
|
2668
|
+
params: {
|
|
2669
|
+
positionalOperationId,
|
|
2670
|
+
commonOperationId: commonParams.operationId
|
|
2671
|
+
}
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2675
|
+
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2676
|
+
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2677
|
+
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2678
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
2679
|
+
method,
|
|
2680
|
+
connectId: effectiveConnectId || "(empty)",
|
|
2681
|
+
params
|
|
2682
|
+
});
|
|
2683
|
+
const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
|
|
1814
2684
|
try {
|
|
1815
2685
|
const result = await this._jobQueue.enqueue(
|
|
1816
2686
|
queueKey,
|
|
1817
|
-
async (signal) =>
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2687
|
+
async (signal) => {
|
|
2688
|
+
if (operationId) this._activeOperationJobs.add(operationId);
|
|
2689
|
+
try {
|
|
2690
|
+
return await this._runConnectorCall(
|
|
2691
|
+
effectiveConnectId,
|
|
2692
|
+
method,
|
|
2693
|
+
params,
|
|
2694
|
+
signal,
|
|
2695
|
+
fingerprint,
|
|
2696
|
+
permissionDeviceId,
|
|
2697
|
+
commonParams,
|
|
2698
|
+
installContext ?? {},
|
|
2699
|
+
operationId
|
|
2700
|
+
);
|
|
2701
|
+
} catch (error) {
|
|
2702
|
+
const status = signal.aborted ? "cancelled" : "failed";
|
|
2703
|
+
for (const [pickedConnectId, owner] of this._pendingPickBindings) {
|
|
2704
|
+
this._pendingPickBindings.delete(pickedConnectId);
|
|
2705
|
+
this._discardPendingBinding(owner, status);
|
|
2706
|
+
await this._dropSession(pickedConnectId, signal);
|
|
2707
|
+
}
|
|
2708
|
+
this._finishBleBinding(status);
|
|
2709
|
+
throw error;
|
|
2710
|
+
} finally {
|
|
2711
|
+
if (operationId) {
|
|
2712
|
+
this._activeOperationJobs.delete(operationId);
|
|
2713
|
+
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2714
|
+
await this._releaseLostOperationConnection(operationId);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
},
|
|
1827
2719
|
{
|
|
1828
2720
|
label: method,
|
|
1829
2721
|
rejectIfBusy: true,
|
|
1830
2722
|
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
1831
2723
|
}
|
|
1832
2724
|
);
|
|
1833
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
2725
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
2726
|
+
method,
|
|
2727
|
+
success: true,
|
|
2728
|
+
payload: redactResultForLog(result)
|
|
2729
|
+
});
|
|
1834
2730
|
return result;
|
|
1835
2731
|
} catch (err) {
|
|
1836
2732
|
const e = err;
|
|
@@ -1844,15 +2740,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1844
2740
|
}
|
|
1845
2741
|
});
|
|
1846
2742
|
throw err;
|
|
2743
|
+
} finally {
|
|
2744
|
+
releaseOperationRetention?.();
|
|
1847
2745
|
}
|
|
1848
2746
|
}
|
|
2747
|
+
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2748
|
+
_abortReason(signal) {
|
|
2749
|
+
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2750
|
+
}
|
|
1849
2751
|
/**
|
|
1850
2752
|
* Race a promise against an abort signal. On abort, rejects with
|
|
1851
2753
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
1852
2754
|
*/
|
|
1853
2755
|
_abortable(signal, promise) {
|
|
1854
|
-
const getAbortReason = () =>
|
|
2756
|
+
const getAbortReason = () => this._abortReason(signal);
|
|
1855
2757
|
if (signal.aborted) {
|
|
2758
|
+
void promise.catch(() => void 0);
|
|
1856
2759
|
return Promise.reject(getAbortReason());
|
|
1857
2760
|
}
|
|
1858
2761
|
return new Promise((resolve, reject) => {
|
|
@@ -1879,13 +2782,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1879
2782
|
}
|
|
1880
2783
|
}
|
|
1881
2784
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
1882
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2785
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
|
|
1883
2786
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2787
|
+
if (!operationId) {
|
|
2788
|
+
await this._ensureDevicePermission(
|
|
2789
|
+
connectId,
|
|
2790
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
2791
|
+
signal
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
1889
2794
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1890
2795
|
let effectiveParams = params;
|
|
1891
2796
|
if (method === "btcGetPublicKey") {
|
|
@@ -1899,19 +2804,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1899
2804
|
effectiveParams = gatedParams;
|
|
1900
2805
|
}
|
|
1901
2806
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
1902
|
-
|
|
1903
|
-
|
|
2807
|
+
let businessCallStarted = false;
|
|
2808
|
+
const selectedBleTarget = connectId ? this._selectedBleReconnectTargets.get(connectId) : void 0;
|
|
2809
|
+
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2810
|
+
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2811
|
+
(connection) => connection.transport === knownTransport
|
|
2812
|
+
);
|
|
2813
|
+
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2814
|
+
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && selectedBleTarget?.chain === fingerprint.chain && selectedBleTarget.fingerprint === fingerprint.deviceId ? selectedBleTarget.connectId : inputConnectId;
|
|
2815
|
+
const checksWallet = Boolean(
|
|
2816
|
+
fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId
|
|
2817
|
+
);
|
|
2818
|
+
const connectionAttempt = {
|
|
2819
|
+
...commonParams,
|
|
2820
|
+
deferBindingSave: checksWallet
|
|
2821
|
+
};
|
|
2822
|
+
const bundleConnection = installContext?.connection;
|
|
2823
|
+
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2824
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2825
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2826
|
+
message: "Ledger all-network connection ended"
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
const resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2830
|
+
preferredConnectId,
|
|
1904
2831
|
signal,
|
|
1905
|
-
allowUsbEphemeralFallback
|
|
2832
|
+
allowUsbEphemeralFallback,
|
|
2833
|
+
void 0,
|
|
2834
|
+
connectionAttempt
|
|
1906
2835
|
);
|
|
1907
2836
|
const sessionId = this._sessions.get(resolvedConnectId);
|
|
2837
|
+
if (sessionId && installContext && !installContext.connection) {
|
|
2838
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2839
|
+
}
|
|
1908
2840
|
if (!sessionId) {
|
|
2841
|
+
if (operationId) {
|
|
2842
|
+
this._operations.end(operationId, "disconnect");
|
|
2843
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2844
|
+
code: import_hwk_adapter_core3.HardwareErrorCode.OperationEnded,
|
|
2845
|
+
message: "Ledger operation connection is no longer active",
|
|
2846
|
+
params: { operationId, reason: "disconnect" }
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
1909
2849
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
1910
2850
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
1911
2851
|
});
|
|
1912
2852
|
}
|
|
1913
2853
|
try {
|
|
1914
|
-
|
|
2854
|
+
const isBleSession = this._sessionTransports.get(sessionId) === "ble";
|
|
2855
|
+
if (!operationId && connectionAttempt.pendingBinding) {
|
|
2856
|
+
this._pendingPickBindings.set(resolvedConnectId, connectionAttempt);
|
|
2857
|
+
}
|
|
2858
|
+
const bindingOwner = operationId ? this._pendingOperationBindings.get(operationId) : this._pendingPickBindings.get(resolvedConnectId);
|
|
2859
|
+
const pendingBinding = bindingOwner?.pendingBinding?.connectId === resolvedConnectId ? bindingOwner.pendingBinding : void 0;
|
|
2860
|
+
if (fingerprint && checksWallet) {
|
|
1915
2861
|
const fp = await this._abortable(
|
|
1916
2862
|
signal,
|
|
1917
2863
|
this._verifyDeviceFingerprintWithSession(
|
|
@@ -1921,14 +2867,65 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1921
2867
|
)
|
|
1922
2868
|
);
|
|
1923
2869
|
if (!fp.success) {
|
|
2870
|
+
if (pendingBinding) {
|
|
2871
|
+
this._discardPendingBinding(bindingOwner, "failed");
|
|
2872
|
+
this._pendingPickBindings.delete(resolvedConnectId);
|
|
2873
|
+
if (operationId) {
|
|
2874
|
+
this._pendingOperationBindings.delete(operationId);
|
|
2875
|
+
this._operations.end(operationId, "explicit");
|
|
2876
|
+
await this._dropSession(resolvedConnectId, signal);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
1924
2879
|
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1925
2880
|
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
1926
2881
|
});
|
|
1927
2882
|
}
|
|
1928
2883
|
}
|
|
2884
|
+
if (pendingBinding && bindingOwner) {
|
|
2885
|
+
bindingOwner.pendingBinding = void 0;
|
|
2886
|
+
if (operationId) this._pendingOperationBindings.delete(operationId);
|
|
2887
|
+
else this._pendingPickBindings.delete(resolvedConnectId);
|
|
2888
|
+
await this._saveSelectedBleBinding(
|
|
2889
|
+
pendingBinding.connectId,
|
|
2890
|
+
pendingBinding.requestId,
|
|
2891
|
+
bindingOwner,
|
|
2892
|
+
operationId,
|
|
2893
|
+
signal,
|
|
2894
|
+
fingerprint && checksWallet ? {
|
|
2895
|
+
vendor: "ledger",
|
|
2896
|
+
type: "chainFingerprint",
|
|
2897
|
+
chain: fingerprint.chain,
|
|
2898
|
+
value: fingerprint.deviceId
|
|
2899
|
+
} : void 0
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId && !operationId && isBleSession && connectionAttempt.selectedConnection?.connectId === resolvedConnectId) {
|
|
2903
|
+
this._selectedBleReconnectTargets.set(connectId, {
|
|
2904
|
+
connectId: resolvedConnectId,
|
|
2905
|
+
chain: fingerprint.chain,
|
|
2906
|
+
fingerprint: fingerprint.deviceId
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
businessCallStarted = true;
|
|
1929
2910
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
1930
2911
|
} catch (err) {
|
|
1931
2912
|
if (signal.aborted) throw err;
|
|
2913
|
+
if (isLostConnectionError(err)) {
|
|
2914
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2915
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2916
|
+
else {
|
|
2917
|
+
this._sessions.delete(resolvedConnectId);
|
|
2918
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2919
|
+
}
|
|
2920
|
+
const ambiguous = businessCallStarted && !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method);
|
|
2921
|
+
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2922
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2923
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(err).code,
|
|
2924
|
+
message: "Ledger operation connection was lost; start a new operation",
|
|
2925
|
+
params: ambiguous ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, { operationId }) : interactionParams,
|
|
2926
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
1932
2929
|
const errObj = err;
|
|
1933
2930
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1934
2931
|
message: errObj?.message,
|
|
@@ -1940,6 +2937,61 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1940
2937
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1941
2938
|
isStuckApp: isStuckAppStateError(err)
|
|
1942
2939
|
});
|
|
2940
|
+
const assertSessionCurrent = (message) => {
|
|
2941
|
+
if (this._sessions.get(resolvedConnectId) === sessionId) return;
|
|
2942
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2943
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected,
|
|
2944
|
+
message
|
|
2945
|
+
});
|
|
2946
|
+
};
|
|
2947
|
+
if ((isDeviceLockedError(err) || errObj?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2948
|
+
await this._waitForDeviceConnect(signal);
|
|
2949
|
+
assertSessionCurrent("Ledger connection ended while waiting for unlock");
|
|
2950
|
+
return this._runConnectorCall(
|
|
2951
|
+
resolvedConnectId,
|
|
2952
|
+
method,
|
|
2953
|
+
effectiveParams,
|
|
2954
|
+
signal,
|
|
2955
|
+
fingerprint,
|
|
2956
|
+
permissionDeviceId,
|
|
2957
|
+
commonParams,
|
|
2958
|
+
installContext,
|
|
2959
|
+
operationId,
|
|
2960
|
+
lockedRetryBudget - 1
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2964
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2965
|
+
assertSessionCurrent("Ledger connection ended during the app transition");
|
|
2966
|
+
try {
|
|
2967
|
+
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2968
|
+
} catch (retryErr) {
|
|
2969
|
+
if (isStuckAppStateError(retryErr)) throw err;
|
|
2970
|
+
if (isLostConnectionError(retryErr)) {
|
|
2971
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2972
|
+
else {
|
|
2973
|
+
this._sessions.delete(resolvedConnectId);
|
|
2974
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2975
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2976
|
+
}
|
|
2977
|
+
const ambiguous = !(0, import_hwk_adapter_core3.canReplayHardwareMethodAfterTransportFailure)(method);
|
|
2978
|
+
throw (0, import_hwk_adapter_core3.createHwkError)({
|
|
2979
|
+
code: operationId ? import_hwk_adapter_core3.HardwareErrorCode.OperationEnded : mapLedgerError(retryErr).code,
|
|
2980
|
+
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2981
|
+
params: ambiguous ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(method, { operationId, reason: "disconnect" }) : { operationId, reason: "disconnect" },
|
|
2982
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
throw retryErr;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
if (!operationId && errObj?.code === import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch) {
|
|
2989
|
+
this._sessions.delete(resolvedConnectId);
|
|
2990
|
+
this._sessionTransports.delete(sessionId);
|
|
2991
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2992
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2993
|
+
throw err;
|
|
2994
|
+
}
|
|
1943
2995
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
1944
2996
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === import_hwk_adapter_core3.HardwareErrorCode.AppNotInstalled;
|
|
1945
2997
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -1995,157 +3047,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1995
3047
|
fingerprint,
|
|
1996
3048
|
permissionDeviceId,
|
|
1997
3049
|
commonParams,
|
|
1998
|
-
installContext
|
|
3050
|
+
installContext,
|
|
3051
|
+
operationId
|
|
1999
3052
|
);
|
|
2000
3053
|
}
|
|
2001
3054
|
}
|
|
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
|
|
2039
|
-
);
|
|
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
|
-
}
|
|
2088
|
-
throw lastErr;
|
|
2089
|
-
}
|
|
2090
|
-
if (isTimeoutError(err)) {
|
|
2091
|
-
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
2092
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2093
|
-
return this._retryWithFreshConnection(
|
|
2094
|
-
resolvedConnectId,
|
|
2095
|
-
method,
|
|
2096
|
-
effectiveParams,
|
|
2097
|
-
signal,
|
|
2098
|
-
err,
|
|
2099
|
-
fingerprint
|
|
2100
|
-
);
|
|
2101
|
-
}
|
|
2102
|
-
if (isConnectionLevelError(err)) {
|
|
2103
|
-
debugLog("[LedgerAdapter] connection-level fail-closed reset");
|
|
2104
|
-
this._sessions.delete(resolvedConnectId);
|
|
2105
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2106
|
-
this.connector.reset();
|
|
2107
|
-
const codeNum = err?.code;
|
|
2108
|
-
throw Object.assign(err, {
|
|
2109
|
-
code: codeNum ?? import_hwk_adapter_core3.HardwareErrorCode.DeviceDisconnected
|
|
2110
|
-
});
|
|
2111
|
-
}
|
|
2112
3055
|
throw err;
|
|
2113
3056
|
}
|
|
2114
3057
|
}
|
|
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
3058
|
_sleepAbortable(ms, signal) {
|
|
2150
3059
|
return new Promise((resolve, reject) => {
|
|
2151
3060
|
if (signal.aborted) {
|
|
@@ -2160,115 +3069,26 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2160
3069
|
clearTimeout(timer);
|
|
2161
3070
|
reject(signal.reason ?? new Error("Aborted"));
|
|
2162
3071
|
};
|
|
2163
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
/**
|
|
2167
|
-
* Clear stale session, reconnect, and retry the call.
|
|
2168
|
-
*
|
|
2169
|
-
* Timeout recovery starts with a full connector reset. After an APDU
|
|
2170
|
-
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
2171
|
-
* on the same DMK can poison the next chain switch.
|
|
2172
|
-
*/
|
|
2173
|
-
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2174
|
-
this.connector.reset();
|
|
2175
|
-
this._sessions.clear();
|
|
2176
|
-
this._discoveredDevices.clear();
|
|
2177
|
-
this._connectingPromise = null;
|
|
2178
|
-
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2179
|
-
const retryConnectId = await this.ensureConnected(
|
|
2180
|
-
targetConnectId,
|
|
2181
|
-
signal,
|
|
2182
|
-
allowUsbEphemeralFallback
|
|
2183
|
-
);
|
|
2184
|
-
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2185
|
-
if (!retrySessionId) {
|
|
2186
|
-
throw originalErr;
|
|
2187
|
-
}
|
|
2188
|
-
try {
|
|
2189
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2190
|
-
const fp = await this._abortable(
|
|
2191
|
-
signal,
|
|
2192
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2193
|
-
retrySessionId,
|
|
2194
|
-
fingerprint.deviceId,
|
|
2195
|
-
fingerprint.chain
|
|
2196
|
-
)
|
|
2197
|
-
);
|
|
2198
|
-
if (!fp.success) {
|
|
2199
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2200
|
-
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
2201
|
-
});
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
return await this._callConnector(retrySessionId, method, params, signal);
|
|
2205
|
-
} catch (retryErr) {
|
|
2206
|
-
if (signal.aborted) throw retryErr;
|
|
2207
|
-
this.connector.reset();
|
|
2208
|
-
this._sessions.clear();
|
|
2209
|
-
this._discoveredDevices.clear();
|
|
2210
|
-
this._connectingPromise = null;
|
|
2211
|
-
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
2212
|
-
throw retryErr;
|
|
2213
|
-
}
|
|
2214
|
-
debugLog(
|
|
2215
|
-
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
2216
|
-
);
|
|
2217
|
-
this.connector.reset();
|
|
2218
|
-
this._sessions.clear();
|
|
2219
|
-
this._discoveredDevices.clear();
|
|
2220
|
-
this._connectingPromise = null;
|
|
2221
|
-
const finalConnectId = await this.ensureConnected(
|
|
2222
|
-
targetConnectId,
|
|
2223
|
-
signal,
|
|
2224
|
-
allowUsbEphemeralFallback
|
|
2225
|
-
);
|
|
2226
|
-
const finalSessionId = this._sessions.get(finalConnectId);
|
|
2227
|
-
if (!finalSessionId) {
|
|
2228
|
-
throw originalErr;
|
|
2229
|
-
}
|
|
2230
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2231
|
-
const fp = await this._abortable(
|
|
2232
|
-
signal,
|
|
2233
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2234
|
-
finalSessionId,
|
|
2235
|
-
fingerprint.deviceId,
|
|
2236
|
-
fingerprint.chain
|
|
2237
|
-
)
|
|
2238
|
-
);
|
|
2239
|
-
if (!fp.success) {
|
|
2240
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2241
|
-
code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch
|
|
2242
|
-
});
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
return this._callConnector(finalSessionId, method, params, signal);
|
|
2246
|
-
}
|
|
3072
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3073
|
+
});
|
|
2247
3074
|
}
|
|
2248
3075
|
/**
|
|
2249
|
-
* Ensure OS-level
|
|
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
|
|
3076
|
+
* Ensure OS-level Bluetooth/USB permission; times out after 60s if the host never
|
|
3077
|
+
* replies. Without connectId the request is environment-level, else device-level.
|
|
2259
3078
|
*/
|
|
2260
3079
|
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
2261
3080
|
if (signal?.aborted) {
|
|
2262
3081
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2263
3082
|
}
|
|
2264
3083
|
const transportType = this.activeTransport ?? "hid";
|
|
3084
|
+
const operationId = this._activeOperationId();
|
|
2265
3085
|
const waitPromise = this._uiRegistry.wait(
|
|
2266
3086
|
import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2267
|
-
{ timeoutMs: 6e4 }
|
|
3087
|
+
{ timeoutMs: 6e4, operationId }
|
|
2268
3088
|
);
|
|
2269
3089
|
this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
2270
3090
|
type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2271
|
-
payload: { transportType, connectId, deviceId }
|
|
3091
|
+
payload: { transportType, connectId, deviceId, operationId }
|
|
2272
3092
|
});
|
|
2273
3093
|
let response;
|
|
2274
3094
|
const onAbort = () => {
|
|
@@ -2314,7 +3134,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2314
3134
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
2315
3135
|
const e = err;
|
|
2316
3136
|
const params = e.code === import_hwk_adapter_core3.HardwareErrorCode.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
2317
|
-
return ledgerFailure(
|
|
3137
|
+
return ledgerFailure(
|
|
3138
|
+
e.code,
|
|
3139
|
+
e.message ?? "Unknown error",
|
|
3140
|
+
e.appName,
|
|
3141
|
+
tag,
|
|
3142
|
+
params,
|
|
3143
|
+
void 0,
|
|
3144
|
+
(0, import_hwk_adapter_core3.isHwkRecoveryHint)(e.recovery) ? e.recovery : void 0
|
|
3145
|
+
);
|
|
2318
3146
|
}
|
|
2319
3147
|
const mapped = mapLedgerError(err);
|
|
2320
3148
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -2341,7 +3169,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2341
3169
|
deviceId: device.deviceId,
|
|
2342
3170
|
connectId: device.connectId,
|
|
2343
3171
|
label: device.name,
|
|
2344
|
-
connectionType: this.connector.connectionType,
|
|
3172
|
+
connectionType: device.connectionType ?? this.connector.connectionType,
|
|
2345
3173
|
rssi: device.rssi,
|
|
2346
3174
|
isConnectable: device.isConnectable,
|
|
2347
3175
|
serialNumber: device.serialNumber,
|
|
@@ -2366,7 +3194,7 @@ _LedgerAdapter.APP_INSTALL_PROGRESS_MIN_DELTA = 0.05;
|
|
|
2366
3194
|
var LedgerAdapter = _LedgerAdapter;
|
|
2367
3195
|
|
|
2368
3196
|
// src/connector/LedgerConnectorBase.ts
|
|
2369
|
-
var
|
|
3197
|
+
var import_hwk_adapter_core12 = require("@onekeyfe/hwk-adapter-core");
|
|
2370
3198
|
|
|
2371
3199
|
// src/device/LedgerDeviceManager.ts
|
|
2372
3200
|
var LedgerDeviceManager = class {
|
|
@@ -2902,7 +3730,7 @@ var SignerManager = class _SignerManager {
|
|
|
2902
3730
|
return (args) => new import_device_signer_kit_ethereum.SignerEthBuilder(args);
|
|
2903
3731
|
}
|
|
2904
3732
|
static _createContextModule() {
|
|
2905
|
-
const contextModule = new import_context_module.ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3733
|
+
const contextModule = new import_context_module.ContextModuleBuilder({}).setChain(import_context_module.ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
|
|
2906
3734
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2907
3735
|
}
|
|
2908
3736
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -2940,6 +3768,27 @@ function collapseSignerInteraction(interaction) {
|
|
|
2940
3768
|
return import_hwk_adapter_core5.EConnectorInteraction.ConfirmOnDevice;
|
|
2941
3769
|
}
|
|
2942
3770
|
}
|
|
3771
|
+
function wireSignerToSession(ctx, sessionId, chain, signer) {
|
|
3772
|
+
signer.onInteraction = (interaction) => {
|
|
3773
|
+
debugLog(`[LedgerConnector] ${chain}.onInteraction:`, interaction);
|
|
3774
|
+
ctx.emit("ui-event", {
|
|
3775
|
+
type: collapseSignerInteraction(interaction),
|
|
3776
|
+
payload: { sessionId }
|
|
3777
|
+
});
|
|
3778
|
+
};
|
|
3779
|
+
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
3780
|
+
return signer;
|
|
3781
|
+
}
|
|
3782
|
+
async function runSignerCall(ctx, sessionId, call) {
|
|
3783
|
+
try {
|
|
3784
|
+
return await call();
|
|
3785
|
+
} catch (err) {
|
|
3786
|
+
ctx.invalidateSession(sessionId);
|
|
3787
|
+
throw ctx.wrapError(err);
|
|
3788
|
+
} finally {
|
|
3789
|
+
ctx.clearCanceller(sessionId);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
2943
3792
|
|
|
2944
3793
|
// src/connector/chains/evm.ts
|
|
2945
3794
|
async function evmGetAddress(ctx, sessionId, params) {
|
|
@@ -3373,6 +4222,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
3373
4222
|
|
|
3374
4223
|
// src/connector/chains/sol.ts
|
|
3375
4224
|
var import_hwk_adapter_core9 = require("@onekeyfe/hwk-adapter-core");
|
|
4225
|
+
var import_bs58 = __toESM(require("bs58"));
|
|
3376
4226
|
|
|
3377
4227
|
// src/signer/SignerSol.ts
|
|
3378
4228
|
var SignerSol = class {
|
|
@@ -3456,8 +4306,21 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3456
4306
|
const path = normalizePath(params.path);
|
|
3457
4307
|
const messageBytes = (0, import_hwk_adapter_core9.hexToBytes)(params.message);
|
|
3458
4308
|
try {
|
|
4309
|
+
if (params.messageVersion === 1) {
|
|
4310
|
+
const preparedMessage = (0, import_hwk_adapter_core9.prepareSolanaOffchainMessageV1)({
|
|
4311
|
+
message: messageBytes,
|
|
4312
|
+
requiredSigners: params.requiredSigners
|
|
4313
|
+
});
|
|
4314
|
+
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4315
|
+
"@ledgerhq/device-signer-kit-solana"
|
|
4316
|
+
);
|
|
4317
|
+
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4318
|
+
version: SignMessageVersion.Raw
|
|
4319
|
+
});
|
|
4320
|
+
return { signature: decodeBase58Signature(result2.signature) };
|
|
4321
|
+
}
|
|
3459
4322
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
3460
|
-
return { signature: result.signature };
|
|
4323
|
+
return { signature: decodeBase58EnvelopeSignature(result.signature) };
|
|
3461
4324
|
} catch (err) {
|
|
3462
4325
|
ctx.invalidateSession(sessionId);
|
|
3463
4326
|
throw ctx.wrapError(err);
|
|
@@ -3465,11 +4328,27 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3465
4328
|
ctx.clearCanceller(sessionId);
|
|
3466
4329
|
}
|
|
3467
4330
|
}
|
|
4331
|
+
function decodeBase58Signature(signature) {
|
|
4332
|
+
const bytes = import_bs58.default.decode(signature);
|
|
4333
|
+
if (bytes.length !== 64) {
|
|
4334
|
+
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4335
|
+
}
|
|
4336
|
+
return (0, import_hwk_adapter_core9.bytesToHex)(bytes);
|
|
4337
|
+
}
|
|
4338
|
+
function decodeBase58EnvelopeSignature(envelope) {
|
|
4339
|
+
const bytes = import_bs58.default.decode(envelope);
|
|
4340
|
+
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4341
|
+
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4342
|
+
}
|
|
4343
|
+
return (0, import_hwk_adapter_core9.bytesToHex)(bytes.subarray(1, 65));
|
|
4344
|
+
}
|
|
3468
4345
|
async function _createSolSigner(ctx, sessionId) {
|
|
3469
4346
|
const dmk = await ctx.getOrCreateDmk();
|
|
3470
|
-
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit(
|
|
4347
|
+
const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
|
|
4348
|
+
"@ledgerhq/context-module"
|
|
4349
|
+
);
|
|
3471
4350
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
3472
|
-
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4351
|
+
const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
|
|
3473
4352
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
3474
4353
|
const signer = new SignerSol(sdkSigner);
|
|
3475
4354
|
signer.onInteraction = (interaction) => {
|
|
@@ -3485,360 +4364,151 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
3485
4364
|
return signer;
|
|
3486
4365
|
}
|
|
3487
4366
|
|
|
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");
|
|
4367
|
+
// src/connector/chains/zcash.ts
|
|
3497
4368
|
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);
|
|
4369
|
+
|
|
4370
|
+
// src/signer/SignerZcash.ts
|
|
4371
|
+
var SignerZcash = class {
|
|
4372
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4373
|
+
constructor(_sdk) {
|
|
4374
|
+
this._sdk = _sdk;
|
|
3556
4375
|
}
|
|
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
|
-
}
|
|
4376
|
+
/** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
|
|
4377
|
+
async getFullViewingKey(derivationPath, options) {
|
|
4378
|
+
const action = this._sdk.getFullViewingKey(derivationPath, options);
|
|
4379
|
+
return deviceActionToPromise(
|
|
4380
|
+
action,
|
|
4381
|
+
this.onInteraction,
|
|
4382
|
+
void 0,
|
|
4383
|
+
this.onRegisterCanceller
|
|
3593
4384
|
);
|
|
3594
4385
|
}
|
|
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})`
|
|
4386
|
+
/** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
|
|
4387
|
+
async getShieldedAddress(derivationPath, options) {
|
|
4388
|
+
const action = this._sdk.getShieldedAddress(derivationPath, options);
|
|
4389
|
+
return deviceActionToPromise(
|
|
4390
|
+
action,
|
|
4391
|
+
this.onInteraction,
|
|
4392
|
+
void 0,
|
|
4393
|
+
this.onRegisterCanceller
|
|
3657
4394
|
);
|
|
3658
4395
|
}
|
|
3659
|
-
_isDashboard(appName) {
|
|
3660
|
-
return appName === DASHBOARD_APP_NAME;
|
|
3661
|
-
}
|
|
3662
|
-
_wait() {
|
|
3663
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3664
|
-
}
|
|
3665
4396
|
};
|
|
3666
4397
|
|
|
3667
|
-
// src/connector/chains/
|
|
3668
|
-
function
|
|
3669
|
-
|
|
4398
|
+
// src/connector/chains/zcash.ts
|
|
4399
|
+
async function zcashGetFullViewingKey(ctx, sessionId, params) {
|
|
4400
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4401
|
+
const path = normalizePath(params.path);
|
|
4402
|
+
const mode = params.mode ?? "ufvk";
|
|
4403
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4404
|
+
const result = await signer.getFullViewingKey(path, { mode });
|
|
4405
|
+
if (result.mode === "ufvk") {
|
|
4406
|
+
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
4407
|
+
}
|
|
4408
|
+
return { path: params.path, mode, orchardFvk: (0, import_hwk_adapter_core10.bytesToHex)(result.fullViewingKey) };
|
|
4409
|
+
});
|
|
3670
4410
|
}
|
|
3671
|
-
async function
|
|
3672
|
-
const
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.ConfirmOpenApp,
|
|
3678
|
-
payload: { sessionId }
|
|
4411
|
+
async function zcashGetShieldedAddress(ctx, sessionId, params) {
|
|
4412
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4413
|
+
const path = normalizePath(params.path);
|
|
4414
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4415
|
+
const result = await signer.getShieldedAddress(path, {
|
|
4416
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
3679
4417
|
});
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
if (openAppPromptShown) {
|
|
3683
|
-
ctx.emit("ui-event", {
|
|
3684
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.InteractionComplete,
|
|
3685
|
-
payload: { sessionId }
|
|
3686
|
-
});
|
|
3687
|
-
openAppPromptShown = false;
|
|
3688
|
-
}
|
|
3689
|
-
};
|
|
3690
|
-
try {
|
|
3691
|
-
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3692
|
-
} catch (err) {
|
|
3693
|
-
debugLog(
|
|
3694
|
-
"[LegacyChainCall] pre-flight ensureAppOpen failed:",
|
|
3695
|
-
appName,
|
|
3696
|
-
err?.message
|
|
3697
|
-
);
|
|
3698
|
-
closeOpenAppUiIfShown();
|
|
3699
|
-
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3700
|
-
}
|
|
3701
|
-
const runOnce = async () => {
|
|
3702
|
-
let confirmEmitted = false;
|
|
3703
|
-
if (needsConfirmation) {
|
|
3704
|
-
ctx.emit("ui-event", {
|
|
3705
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.ConfirmOnDevice,
|
|
3706
|
-
payload: { sessionId }
|
|
3707
|
-
});
|
|
3708
|
-
confirmEmitted = true;
|
|
3709
|
-
}
|
|
3710
|
-
try {
|
|
3711
|
-
return await action(sessionId);
|
|
3712
|
-
} finally {
|
|
3713
|
-
if (confirmEmitted || openAppPromptShown) {
|
|
3714
|
-
ctx.emit("ui-event", {
|
|
3715
|
-
type: import_hwk_adapter_core11.EConnectorInteraction.InteractionComplete,
|
|
3716
|
-
payload: { sessionId }
|
|
3717
|
-
});
|
|
3718
|
-
openAppPromptShown = false;
|
|
3719
|
-
}
|
|
3720
|
-
}
|
|
3721
|
-
};
|
|
3722
|
-
try {
|
|
3723
|
-
return await runOnce();
|
|
3724
|
-
} catch (err) {
|
|
3725
|
-
if (!isLegacyWrongAppError(err, appName)) {
|
|
3726
|
-
debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
|
|
3727
|
-
ctx.invalidateSession(sessionId);
|
|
3728
|
-
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3729
|
-
}
|
|
3730
|
-
debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
|
|
3731
|
-
try {
|
|
3732
|
-
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3733
|
-
} catch (switchErr) {
|
|
3734
|
-
debugLog(
|
|
3735
|
-
"[LegacyChainCall] retry ensureAppOpen failed:",
|
|
3736
|
-
appName,
|
|
3737
|
-
switchErr?.message
|
|
3738
|
-
);
|
|
3739
|
-
closeOpenAppUiIfShown();
|
|
3740
|
-
throw ctx.wrapError(switchErr, { defaultAppName: appName });
|
|
3741
|
-
}
|
|
3742
|
-
ctx.clearAllSigners();
|
|
3743
|
-
const result = await runOnce();
|
|
3744
|
-
debugLog("[LegacyChainCall] retry succeeded:", appName);
|
|
3745
|
-
return result;
|
|
3746
|
-
}
|
|
4418
|
+
return { address: result.address, path: params.path };
|
|
4419
|
+
});
|
|
3747
4420
|
}
|
|
3748
|
-
async function
|
|
4421
|
+
async function _createZcashSigner(ctx, sessionId) {
|
|
3749
4422
|
const dmk = await ctx.getOrCreateDmk();
|
|
3750
|
-
const
|
|
3751
|
-
|
|
4423
|
+
const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
|
|
4424
|
+
const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
|
|
4425
|
+
return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
|
|
3752
4426
|
}
|
|
3753
4427
|
|
|
3754
|
-
// src/
|
|
3755
|
-
var
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
4428
|
+
// src/connector/chains/tron.ts
|
|
4429
|
+
var import_hwk_adapter_core11 = require("@onekeyfe/hwk-adapter-core");
|
|
4430
|
+
|
|
4431
|
+
// src/signer/SignerTron.ts
|
|
4432
|
+
var SignerTron = class {
|
|
4433
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4434
|
+
constructor(_sdk) {
|
|
4435
|
+
this._sdk = _sdk;
|
|
3761
4436
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
}
|
|
3772
|
-
result.set(statusCode, data.length);
|
|
3773
|
-
return result;
|
|
4437
|
+
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4438
|
+
async getAddress(derivationPath, options) {
|
|
4439
|
+
const action = this._sdk.getAddress(derivationPath, options);
|
|
4440
|
+
const result = await deviceActionToPromise(
|
|
4441
|
+
action,
|
|
4442
|
+
this.onInteraction,
|
|
4443
|
+
void 0,
|
|
4444
|
+
this.onRegisterCanceller
|
|
4445
|
+
);
|
|
4446
|
+
return { address: result.address, publicKey: result.publicKey };
|
|
3774
4447
|
}
|
|
3775
|
-
|
|
4448
|
+
/** Sign a protobuf-encoded raw transaction. */
|
|
4449
|
+
async signTransaction(derivationPath, transaction, options) {
|
|
4450
|
+
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
4451
|
+
return deviceActionToPromise(
|
|
4452
|
+
action,
|
|
4453
|
+
this.onInteraction,
|
|
4454
|
+
void 0,
|
|
4455
|
+
this.onRegisterCanceller
|
|
4456
|
+
);
|
|
4457
|
+
}
|
|
4458
|
+
/** Sign a personal message (TIP-191). */
|
|
4459
|
+
async signPersonalMessage(derivationPath, message, options) {
|
|
4460
|
+
const action = this._sdk.signPersonalMessage(derivationPath, message, options);
|
|
4461
|
+
return deviceActionToPromise(
|
|
4462
|
+
action,
|
|
4463
|
+
this.onInteraction,
|
|
4464
|
+
void 0,
|
|
4465
|
+
this.onRegisterCanceller
|
|
4466
|
+
);
|
|
3776
4467
|
}
|
|
3777
4468
|
};
|
|
3778
4469
|
|
|
3779
4470
|
// src/connector/chains/tron.ts
|
|
3780
4471
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4472
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3781
4473
|
const path = normalizePath(params.path);
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
{
|
|
3787
|
-
|
|
3788
|
-
// Only show "confirm on device" UI when the device is actually going
|
|
3789
|
-
// to display the address for the user to verify.
|
|
3790
|
-
needsConfirmation: showOnDevice
|
|
3791
|
-
},
|
|
3792
|
-
async (sid) => {
|
|
3793
|
-
const trx = await _createTrx(ctx, sid);
|
|
3794
|
-
const result = await trx.getAddress(path, showOnDevice);
|
|
3795
|
-
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
3796
|
-
}
|
|
3797
|
-
);
|
|
4474
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4475
|
+
const result = await tronSigner.getAddress(path, {
|
|
4476
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4477
|
+
});
|
|
4478
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
4479
|
+
});
|
|
3798
4480
|
}
|
|
3799
4481
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
3800
4482
|
if (!params.rawTxHex) {
|
|
3801
4483
|
throw Object.assign(
|
|
3802
4484
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
3803
|
-
{ code:
|
|
4485
|
+
{ code: import_hwk_adapter_core11.HardwareErrorCode.InvalidParams }
|
|
3804
4486
|
);
|
|
3805
4487
|
}
|
|
4488
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3806
4489
|
const path = normalizePath(params.path);
|
|
3807
|
-
return
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
async (sid) => {
|
|
3812
|
-
const trx = await _createTrx(ctx, sid);
|
|
3813
|
-
const signature = await trx.signTransaction(
|
|
3814
|
-
path,
|
|
3815
|
-
params.rawTxHex,
|
|
3816
|
-
params.tokenSignatures ?? []
|
|
3817
|
-
);
|
|
3818
|
-
return { signature };
|
|
3819
|
-
}
|
|
3820
|
-
);
|
|
4490
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4491
|
+
const signature = await tronSigner.signTransaction(path, (0, import_hwk_adapter_core11.hexToBytes)(params.rawTxHex));
|
|
4492
|
+
return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
|
|
4493
|
+
});
|
|
3821
4494
|
}
|
|
3822
4495
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4496
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3823
4497
|
const path = normalizePath(params.path);
|
|
3824
|
-
return
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
async (sid) => {
|
|
3829
|
-
const trx = await _createTrx(ctx, sid);
|
|
3830
|
-
const signature = await trx.signPersonalMessage(path, params.messageHex);
|
|
3831
|
-
return { signature };
|
|
3832
|
-
}
|
|
3833
|
-
);
|
|
4498
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4499
|
+
const signature = await tronSigner.signPersonalMessage(path, (0, import_hwk_adapter_core11.hexToBytes)(params.messageHex));
|
|
4500
|
+
return { signature: (0, import_hwk_adapter_core11.bytesToHex)(signature) };
|
|
4501
|
+
});
|
|
3834
4502
|
}
|
|
3835
|
-
async function
|
|
4503
|
+
async function _createTronSigner(ctx, sessionId) {
|
|
3836
4504
|
const dmk = await ctx.getOrCreateDmk();
|
|
3837
|
-
|
|
4505
|
+
const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
|
|
4506
|
+
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4507
|
+
return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
|
|
3838
4508
|
}
|
|
3839
4509
|
|
|
3840
4510
|
// src/device-apps/customActions.ts
|
|
3841
|
-
var
|
|
4511
|
+
var import_device_management_kit2 = require("@ledgerhq/device-management-kit");
|
|
3842
4512
|
var import_rxjs = require("rxjs");
|
|
3843
4513
|
var GetOsVersionDeviceAction = class {
|
|
3844
4514
|
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
@@ -3852,7 +4522,7 @@ var GetOsVersionDeviceAction = class {
|
|
|
3852
4522
|
(async () => {
|
|
3853
4523
|
try {
|
|
3854
4524
|
subject.next({
|
|
3855
|
-
status:
|
|
4525
|
+
status: import_device_management_kit2.DeviceActionStatus.Pending,
|
|
3856
4526
|
intermediateValue: { requiredUserInteraction: "none" }
|
|
3857
4527
|
});
|
|
3858
4528
|
const result = await internalApi.sendCommand(new this._deps.GetOsVersionCommand());
|
|
@@ -3861,11 +4531,11 @@ var GetOsVersionDeviceAction = class {
|
|
|
3861
4531
|
const errObj = result?.error;
|
|
3862
4532
|
throw new Error(errObj?.message ?? "GetOsVersionCommand failed");
|
|
3863
4533
|
}
|
|
3864
|
-
subject.next({ status:
|
|
4534
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: result.data });
|
|
3865
4535
|
subject.complete();
|
|
3866
4536
|
} catch (err) {
|
|
3867
4537
|
if (cancelled) return;
|
|
3868
|
-
subject.next({ status:
|
|
4538
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
|
|
3869
4539
|
subject.complete();
|
|
3870
4540
|
}
|
|
3871
4541
|
})();
|
|
@@ -3873,7 +4543,7 @@ var GetOsVersionDeviceAction = class {
|
|
|
3873
4543
|
observable: subject.asObservable(),
|
|
3874
4544
|
cancel: () => {
|
|
3875
4545
|
cancelled = true;
|
|
3876
|
-
subject.next({ status:
|
|
4546
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
|
|
3877
4547
|
subject.complete();
|
|
3878
4548
|
}
|
|
3879
4549
|
};
|
|
@@ -3891,7 +4561,7 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3891
4561
|
(async () => {
|
|
3892
4562
|
try {
|
|
3893
4563
|
subject.next({
|
|
3894
|
-
status:
|
|
4564
|
+
status: import_device_management_kit2.DeviceActionStatus.Pending,
|
|
3895
4565
|
intermediateValue: { requiredUserInteraction: "none" }
|
|
3896
4566
|
});
|
|
3897
4567
|
const osVersionResult = await internalApi.sendCommand(new this._GetOsVersionCommand());
|
|
@@ -3908,11 +4578,11 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3908
4578
|
throw new Error(httpErr?.message ?? "Manager API getAppList failed");
|
|
3909
4579
|
}
|
|
3910
4580
|
const apps = either.extract();
|
|
3911
|
-
subject.next({ status:
|
|
4581
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Completed, output: apps });
|
|
3912
4582
|
subject.complete();
|
|
3913
4583
|
} catch (err) {
|
|
3914
4584
|
if (cancelled) return;
|
|
3915
|
-
subject.next({ status:
|
|
4585
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Error, error: err });
|
|
3916
4586
|
subject.complete();
|
|
3917
4587
|
}
|
|
3918
4588
|
})();
|
|
@@ -3920,7 +4590,7 @@ var ListAvailableAppsDeviceAction = class {
|
|
|
3920
4590
|
observable: subject.asObservable(),
|
|
3921
4591
|
cancel: () => {
|
|
3922
4592
|
cancelled = true;
|
|
3923
|
-
subject.next({ status:
|
|
4593
|
+
subject.next({ status: import_device_management_kit2.DeviceActionStatus.Stopped });
|
|
3924
4594
|
subject.complete();
|
|
3925
4595
|
}
|
|
3926
4596
|
};
|
|
@@ -4013,7 +4683,7 @@ var DeviceApps = class {
|
|
|
4013
4683
|
seTargetId: v.seTargetId,
|
|
4014
4684
|
mcuTargetId: v.mcuTargetId,
|
|
4015
4685
|
seVersion: v.seVersion,
|
|
4016
|
-
seFlagsHex:
|
|
4686
|
+
seFlagsHex: bytesToHex4(v.seFlags),
|
|
4017
4687
|
mcuSephVersion: v.mcuSephVersion,
|
|
4018
4688
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
4019
4689
|
hwVersion: v.hwVersion
|
|
@@ -4087,7 +4757,7 @@ var DeviceApps = class {
|
|
|
4087
4757
|
}
|
|
4088
4758
|
}
|
|
4089
4759
|
};
|
|
4090
|
-
function
|
|
4760
|
+
function bytesToHex4(bytes) {
|
|
4091
4761
|
if (!bytes) return "";
|
|
4092
4762
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4093
4763
|
}
|
|
@@ -4130,10 +4800,11 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4130
4800
|
evm: "Ethereum",
|
|
4131
4801
|
btc: "Bitcoin",
|
|
4132
4802
|
sol: "Solana",
|
|
4133
|
-
tron: "Tron"
|
|
4803
|
+
tron: "Tron",
|
|
4804
|
+
zcash: "Zcash"
|
|
4134
4805
|
};
|
|
4135
4806
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4136
|
-
Object.values(
|
|
4807
|
+
Object.values(import_hwk_adapter_core12.HardwareErrorCode).filter((value) => typeof value === "number")
|
|
4137
4808
|
);
|
|
4138
4809
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4139
4810
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4170,6 +4841,10 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4170
4841
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4171
4842
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4172
4843
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4844
|
+
case "@ledgerhq/device-signer-kit-tron":
|
|
4845
|
+
return import("@ledgerhq/device-signer-kit-tron");
|
|
4846
|
+
case "@ledgerhq/device-signer-kit-zcash":
|
|
4847
|
+
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4173
4848
|
case "@ledgerhq/context-module":
|
|
4174
4849
|
return import("@ledgerhq/context-module");
|
|
4175
4850
|
default:
|
|
@@ -4229,6 +4904,7 @@ var LedgerConnectorBase = class {
|
|
|
4229
4904
|
this._ctx = {
|
|
4230
4905
|
emit: (event, data) => this._emit(event, data),
|
|
4231
4906
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4907
|
+
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4232
4908
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4233
4909
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4234
4910
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -4280,7 +4956,7 @@ var LedgerConnectorBase = class {
|
|
|
4280
4956
|
async searchDevices() {
|
|
4281
4957
|
const dm = await this._getDeviceManager();
|
|
4282
4958
|
const descriptors = await this._discoverDescriptors(dm);
|
|
4283
|
-
const resolvedDescriptors = descriptors.filter((d) => !(0,
|
|
4959
|
+
const resolvedDescriptors = descriptors.filter((d) => !(0, import_hwk_adapter_core12.isKnownNonTargetHardwareVendor)(d, "ledger")).map((d) => ({
|
|
4284
4960
|
descriptor: d,
|
|
4285
4961
|
connectId: this._resolveConnectId(d)
|
|
4286
4962
|
}));
|
|
@@ -4328,7 +5004,7 @@ var LedgerConnectorBase = class {
|
|
|
4328
5004
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
4329
5005
|
);
|
|
4330
5006
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
4331
|
-
err.code =
|
|
5007
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
4332
5008
|
reject(err);
|
|
4333
5009
|
}, HANG_CEILING_MS);
|
|
4334
5010
|
});
|
|
@@ -4361,7 +5037,7 @@ var LedgerConnectorBase = class {
|
|
|
4361
5037
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
4362
5038
|
);
|
|
4363
5039
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
4364
|
-
err.code =
|
|
5040
|
+
err.code = import_hwk_adapter_core12.HardwareErrorCode.DeviceNotFound;
|
|
4365
5041
|
throw err;
|
|
4366
5042
|
};
|
|
4367
5043
|
const doConnect = async (path) => {
|
|
@@ -4418,14 +5094,14 @@ var LedgerConnectorBase = class {
|
|
|
4418
5094
|
this._resetSignersAndSessions();
|
|
4419
5095
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
4420
5096
|
const tag = err?._tag;
|
|
4421
|
-
if (isKnownConnectionTag(tag)) {
|
|
5097
|
+
if (isKnownConnectionTag(tag) && !isConnectionOpeningTag(tag)) {
|
|
4422
5098
|
throw err;
|
|
4423
5099
|
}
|
|
4424
5100
|
const wrapped = new Error(
|
|
4425
5101
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
4426
5102
|
);
|
|
4427
5103
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
4428
|
-
wrapped.code =
|
|
5104
|
+
wrapped.code = import_hwk_adapter_core12.HardwareErrorCode.BlePairingTimeout;
|
|
4429
5105
|
wrapped.originalError = err;
|
|
4430
5106
|
throw wrapped;
|
|
4431
5107
|
}
|
|
@@ -4503,7 +5179,7 @@ var LedgerConnectorBase = class {
|
|
|
4503
5179
|
this._unwatchSessionState(sessionId);
|
|
4504
5180
|
this._signerManager?.invalidate(sessionId);
|
|
4505
5181
|
this._cancellers.get(sessionId)?.({
|
|
4506
|
-
code:
|
|
5182
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceDisconnected,
|
|
4507
5183
|
tag: "DeviceDisconnected",
|
|
4508
5184
|
message: "Device disconnected"
|
|
4509
5185
|
});
|
|
@@ -4527,9 +5203,9 @@ var LedgerConnectorBase = class {
|
|
|
4527
5203
|
if (isAppStuckByApdu(err)) {
|
|
4528
5204
|
return {
|
|
4529
5205
|
success: false,
|
|
4530
|
-
error: (0,
|
|
5206
|
+
error: (0, import_hwk_adapter_core12.serializeConnectorError)(
|
|
4531
5207
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
4532
|
-
code:
|
|
5208
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.DeviceAppStuck,
|
|
4533
5209
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
4534
5210
|
originalError: err
|
|
4535
5211
|
})
|
|
@@ -4539,16 +5215,16 @@ var LedgerConnectorBase = class {
|
|
|
4539
5215
|
if (isTransportStuck(err)) {
|
|
4540
5216
|
return {
|
|
4541
5217
|
success: false,
|
|
4542
|
-
error: (0,
|
|
5218
|
+
error: (0, import_hwk_adapter_core12.serializeConnectorError)(
|
|
4543
5219
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
4544
|
-
code:
|
|
5220
|
+
code: import_hwk_adapter_core12.HardwareErrorCode.TransportError,
|
|
4545
5221
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
4546
5222
|
originalError: err
|
|
4547
5223
|
})
|
|
4548
5224
|
)
|
|
4549
5225
|
};
|
|
4550
5226
|
}
|
|
4551
|
-
return { success: false, error: (0,
|
|
5227
|
+
return { success: false, error: (0, import_hwk_adapter_core12.serializeConnectorError)(err) };
|
|
4552
5228
|
}
|
|
4553
5229
|
}
|
|
4554
5230
|
async _dispatch(sessionId, method, params) {
|
|
@@ -4587,6 +5263,11 @@ var LedgerConnectorBase = class {
|
|
|
4587
5263
|
return solSignTransaction(ctx, sessionId, params);
|
|
4588
5264
|
case "solSignMessage":
|
|
4589
5265
|
return solSignMessage(ctx, sessionId, params);
|
|
5266
|
+
// ZCASH
|
|
5267
|
+
case "zcashGetFullViewingKey":
|
|
5268
|
+
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5269
|
+
case "zcashGetShieldedAddress":
|
|
5270
|
+
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
4590
5271
|
// TRON
|
|
4591
5272
|
case "tronGetAddress":
|
|
4592
5273
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -4611,7 +5292,7 @@ var LedgerConnectorBase = class {
|
|
|
4611
5292
|
try {
|
|
4612
5293
|
return await apps.install(p.appName, ({ progress }) => {
|
|
4613
5294
|
ctx.emit("ui-event", {
|
|
4614
|
-
type:
|
|
5295
|
+
type: import_hwk_adapter_core12.EConnectorInteraction.AppInstallProgress,
|
|
4615
5296
|
payload: {
|
|
4616
5297
|
sessionId,
|
|
4617
5298
|
appName: p.appName,
|
|
@@ -4620,7 +5301,7 @@ var LedgerConnectorBase = class {
|
|
|
4620
5301
|
});
|
|
4621
5302
|
});
|
|
4622
5303
|
} catch (err) {
|
|
4623
|
-
ctx.
|
|
5304
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4624
5305
|
throw ctx.wrapError(err);
|
|
4625
5306
|
} finally {
|
|
4626
5307
|
ctx.clearCanceller(sessionId);
|
|
@@ -4710,7 +5391,7 @@ var LedgerConnectorBase = class {
|
|
|
4710
5391
|
);
|
|
4711
5392
|
return { isGenuine: output.isGenuine, deviceId };
|
|
4712
5393
|
} catch (err) {
|
|
4713
|
-
ctx.
|
|
5394
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4714
5395
|
throw ctx.wrapError(err);
|
|
4715
5396
|
} finally {
|
|
4716
5397
|
ctx.clearCanceller(sessionId);
|
|
@@ -4829,9 +5510,26 @@ var LedgerConnectorBase = class {
|
|
|
4829
5510
|
}
|
|
4830
5511
|
return this._deviceAppsManager;
|
|
4831
5512
|
}
|
|
5513
|
+
// DeviceAppsManager is a per-call factory with no cached session state, so
|
|
5514
|
+
// there is nothing to invalidate for it here.
|
|
4832
5515
|
_invalidateSession(sessionId) {
|
|
4833
5516
|
this._signerManager?.invalidate(sessionId);
|
|
4834
5517
|
}
|
|
5518
|
+
/**
|
|
5519
|
+
* Fire the canceller so DMK closes the secure-channel WebSocket. The DMK device
|
|
5520
|
+
* session is kept: unlock/retry recovery reuses it.
|
|
5521
|
+
*/
|
|
5522
|
+
_teardownSecureChannelSession(sessionId) {
|
|
5523
|
+
const cancel = this._cancellers.get(sessionId);
|
|
5524
|
+
this._cancellers.delete(sessionId);
|
|
5525
|
+
if (cancel) {
|
|
5526
|
+
try {
|
|
5527
|
+
cancel();
|
|
5528
|
+
} catch {
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
this._invalidateSession(sessionId);
|
|
5532
|
+
}
|
|
4835
5533
|
/**
|
|
4836
5534
|
* Replace an old session with a new one after app switch.
|
|
4837
5535
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -4916,7 +5614,7 @@ var LedgerConnectorBase = class {
|
|
|
4916
5614
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
4917
5615
|
*/
|
|
4918
5616
|
_ctxForMethod(method) {
|
|
4919
|
-
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5617
|
+
const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
|
|
4920
5618
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
4921
5619
|
if (!defaultAppName) return this._ctx;
|
|
4922
5620
|
return {
|
|
@@ -4950,6 +5648,204 @@ var LedgerConnectorBase = class {
|
|
|
4950
5648
|
return error;
|
|
4951
5649
|
}
|
|
4952
5650
|
};
|
|
5651
|
+
|
|
5652
|
+
// src/transport/DmkTransport.ts
|
|
5653
|
+
var import_hw_transport = __toESM(require("@ledgerhq/hw-transport"));
|
|
5654
|
+
var DmkTransport = class extends import_hw_transport.default {
|
|
5655
|
+
constructor(dmk, sessionId) {
|
|
5656
|
+
super();
|
|
5657
|
+
this._dmk = dmk;
|
|
5658
|
+
this._sessionId = sessionId;
|
|
5659
|
+
}
|
|
5660
|
+
async exchange(apdu) {
|
|
5661
|
+
const response = await this._dmk.sendApdu({
|
|
5662
|
+
sessionId: this._sessionId,
|
|
5663
|
+
apdu: new Uint8Array(apdu)
|
|
5664
|
+
});
|
|
5665
|
+
const { data, statusCode } = response;
|
|
5666
|
+
const result = Buffer.alloc(data.length + 2);
|
|
5667
|
+
if (data.length > 0) {
|
|
5668
|
+
result.set(data, 0);
|
|
5669
|
+
}
|
|
5670
|
+
result.set(statusCode, data.length);
|
|
5671
|
+
return result;
|
|
5672
|
+
}
|
|
5673
|
+
async close() {
|
|
5674
|
+
}
|
|
5675
|
+
};
|
|
5676
|
+
|
|
5677
|
+
// src/app/AppManager.ts
|
|
5678
|
+
var import_device_management_kit3 = require("@ledgerhq/device-management-kit");
|
|
5679
|
+
var import_hwk_adapter_core13 = require("@onekeyfe/hwk-adapter-core");
|
|
5680
|
+
var APP_NAME_MAP = {
|
|
5681
|
+
ETH: "Ethereum",
|
|
5682
|
+
BTC: "Bitcoin",
|
|
5683
|
+
SOL: "Solana",
|
|
5684
|
+
TRX: "Tron",
|
|
5685
|
+
ZEC: "Zcash",
|
|
5686
|
+
XRP: "XRP",
|
|
5687
|
+
ADA: "Cardano",
|
|
5688
|
+
DOT: "Polkadot",
|
|
5689
|
+
ATOM: "Cosmos"
|
|
5690
|
+
};
|
|
5691
|
+
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5692
|
+
var AppManager = class {
|
|
5693
|
+
constructor(dmk, options) {
|
|
5694
|
+
this._dmk = dmk;
|
|
5695
|
+
this._waitMs = options?.waitMs ?? 1e3;
|
|
5696
|
+
this._maxRetries = options?.maxRetries ?? 10;
|
|
5697
|
+
}
|
|
5698
|
+
/**
|
|
5699
|
+
* Return the Ledger app name for a given chain ticker,
|
|
5700
|
+
* or undefined if the chain is not supported.
|
|
5701
|
+
*/
|
|
5702
|
+
static getAppName(chain) {
|
|
5703
|
+
return APP_NAME_MAP[chain];
|
|
5704
|
+
}
|
|
5705
|
+
/**
|
|
5706
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5707
|
+
*
|
|
5708
|
+
* Flow:
|
|
5709
|
+
* 1. Check the currently running app.
|
|
5710
|
+
* 2. If it is already the target, return immediately.
|
|
5711
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
5712
|
+
* 4. Open the target app.
|
|
5713
|
+
* 5. Poll until the device confirms the target app is running.
|
|
5714
|
+
*/
|
|
5715
|
+
/**
|
|
5716
|
+
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5717
|
+
* device is about to display "Open <app>" on screen and wait for the
|
|
5718
|
+
* user's button press. UI consumers should show their "open app" prompt
|
|
5719
|
+
* in response. NOT called when the target app is already open (no user
|
|
5720
|
+
* interaction needed in that case).
|
|
5721
|
+
*
|
|
5722
|
+
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5723
|
+
* has physically confirmed on the device, so anything that runs AFTER
|
|
5724
|
+
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5725
|
+
* Hence the callback must fire BEFORE that await.
|
|
5726
|
+
*/
|
|
5727
|
+
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5728
|
+
const currentApp = await this._getCurrentApp(sessionId);
|
|
5729
|
+
if (currentApp === targetAppName) {
|
|
5730
|
+
return;
|
|
5731
|
+
}
|
|
5732
|
+
if (!this._isDashboard(currentApp)) {
|
|
5733
|
+
await this._closeCurrentApp(sessionId);
|
|
5734
|
+
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5735
|
+
}
|
|
5736
|
+
onConfirmOnDevice?.();
|
|
5737
|
+
await this._openApp(sessionId, targetAppName);
|
|
5738
|
+
await this._waitForApp(sessionId, targetAppName);
|
|
5739
|
+
}
|
|
5740
|
+
// ---------------------------------------------------------------------------
|
|
5741
|
+
// Private helpers
|
|
5742
|
+
// ---------------------------------------------------------------------------
|
|
5743
|
+
async _getCurrentApp(sessionId) {
|
|
5744
|
+
const result = await this._dmk.sendCommand({
|
|
5745
|
+
sessionId,
|
|
5746
|
+
command: new import_device_management_kit3.GetAppAndVersionCommand()
|
|
5747
|
+
});
|
|
5748
|
+
if ((0, import_device_management_kit3.isSuccessCommandResult)(result)) {
|
|
5749
|
+
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5750
|
+
return result.data.name;
|
|
5751
|
+
}
|
|
5752
|
+
const errResult = result;
|
|
5753
|
+
const dmkErr = errResult.error ?? {};
|
|
5754
|
+
const original = dmkErr.originalError;
|
|
5755
|
+
debugLog(
|
|
5756
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5757
|
+
sessionId,
|
|
5758
|
+
"tag=",
|
|
5759
|
+
dmkErr._tag,
|
|
5760
|
+
"errorCode=",
|
|
5761
|
+
dmkErr.errorCode,
|
|
5762
|
+
"message=",
|
|
5763
|
+
dmkErr.message,
|
|
5764
|
+
"originalErrorMessage=",
|
|
5765
|
+
original?.message ?? String(original ?? "")
|
|
5766
|
+
);
|
|
5767
|
+
throw Object.assign(
|
|
5768
|
+
new Error(
|
|
5769
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
5770
|
+
),
|
|
5771
|
+
{
|
|
5772
|
+
_tag: dmkErr._tag,
|
|
5773
|
+
errorCode: dmkErr.errorCode,
|
|
5774
|
+
originalError: original
|
|
5775
|
+
}
|
|
5776
|
+
);
|
|
5777
|
+
}
|
|
5778
|
+
async _openApp(sessionId, appName) {
|
|
5779
|
+
const result = await this._dmk.sendCommand({
|
|
5780
|
+
sessionId,
|
|
5781
|
+
command: new import_device_management_kit3.OpenAppCommand({ appName })
|
|
5782
|
+
});
|
|
5783
|
+
if (!(0, import_device_management_kit3.isSuccessCommandResult)(result)) {
|
|
5784
|
+
const dmkErr = result.error;
|
|
5785
|
+
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5786
|
+
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5787
|
+
debugLog(
|
|
5788
|
+
"[AppManager] openApp failed:",
|
|
5789
|
+
appName,
|
|
5790
|
+
"errorCode:",
|
|
5791
|
+
errorCode,
|
|
5792
|
+
"tag:",
|
|
5793
|
+
dmkErr._tag
|
|
5794
|
+
);
|
|
5795
|
+
let code;
|
|
5796
|
+
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5797
|
+
code = import_hwk_adapter_core13.HardwareErrorCode.AppNotInstalled;
|
|
5798
|
+
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5799
|
+
code = import_hwk_adapter_core13.HardwareErrorCode.UserRejected;
|
|
5800
|
+
}
|
|
5801
|
+
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5802
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
5803
|
+
code,
|
|
5804
|
+
errorCode,
|
|
5805
|
+
statusCode: errorCode,
|
|
5806
|
+
appName,
|
|
5807
|
+
originalError: dmkErr
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
async _closeCurrentApp(sessionId) {
|
|
5812
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
5813
|
+
await this._dmk.sendCommand({
|
|
5814
|
+
sessionId,
|
|
5815
|
+
command: new import_device_management_kit3.CloseAppCommand()
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
/**
|
|
5819
|
+
* Poll the device until the expected app is reported as running,
|
|
5820
|
+
* or throw after `_maxRetries` attempts.
|
|
5821
|
+
*/
|
|
5822
|
+
async _waitForApp(sessionId, expectedAppName) {
|
|
5823
|
+
let lastSeen = "";
|
|
5824
|
+
for (let i = 0; i < this._maxRetries; i++) {
|
|
5825
|
+
await this._wait();
|
|
5826
|
+
const current = await this._getCurrentApp(sessionId);
|
|
5827
|
+
lastSeen = current;
|
|
5828
|
+
if (current === expectedAppName) {
|
|
5829
|
+
return;
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
debugLog(
|
|
5833
|
+
"[AppManager] waitForApp exhausted: expected=",
|
|
5834
|
+
expectedAppName,
|
|
5835
|
+
"lastSeen=",
|
|
5836
|
+
lastSeen
|
|
5837
|
+
);
|
|
5838
|
+
throw new Error(
|
|
5839
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5840
|
+
);
|
|
5841
|
+
}
|
|
5842
|
+
_isDashboard(appName) {
|
|
5843
|
+
return appName === DASHBOARD_APP_NAME;
|
|
5844
|
+
}
|
|
5845
|
+
_wait() {
|
|
5846
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5847
|
+
}
|
|
5848
|
+
};
|
|
4953
5849
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4954
5850
|
0 && (module.exports = {
|
|
4955
5851
|
AppManager,
|
|
@@ -4961,6 +5857,7 @@ var LedgerConnectorBase = class {
|
|
|
4961
5857
|
SignerEth,
|
|
4962
5858
|
SignerManager,
|
|
4963
5859
|
SignerSol,
|
|
5860
|
+
SignerZcash,
|
|
4964
5861
|
debugLog,
|
|
4965
5862
|
deviceActionToPromise,
|
|
4966
5863
|
isDeviceLockedError,
|