@onekeyfe/hwk-ledger-adapter 1.2.8-alpha.1 → 1.2.8-alpha.3
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 +39 -86
- package/dist/index.d.ts +39 -86
- package/dist/index.js +847 -1794
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +835 -1803
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -14
package/dist/index.mjs
CHANGED
|
@@ -6,41 +6,30 @@ import {
|
|
|
6
6
|
DeviceJobQueue,
|
|
7
7
|
EConnectorInteraction,
|
|
8
8
|
HardwareErrorCode as HardwareErrorCode3,
|
|
9
|
-
OperationRegistry,
|
|
10
|
-
SDK,
|
|
11
9
|
TypedEventEmitter,
|
|
12
10
|
UI_REQUEST,
|
|
13
11
|
UI_REQUEST_PREEMPTED_TAG,
|
|
14
12
|
UiRequestRegistry,
|
|
15
|
-
canReplayHardwareMethodAfterTransportFailure,
|
|
16
13
|
createHwkError,
|
|
17
14
|
deriveDeviceFingerprint,
|
|
18
15
|
failure as failure2,
|
|
19
|
-
isHardwareOperationId,
|
|
20
|
-
isHwkRecoveryHint,
|
|
21
|
-
operationMayHaveCompletedParams,
|
|
22
16
|
rehydrateConnectorError,
|
|
23
|
-
requestBleDeviceSelection,
|
|
24
|
-
requestSaveDeviceBinding,
|
|
25
|
-
resolveSearchTargetReusePolicy,
|
|
26
17
|
success
|
|
27
18
|
} from "@onekeyfe/hwk-adapter-core";
|
|
28
19
|
|
|
29
20
|
// src/errors.ts
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
21
|
+
import { HardwareErrorCode, enrichErrorMessage } from "@onekeyfe/hwk-adapter-core";
|
|
22
|
+
var MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = "Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.";
|
|
23
|
+
function createMultipleUsbLedgerDevicesError() {
|
|
24
|
+
return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), {
|
|
25
|
+
code: HardwareErrorCode.DeviceOneDeviceOnly
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function ledgerFailure(code, error, appName, tag, params) {
|
|
37
29
|
const payload = { error, code };
|
|
38
30
|
if (appName !== void 0) payload.appName = appName;
|
|
39
31
|
if (tag !== void 0) payload._tag = tag;
|
|
40
32
|
if (params !== void 0) payload.params = params;
|
|
41
|
-
const resolvedOrigin = origin ?? defaultOriginForCode(code);
|
|
42
|
-
if (resolvedOrigin !== void 0) payload.origin = resolvedOrigin;
|
|
43
|
-
payload.recovery = recovery ?? defaultRecoveryForCode(code);
|
|
44
33
|
return { success: false, payload };
|
|
45
34
|
}
|
|
46
35
|
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
@@ -181,10 +170,7 @@ var ERROR_TAG = {
|
|
|
181
170
|
UnknownDevice: "UnknownDeviceError",
|
|
182
171
|
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
183
172
|
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
184
|
-
|
|
185
|
-
// spellings are kept so a DMK release that aligns them still classifies.
|
|
186
|
-
OpeningConnection: "ConnectionOpeningError",
|
|
187
|
-
OpeningConnectionLegacy: "OpeningConnectionError",
|
|
173
|
+
OpeningConnection: "OpeningConnectionError",
|
|
188
174
|
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
189
175
|
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
190
176
|
Disconnect: "DisconnectError",
|
|
@@ -197,16 +183,7 @@ var ERROR_TAG = {
|
|
|
197
183
|
// DMK remote-network failures (manager-api HTTP / secure-channel WS).
|
|
198
184
|
WebSocketConnection: "WebSocketConnectionError",
|
|
199
185
|
HttpFetch: "FetchError",
|
|
200
|
-
|
|
201
|
-
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError",
|
|
202
|
-
ApplicationsMetadataTask: "GetApplicationsMetadataTaskError",
|
|
203
|
-
// DMK OS device actions. SecureChannelError is what mapInstallDAErrors() leaves
|
|
204
|
-
// after splitting out device answers, i.e. the relay itself broke.
|
|
205
|
-
SecureChannel: "SecureChannelError",
|
|
206
|
-
RefusedByUserDA: "RefusedByUserDAError",
|
|
207
|
-
AppAlreadyInstalledDA: "AppAlreadyInstalledDAError",
|
|
208
|
-
OutOfMemoryDA: "OutOfMemoryDAError",
|
|
209
|
-
DeviceNotOnboarded: "DeviceNotOnboardedError"
|
|
186
|
+
InvalidFirmwareMetadataResponse: "InvalidGetFirmwareMetadataResponseError"
|
|
210
187
|
};
|
|
211
188
|
function isDeviceLockedError(err) {
|
|
212
189
|
if (!err || typeof err !== "object") return false;
|
|
@@ -236,6 +213,7 @@ function isBlePairingFailureError(err) {
|
|
|
236
213
|
return false;
|
|
237
214
|
}
|
|
238
215
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
216
|
+
ERROR_TAG.DeviceLocked,
|
|
239
217
|
ERROR_TAG.DeviceNotAdvertising,
|
|
240
218
|
ERROR_TAG.BlePairingTimeout,
|
|
241
219
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -247,7 +225,6 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
|
247
225
|
ERROR_TAG.DeviceSessionRefresher,
|
|
248
226
|
ERROR_TAG.DeviceNotInitialized,
|
|
249
227
|
ERROR_TAG.OpeningConnection,
|
|
250
|
-
ERROR_TAG.OpeningConnectionLegacy,
|
|
251
228
|
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
252
229
|
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
253
230
|
ERROR_TAG.Disconnect,
|
|
@@ -262,10 +239,7 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
|
262
239
|
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
263
240
|
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
264
241
|
]);
|
|
265
|
-
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
266
|
-
ERROR_TAG.OpeningConnection,
|
|
267
|
-
ERROR_TAG.OpeningConnectionLegacy
|
|
268
|
-
]);
|
|
242
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OpeningConnection]);
|
|
269
243
|
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
270
244
|
ERROR_TAG.DeviceNotRecognized,
|
|
271
245
|
ERROR_TAG.DeviceSessionNotFound,
|
|
@@ -277,18 +251,17 @@ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
|
277
251
|
ERROR_TAG.WebHIDDisconnect
|
|
278
252
|
]);
|
|
279
253
|
function isConnectionLevelError(err) {
|
|
280
|
-
|
|
254
|
+
if (!err || typeof err !== "object") return false;
|
|
255
|
+
const tag = err._tag;
|
|
256
|
+
if (tag && CONNECTION_LEVEL_TAGS.has(tag)) return true;
|
|
257
|
+
const e = err;
|
|
258
|
+
if (e.originalError != null && isConnectionLevelError(e.originalError)) return true;
|
|
259
|
+
if (e.error != null && e._tag && isConnectionLevelError(e.error)) return true;
|
|
260
|
+
return false;
|
|
281
261
|
}
|
|
282
262
|
function isKnownConnectionTag(tag) {
|
|
283
263
|
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
284
264
|
}
|
|
285
|
-
var CONNECTION_OPENING_TAGS = /* @__PURE__ */ new Set([
|
|
286
|
-
ERROR_TAG.OpeningConnection,
|
|
287
|
-
ERROR_TAG.OpeningConnectionLegacy
|
|
288
|
-
]);
|
|
289
|
-
function isConnectionOpeningTag(tag) {
|
|
290
|
-
return typeof tag === "string" && CONNECTION_OPENING_TAGS.has(tag);
|
|
291
|
-
}
|
|
292
265
|
function hasStatusCode(err, codeSet) {
|
|
293
266
|
if (!err || typeof err !== "object") return false;
|
|
294
267
|
const e = err;
|
|
@@ -324,28 +297,28 @@ function hasInvalidArgumentCode(err) {
|
|
|
324
297
|
if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
|
|
325
298
|
return false;
|
|
326
299
|
}
|
|
327
|
-
function
|
|
300
|
+
function isDeviceNotFoundError(err) {
|
|
328
301
|
if (!err || typeof err !== "object") return false;
|
|
302
|
+
const tag = err._tag;
|
|
303
|
+
if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
|
|
329
304
|
const e = err;
|
|
330
|
-
if (
|
|
331
|
-
if (e.
|
|
332
|
-
if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
|
|
305
|
+
if (e.originalError != null && isDeviceNotFoundError(e.originalError)) return true;
|
|
306
|
+
if (e.error != null && e._tag && isDeviceNotFoundError(e.error)) return true;
|
|
333
307
|
return false;
|
|
334
308
|
}
|
|
335
|
-
function isDeviceNotFoundError(err) {
|
|
336
|
-
return hasErrorTag(err, DEVICE_NOT_FOUND_TAGS);
|
|
337
|
-
}
|
|
338
309
|
function isDeviceBusyError(err) {
|
|
339
|
-
|
|
310
|
+
if (!err || typeof err !== "object") return false;
|
|
311
|
+
const tag = err._tag;
|
|
312
|
+
if (tag && DEVICE_BUSY_TAGS.has(tag)) return true;
|
|
313
|
+
const e = err;
|
|
314
|
+
if (e.originalError != null && isDeviceBusyError(e.originalError)) return true;
|
|
315
|
+
if (e.error != null && e._tag && isDeviceBusyError(e.error)) return true;
|
|
316
|
+
return false;
|
|
340
317
|
}
|
|
341
|
-
var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
|
|
342
|
-
ERROR_TAG.UserRefusedOnDevice,
|
|
343
|
-
ERROR_TAG.RefusedByUserDA
|
|
344
|
-
]);
|
|
345
318
|
function isUserRejectedError(err) {
|
|
346
319
|
if (!err || typeof err !== "object") return false;
|
|
347
320
|
const e = err;
|
|
348
|
-
if (
|
|
321
|
+
if (e._tag === ERROR_TAG.UserRefusedOnDevice) return true;
|
|
349
322
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
350
323
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
351
324
|
return false;
|
|
@@ -376,33 +349,15 @@ function isAppNotInstalledError(err) {
|
|
|
376
349
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
377
350
|
return false;
|
|
378
351
|
}
|
|
379
|
-
var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
|
|
380
352
|
function isOutOfMemoryError(err) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
function isAppAlreadyInstalledError(err) {
|
|
385
|
-
return hasErrorTag(err, APP_ALREADY_INSTALLED_TAGS);
|
|
386
|
-
}
|
|
387
|
-
var DEVICE_NOT_ONBOARDED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.DeviceNotOnboarded]);
|
|
388
|
-
function isDeviceNotOnboardedError(err) {
|
|
389
|
-
return hasErrorTag(err, DEVICE_NOT_ONBOARDED_TAGS);
|
|
390
|
-
}
|
|
391
|
-
var FIRMWARE_METADATA_TAGS = /* @__PURE__ */ new Set([
|
|
392
|
-
ERROR_TAG.InvalidFirmwareMetadataResponse,
|
|
393
|
-
ERROR_TAG.ApplicationsMetadataTask
|
|
394
|
-
]);
|
|
395
|
-
function isFirmwareMetadataError(err) {
|
|
396
|
-
return hasErrorTag(err, FIRMWARE_METADATA_TAGS);
|
|
397
|
-
}
|
|
398
|
-
var SECURE_CHANNEL_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.SecureChannel]);
|
|
399
|
-
function isSecureChannelError(err) {
|
|
400
|
-
return hasErrorTag(err, SECURE_CHANNEL_TAGS);
|
|
353
|
+
if (!err || typeof err !== "object") return false;
|
|
354
|
+
const e = err;
|
|
355
|
+
return e._tag === "OutOfMemoryDAError";
|
|
401
356
|
}
|
|
402
357
|
var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
|
|
403
358
|
ERROR_TAG.WebSocketConnection,
|
|
404
359
|
ERROR_TAG.HttpFetch,
|
|
405
|
-
ERROR_TAG.
|
|
360
|
+
ERROR_TAG.InvalidFirmwareMetadataResponse
|
|
406
361
|
]);
|
|
407
362
|
function isNetworkError(err) {
|
|
408
363
|
if (!err || typeof err !== "object") return false;
|
|
@@ -452,34 +407,23 @@ function isTransportStuck(err) {
|
|
|
452
407
|
function isStuckAppStateError(err) {
|
|
453
408
|
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
454
409
|
}
|
|
455
|
-
var DMK_PLACEHOLDER_MESSAGE = "Unknown error.";
|
|
456
|
-
function nestedErrorMessage(nested) {
|
|
457
|
-
if (!nested || typeof nested !== "object") return void 0;
|
|
458
|
-
const { message } = nested;
|
|
459
|
-
if (typeof message !== "string") return void 0;
|
|
460
|
-
const trimmed = message.trim();
|
|
461
|
-
if (!trimmed || trimmed === DMK_PLACEHOLDER_MESSAGE) return void 0;
|
|
462
|
-
return message;
|
|
463
|
-
}
|
|
464
410
|
function mapLedgerError(err, opts) {
|
|
465
411
|
let originalMessage = "Unknown Ledger error";
|
|
466
412
|
if (err instanceof Error) {
|
|
467
413
|
originalMessage = err.message;
|
|
468
414
|
} else if (err && typeof err === "object") {
|
|
469
415
|
const e = err;
|
|
470
|
-
originalMessage = String(
|
|
471
|
-
e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
|
|
472
|
-
);
|
|
416
|
+
originalMessage = String(e.message ?? e._tag ?? e.type ?? JSON.stringify(err));
|
|
473
417
|
}
|
|
474
418
|
let code;
|
|
475
419
|
if (isDeviceLockedError(err)) {
|
|
476
420
|
code = HardwareErrorCode.DeviceLocked;
|
|
477
421
|
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
478
422
|
code = HardwareErrorCode.DeviceNotFound;
|
|
479
|
-
} else if (isBlePairingFailureError(err)) {
|
|
480
|
-
code = HardwareErrorCode.BlePairingTimeout;
|
|
481
423
|
} else if (isDeviceBusyError(err)) {
|
|
482
424
|
code = HardwareErrorCode.DeviceBusy;
|
|
425
|
+
} else if (isBlePairingFailureError(err)) {
|
|
426
|
+
code = HardwareErrorCode.BlePairingTimeout;
|
|
483
427
|
} else if (isUserAbortedError(err)) {
|
|
484
428
|
code = HardwareErrorCode.UserAborted;
|
|
485
429
|
} else if (isUserRejectedError(err)) {
|
|
@@ -488,18 +432,10 @@ function mapLedgerError(err, opts) {
|
|
|
488
432
|
code = HardwareErrorCode.WrongApp;
|
|
489
433
|
} else if (isAppNotInstalledError(err)) {
|
|
490
434
|
code = HardwareErrorCode.AppNotInstalled;
|
|
491
|
-
} else if (isAppAlreadyInstalledError(err)) {
|
|
492
|
-
code = HardwareErrorCode.AppAlreadyInstalled;
|
|
493
435
|
} else if (isOutOfMemoryError(err)) {
|
|
494
436
|
code = HardwareErrorCode.DeviceOutOfMemory;
|
|
495
|
-
} else if (isDeviceNotOnboardedError(err)) {
|
|
496
|
-
code = HardwareErrorCode.DeviceNotInitialized;
|
|
497
|
-
} else if (isFirmwareMetadataError(err)) {
|
|
498
|
-
code = HardwareErrorCode.LedgerFirmwareMetadataError;
|
|
499
437
|
} else if (isNetworkError(err)) {
|
|
500
438
|
code = HardwareErrorCode.NetworkError;
|
|
501
|
-
} else if (isSecureChannelError(err)) {
|
|
502
|
-
code = HardwareErrorCode.LedgerSecureChannelError;
|
|
503
439
|
} else if (isDeviceDisconnectedError(err)) {
|
|
504
440
|
code = HardwareErrorCode.DeviceDisconnected;
|
|
505
441
|
} else if (isTimeoutError(err)) {
|
|
@@ -512,33 +448,11 @@ function mapLedgerError(err, opts) {
|
|
|
512
448
|
}
|
|
513
449
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
514
450
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
515
|
-
return {
|
|
516
|
-
code,
|
|
517
|
-
message: enrichErrorMessage(code, originalMessage),
|
|
518
|
-
origin: defaultOriginForCode(code),
|
|
519
|
-
appName
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// src/utils/queueKey.ts
|
|
524
|
-
var LEDGER_DEFAULT_QUEUE_KEY = "__ledger_default__";
|
|
525
|
-
function ledgerQueueKey({
|
|
526
|
-
operationId,
|
|
527
|
-
connectId
|
|
528
|
-
}) {
|
|
529
|
-
return (operationId ?? connectId) || LEDGER_DEFAULT_QUEUE_KEY;
|
|
451
|
+
return { code, message: enrichErrorMessage(code, originalMessage), appName };
|
|
530
452
|
}
|
|
531
453
|
|
|
532
454
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
533
|
-
import {
|
|
534
|
-
HardwareErrorCode as HardwareErrorCode2,
|
|
535
|
-
failure,
|
|
536
|
-
isConnectionLost,
|
|
537
|
-
isUserRefusal,
|
|
538
|
-
isWalletSafetyFailure,
|
|
539
|
-
resolveHardwareOperationTarget,
|
|
540
|
-
runAllNetworkGetAddress
|
|
541
|
-
} from "@onekeyfe/hwk-adapter-core";
|
|
455
|
+
import { HardwareErrorCode as HardwareErrorCode2, failure, runAllNetworkGetAddress } from "@onekeyfe/hwk-adapter-core";
|
|
542
456
|
|
|
543
457
|
// src/utils/sdkEventBus.ts
|
|
544
458
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -595,107 +509,65 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
595
509
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
596
510
|
function createAllNetworkGetAddress({
|
|
597
511
|
callChain,
|
|
598
|
-
getChainFingerprint
|
|
599
|
-
retainOperation,
|
|
600
|
-
errorToFailure,
|
|
601
|
-
createCancelScope,
|
|
602
|
-
releaseBundle
|
|
512
|
+
getChainFingerprint
|
|
603
513
|
}) {
|
|
604
514
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
605
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
606
|
-
|
|
607
|
-
connectId,
|
|
608
|
-
itemCount: params.bundle.length
|
|
609
|
-
});
|
|
610
|
-
const target = resolveHardwareOperationTarget(connectId, params.operationId, "ledger");
|
|
611
|
-
if (!target.success) return target;
|
|
612
|
-
const effectiveTargetId = target.payload.targetId ?? "";
|
|
613
|
-
let releaseOperationRetention;
|
|
614
|
-
try {
|
|
615
|
-
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
616
|
-
} catch (error) {
|
|
617
|
-
return errorToFailure(error);
|
|
618
|
-
}
|
|
619
|
-
const cancelScope = createCancelScope(
|
|
620
|
-
ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
621
|
-
);
|
|
622
|
-
const installContext = {
|
|
623
|
-
walletCheck: { expected: params.bundle.some((item) => Boolean(getItemDeviceId(item))) }
|
|
624
|
-
};
|
|
515
|
+
debugLog("[LedgerAdapter][REQ]", { method: "allNetworkGetAddress", connectId, params });
|
|
516
|
+
const installContext = {};
|
|
625
517
|
const commonParams = {
|
|
626
|
-
autoInstallApp: params.autoInstallApp
|
|
627
|
-
operationId: target.payload.operationId,
|
|
628
|
-
knownConnections: params.knownConnections,
|
|
629
|
-
extra: params.extra,
|
|
630
|
-
allowDeviceSelection: params.allowDeviceSelection,
|
|
631
|
-
supportedTransports: params.supportedTransports
|
|
518
|
+
autoInstallApp: params.autoInstallApp
|
|
632
519
|
};
|
|
633
520
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
itemDeviceId,
|
|
648
|
-
method,
|
|
649
|
-
item,
|
|
650
|
-
commonParams,
|
|
651
|
-
installContext
|
|
652
|
-
);
|
|
653
|
-
},
|
|
654
|
-
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
655
|
-
getChainFingerprint,
|
|
656
|
-
effectiveTargetId,
|
|
521
|
+
const result = await runAllNetworkGetAddress({
|
|
522
|
+
connectId,
|
|
523
|
+
deviceId: _deviceId,
|
|
524
|
+
params,
|
|
525
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
526
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
527
|
+
callItem: async ({ method, chain, item }) => {
|
|
528
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
529
|
+
return callAllNetworkMethod(
|
|
530
|
+
callChain,
|
|
531
|
+
connectId,
|
|
532
|
+
itemDeviceId,
|
|
533
|
+
method,
|
|
657
534
|
item,
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
535
|
+
commonParams,
|
|
536
|
+
installContext
|
|
537
|
+
);
|
|
538
|
+
},
|
|
539
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
540
|
+
getChainFingerprint,
|
|
541
|
+
connectId,
|
|
542
|
+
item,
|
|
543
|
+
chain,
|
|
544
|
+
payload,
|
|
545
|
+
chainFingerprints
|
|
546
|
+
),
|
|
547
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
548
|
+
buildTopLevelFailure: (response) => {
|
|
549
|
+
const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
|
|
550
|
+
return failure(
|
|
551
|
+
code,
|
|
552
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
553
|
+
response.payload?.params
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
558
|
+
method: "allNetworkGetAddress",
|
|
559
|
+
success: result.success,
|
|
560
|
+
payload: result
|
|
561
|
+
});
|
|
562
|
+
return result;
|
|
685
563
|
};
|
|
686
564
|
}
|
|
687
|
-
function buildCancelledFailure(signal) {
|
|
688
|
-
const reason = signal.reason;
|
|
689
|
-
const code = typeof reason?.code === "number" ? reason.code : HardwareErrorCode2.UserAborted;
|
|
690
|
-
const message = typeof reason?.message === "string" ? reason.message : "";
|
|
691
|
-
return failure(code, message || "All-network get-address cancelled");
|
|
692
|
-
}
|
|
693
565
|
function isTopLevelAllNetworkFailure(response) {
|
|
694
566
|
if (response.success) {
|
|
695
567
|
return false;
|
|
696
568
|
}
|
|
697
569
|
const code = response.payload?.code;
|
|
698
|
-
return
|
|
570
|
+
return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
|
|
699
571
|
}
|
|
700
572
|
function getItemDeviceId(item) {
|
|
701
573
|
const { deviceId } = item;
|
|
@@ -725,18 +597,8 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
725
597
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
726
598
|
return coin ? { ...item, coin } : item;
|
|
727
599
|
}
|
|
728
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints
|
|
729
|
-
const
|
|
730
|
-
if (!knownFingerprint && cancelSignal.aborted) {
|
|
731
|
-
const cancelled = buildCancelledFailure(cancelSignal);
|
|
732
|
-
return { ...item, success: false, payload: cancelled.payload };
|
|
733
|
-
}
|
|
734
|
-
const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
|
|
735
|
-
getChainFingerprint,
|
|
736
|
-
context.connection?.connectId ?? connectId,
|
|
737
|
-
chain,
|
|
738
|
-
context
|
|
739
|
-
);
|
|
600
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
601
|
+
const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(getChainFingerprint, connectId, chain);
|
|
740
602
|
if (!fingerprint) {
|
|
741
603
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
742
604
|
}
|
|
@@ -757,8 +619,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
757
619
|
}
|
|
758
620
|
};
|
|
759
621
|
}
|
|
760
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain
|
|
761
|
-
const response = await getChainFingerprint(connectId, chain
|
|
622
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
623
|
+
const response = await getChainFingerprint(connectId, chain);
|
|
762
624
|
return response.success ? response.payload : "";
|
|
763
625
|
}
|
|
764
626
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -851,16 +713,6 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
|
|
|
851
713
|
function formatDeviceMismatchError(expected, actual) {
|
|
852
714
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
853
715
|
}
|
|
854
|
-
var LOG_REDACTED_RESULT_KEYS = ["ufvk", "orchardFvk"];
|
|
855
|
-
function redactResultForLog(result) {
|
|
856
|
-
if (!result || typeof result !== "object") return result;
|
|
857
|
-
const keys = LOG_REDACTED_RESULT_KEYS.filter((key) => key in result);
|
|
858
|
-
if (!keys.length) return result;
|
|
859
|
-
return { ...result, ...Object.fromEntries(keys.map((key) => [key, "[redacted]"])) };
|
|
860
|
-
}
|
|
861
|
-
function isLostConnectionError(err) {
|
|
862
|
-
return isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err);
|
|
863
|
-
}
|
|
864
716
|
var BTC_HIGH_INDEX_THRESHOLD = 100;
|
|
865
717
|
function btcAccountIndexFromPath(path) {
|
|
866
718
|
const segments = path.replace(/^m\//, "").split("/");
|
|
@@ -872,35 +724,9 @@ function btcAccountIndexFromPath(path) {
|
|
|
872
724
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
873
725
|
constructor(connector, options) {
|
|
874
726
|
this.vendor = "ledger";
|
|
875
|
-
// Cancel frees the DMK action and its intent-queue slot, but cannot retract a
|
|
876
|
-
// confirmation screen the device is already showing.
|
|
877
|
-
this.cancelCapability = "stops-waiting";
|
|
878
727
|
this.emitter = new TypedEventEmitter();
|
|
879
|
-
this._operations = new OperationRegistry({
|
|
880
|
-
vendor: "ledger",
|
|
881
|
-
onEnded: (operation, reason) => {
|
|
882
|
-
this._discardPendingBinding(this._pendingOperationBindings.get(operation.operationId));
|
|
883
|
-
this._pendingOperationBindings.delete(operation.operationId);
|
|
884
|
-
this.emitter.emit(SDK.OPERATION_ENDED, {
|
|
885
|
-
type: SDK.OPERATION_ENDED,
|
|
886
|
-
payload: { operationId: operation.operationId, reason }
|
|
887
|
-
});
|
|
888
|
-
if (reason === "timeout") {
|
|
889
|
-
void this._releaseOperationConnection(operation);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
728
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
894
729
|
this._sessions = /* @__PURE__ */ new Map();
|
|
895
|
-
// An operation's BLE pick is saved by its first call that gets past the wallet check.
|
|
896
|
-
this._pendingOperationBindings = /* @__PURE__ */ new Map();
|
|
897
|
-
// A one-shot call's unchecked BLE pick, by connectId. It outlives the call only
|
|
898
|
-
// through an unlock retry; any other failure drops it with its session.
|
|
899
|
-
this._pendingPickBindings = /* @__PURE__ */ new Map();
|
|
900
|
-
// Transport each session was opened on, by sessionId. The adapter-wide
|
|
901
|
-
// `_activeConnectionType` moves with every discovery and can go stale.
|
|
902
|
-
this._sessionTransports = /* @__PURE__ */ new Map();
|
|
903
|
-
this._selectedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
904
730
|
this._uiRegistry = new UiRequestRegistry();
|
|
905
731
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
906
732
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -914,32 +740,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
914
740
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
915
741
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
916
742
|
this._doConnectAbortController = null;
|
|
917
|
-
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
918
|
-
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
919
|
-
this._resetPromise = null;
|
|
920
|
-
this._stateGeneration = 0;
|
|
921
|
-
this._connectorTeardownTail = Promise.resolve();
|
|
922
|
-
this._pendingConnectorTeardowns = 0;
|
|
923
|
-
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
924
|
-
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
925
743
|
this._installProgressLastEmittedValue = -Infinity;
|
|
926
744
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
927
745
|
callChain: this.callChain.bind(this),
|
|
928
|
-
getChainFingerprint:
|
|
929
|
-
try {
|
|
930
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
931
|
-
chain,
|
|
932
|
-
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
933
|
-
);
|
|
934
|
-
return success(fingerprint);
|
|
935
|
-
} catch (error) {
|
|
936
|
-
return this.errorToFailure(error);
|
|
937
|
-
}
|
|
938
|
-
},
|
|
939
|
-
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
940
|
-
errorToFailure: (error) => this.errorToFailure(error),
|
|
941
|
-
createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey),
|
|
942
|
-
releaseBundle: (context, signal) => this._dropUncheckedBundlePick(context, signal)
|
|
746
|
+
getChainFingerprint: (connectId, chain) => this.getChainFingerprint(connectId, "", chain)
|
|
943
747
|
});
|
|
944
748
|
// ---------------------------------------------------------------------------
|
|
945
749
|
// Private helpers
|
|
@@ -949,7 +753,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
949
753
|
*
|
|
950
754
|
* - If a session already exists for the given connectId, reuse it.
|
|
951
755
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
952
|
-
* - Otherwise: search →
|
|
756
|
+
* - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws.
|
|
953
757
|
*/
|
|
954
758
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
955
759
|
this._connectingPromise = null;
|
|
@@ -966,12 +770,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
966
770
|
});
|
|
967
771
|
};
|
|
968
772
|
this.deviceDisconnectHandler = (data) => {
|
|
969
|
-
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
970
|
-
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
971
|
-
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
972
|
-
} else {
|
|
973
|
-
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
974
|
-
}
|
|
975
773
|
this._discoveredDevices.delete(data.connectId);
|
|
976
774
|
this._sessions.delete(data.connectId);
|
|
977
775
|
this.emitter.emit(DEVICE.DISCONNECT, {
|
|
@@ -980,8 +778,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
980
778
|
});
|
|
981
779
|
};
|
|
982
780
|
// Forward connector `ui-event` to the public hw.emitter so consumers only
|
|
983
|
-
// need to subscribe in one place.
|
|
984
|
-
// sessionId
|
|
781
|
+
// need to subscribe in one place. For the AppInstallProgress variant we
|
|
782
|
+
// re-key sessionId → connectId via the live _sessions map; if no mapping
|
|
783
|
+
// exists (race during teardown) we drop. All other variants pass through
|
|
784
|
+
// unchanged.
|
|
985
785
|
this.uiEventForwarder = (event) => {
|
|
986
786
|
if (event.type === EConnectorInteraction.AppInstallProgress) {
|
|
987
787
|
let connectId;
|
|
@@ -1026,34 +826,11 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1026
826
|
this._jobQueue = new DeviceJobQueue();
|
|
1027
827
|
this.registerEventListeners();
|
|
1028
828
|
}
|
|
1029
|
-
_discardPendingBinding(owner, status = "cancelled") {
|
|
1030
|
-
const pending = owner?.pendingBinding;
|
|
1031
|
-
if (!owner || !pending) return;
|
|
1032
|
-
owner.pendingBinding = void 0;
|
|
1033
|
-
if (pending.requestId === this._bindingSelectionRequestId) this._finishBleBinding(status);
|
|
1034
|
-
}
|
|
1035
|
-
_finishBleBinding(status) {
|
|
1036
|
-
const selectionRequestId = this._bindingSelectionRequestId;
|
|
1037
|
-
this._bindingSelectionRequestId = void 0;
|
|
1038
|
-
if (selectionRequestId)
|
|
1039
|
-
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
1040
|
-
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
1041
|
-
payload: { selectionRequestId, status }
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
_isBleConnection() {
|
|
1045
|
-
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
1046
|
-
}
|
|
1047
829
|
// Transport
|
|
1048
830
|
get activeTransport() {
|
|
1049
|
-
return this.
|
|
831
|
+
return isLedgerBleConnectionType(this.connector.connectionType) ? "ble" : "hid";
|
|
1050
832
|
}
|
|
1051
833
|
getAvailableTransports() {
|
|
1052
|
-
if (this.connector.availableTransports) {
|
|
1053
|
-
return this.connector.availableTransports.map(
|
|
1054
|
-
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
834
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
1058
835
|
}
|
|
1059
836
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -1070,42 +847,21 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1070
847
|
* The next operation will re-discover and re-connect automatically.
|
|
1071
848
|
*/
|
|
1072
849
|
resetState() {
|
|
1073
|
-
void this._resetStateAndDisconnectSessions();
|
|
1074
|
-
}
|
|
1075
|
-
_resetStateAndDisconnectSessions() {
|
|
1076
|
-
if (this._resetPromise) return this._resetPromise;
|
|
1077
|
-
const sessionIds = new Set(this._sessions.values());
|
|
1078
|
-
this._stateGeneration += 1;
|
|
1079
|
-
this._finishBleBinding("cancelled");
|
|
1080
|
-
this._operations.endAll("runtime-reset");
|
|
1081
|
-
this._doConnectAbortController?.abort();
|
|
1082
850
|
this._discoveredDevices.clear();
|
|
1083
851
|
this._sessions.clear();
|
|
1084
|
-
this._sessionTransports.clear();
|
|
1085
|
-
this._pendingOperationBindings.clear();
|
|
1086
|
-
this._pendingPickBindings.clear();
|
|
1087
|
-
this._selectedBleReconnectTargets.clear();
|
|
1088
852
|
this._connectingPromise = null;
|
|
1089
|
-
this._doConnectAbortController = null;
|
|
1090
853
|
this._uiRegistry.reset();
|
|
1091
854
|
this._jobQueue.clear();
|
|
1092
855
|
this._btcHighIndexConfirmedThisSession = false;
|
|
1093
|
-
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1094
|
-
for (const sessionId of sessionIds) {
|
|
1095
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
1096
|
-
}
|
|
1097
|
-
});
|
|
1098
|
-
this._resetPromise = resetPromise;
|
|
1099
|
-
return resetPromise.finally(() => {
|
|
1100
|
-
if (this._resetPromise === resetPromise) {
|
|
1101
|
-
this._resetPromise = null;
|
|
1102
|
-
}
|
|
1103
|
-
});
|
|
1104
856
|
}
|
|
1105
857
|
async dispose() {
|
|
1106
|
-
|
|
858
|
+
this._uiRegistry.reset();
|
|
859
|
+
this._jobQueue.clear();
|
|
1107
860
|
this.unregisterEventListeners();
|
|
1108
861
|
this.connector.reset();
|
|
862
|
+
this._discoveredDevices.clear();
|
|
863
|
+
this._sessions.clear();
|
|
864
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
1109
865
|
this.emitter.removeAllListeners();
|
|
1110
866
|
}
|
|
1111
867
|
uiResponse(response) {
|
|
@@ -1115,32 +871,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1115
871
|
// Device management
|
|
1116
872
|
// ---------------------------------------------------------------------------
|
|
1117
873
|
async searchDevices(options) {
|
|
1118
|
-
return this._searchDevices(options);
|
|
1119
|
-
}
|
|
1120
|
-
async _searchDevices(options, signal) {
|
|
1121
874
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
1122
875
|
try {
|
|
1123
876
|
if (options?.resetSession) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1130
|
-
const stateGeneration = this._stateGeneration;
|
|
1131
|
-
const devices = await this.connector.searchDevices(
|
|
1132
|
-
options?.transportType ? {
|
|
1133
|
-
transportType: options.transportType,
|
|
1134
|
-
waitForAll: options.waitForAllTransports
|
|
1135
|
-
} : void 0
|
|
1136
|
-
);
|
|
1137
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1138
|
-
if (stateGeneration !== this._stateGeneration) {
|
|
1139
|
-
throw createHwkError({
|
|
1140
|
-
code: HardwareErrorCode3.UserAborted,
|
|
1141
|
-
message: "Ledger discovery was reset"
|
|
1142
|
-
});
|
|
877
|
+
this._doConnectAbortController?.abort();
|
|
878
|
+
this._sessions.clear();
|
|
879
|
+
this._connectingPromise = null;
|
|
880
|
+
this._doConnectAbortController = null;
|
|
881
|
+
this._btcHighIndexConfirmedThisSession = false;
|
|
1143
882
|
}
|
|
883
|
+
await this._ensureDevicePermission();
|
|
884
|
+
const devices = await this.connector.searchDevices();
|
|
1144
885
|
this._discoveredDevices.clear();
|
|
1145
886
|
for (const d of devices) {
|
|
1146
887
|
if (d.connectId) {
|
|
@@ -1148,7 +889,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1148
889
|
}
|
|
1149
890
|
}
|
|
1150
891
|
if (this._discoveredDevices.size === 0) {
|
|
1151
|
-
await this._ensureDevicePermission(
|
|
892
|
+
await this._ensureDevicePermission();
|
|
1152
893
|
}
|
|
1153
894
|
const result = Array.from(this._discoveredDevices.values());
|
|
1154
895
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -1167,280 +908,41 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1167
908
|
throw err;
|
|
1168
909
|
}
|
|
1169
910
|
}
|
|
1170
|
-
async searchDeviceTargets(options) {
|
|
1171
|
-
const devices = await this.searchDevices(options);
|
|
1172
|
-
return devices.map((device) => ({
|
|
1173
|
-
searchTargetId: device.connectId,
|
|
1174
|
-
searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
|
|
1175
|
-
vendor: "ledger",
|
|
1176
|
-
connectionType: device.connectionType,
|
|
1177
|
-
kind: "physical",
|
|
1178
|
-
label: device.label,
|
|
1179
|
-
model: device.model,
|
|
1180
|
-
modelName: device.modelName,
|
|
1181
|
-
serialNumber: device.serialNumber
|
|
1182
|
-
}));
|
|
1183
|
-
}
|
|
1184
|
-
async listConnectionTargets(options) {
|
|
1185
|
-
const targets = await this.searchDeviceTargets(options);
|
|
1186
|
-
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1187
|
-
...target,
|
|
1188
|
-
targetId: searchTargetId
|
|
1189
|
-
}));
|
|
1190
|
-
}
|
|
1191
911
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
1192
|
-
async _evictAllSessions(
|
|
1193
|
-
this._operations.endAll("explicit", preserveOperationId);
|
|
912
|
+
async _evictAllSessions() {
|
|
1194
913
|
if (this._sessions.size === 0) return;
|
|
1195
914
|
const stale = [...this._sessions.values()];
|
|
1196
915
|
this._sessions.clear();
|
|
1197
|
-
for (const sid of stale)
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
await this.connector.disconnect(sid);
|
|
1202
|
-
} catch {
|
|
1203
|
-
}
|
|
916
|
+
for (const sid of stale) {
|
|
917
|
+
try {
|
|
918
|
+
await this.connector.disconnect(sid);
|
|
919
|
+
} catch {
|
|
1204
920
|
}
|
|
1205
|
-
}
|
|
921
|
+
}
|
|
1206
922
|
}
|
|
1207
923
|
static _createDeviceBusyError(method) {
|
|
1208
924
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
1209
925
|
code: HardwareErrorCode3.DeviceBusy
|
|
1210
926
|
});
|
|
1211
927
|
}
|
|
1212
|
-
async connectDevice(
|
|
1213
|
-
try {
|
|
1214
|
-
return await this._jobQueue.enqueue(
|
|
1215
|
-
searchTargetId || "__ledger_connect__",
|
|
1216
|
-
async (signal) => {
|
|
1217
|
-
const connected = await this._connectTarget(searchTargetId, void 0, signal);
|
|
1218
|
-
if (!connected.success) return connected;
|
|
1219
|
-
return this._createOperation(searchTargetId, connected.payload);
|
|
1220
|
-
},
|
|
1221
|
-
{
|
|
1222
|
-
label: "connectDevice",
|
|
1223
|
-
rejectIfBusy: true,
|
|
1224
|
-
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1225
|
-
}
|
|
1226
|
-
);
|
|
1227
|
-
} catch (error) {
|
|
1228
|
-
return this.errorToFailure(error);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
/** Binds the picked BLE device; with a recorded wallet identity it must match first. */
|
|
1232
|
-
async bindBleDevice(params) {
|
|
1233
|
-
const identity = params.identity?.vendor === "ledger" ? params.identity : void 0;
|
|
1234
|
-
try {
|
|
1235
|
-
return await this._jobQueue.enqueue(
|
|
1236
|
-
"__ledger_bind_ble__",
|
|
1237
|
-
async (signal) => {
|
|
1238
|
-
if (!this.getAvailableTransports().includes("ble")) {
|
|
1239
|
-
throw createHwkError({
|
|
1240
|
-
code: HardwareErrorCode3.TransportNotAvailable,
|
|
1241
|
-
message: "Ledger Bluetooth transport is not available"
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
this._activeConnectionType = "ble";
|
|
1245
|
-
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1246
|
-
const attempt = {
|
|
1247
|
-
extra: params.extra,
|
|
1248
|
-
bindingReason: "manual-rebind",
|
|
1249
|
-
deferBindingSave: Boolean(identity)
|
|
1250
|
-
};
|
|
1251
|
-
try {
|
|
1252
|
-
const connectId = await this._connectFirstOrSelect(
|
|
1253
|
-
[],
|
|
1254
|
-
void 0,
|
|
1255
|
-
true,
|
|
1256
|
-
void 0,
|
|
1257
|
-
attempt,
|
|
1258
|
-
signal
|
|
1259
|
-
);
|
|
1260
|
-
const pending = attempt.pendingBinding;
|
|
1261
|
-
if (identity && pending) {
|
|
1262
|
-
attempt.pendingBinding = void 0;
|
|
1263
|
-
const sessionId = this._sessions.get(connectId);
|
|
1264
|
-
if (!sessionId) {
|
|
1265
|
-
throw createHwkError({
|
|
1266
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
1267
|
-
message: "Selected Ledger connection ended"
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
1271
|
-
identity.chain,
|
|
1272
|
-
(method, callParams) => this._runConnectorCall(
|
|
1273
|
-
connectId,
|
|
1274
|
-
method,
|
|
1275
|
-
callParams,
|
|
1276
|
-
signal,
|
|
1277
|
-
void 0,
|
|
1278
|
-
void 0,
|
|
1279
|
-
{ autoInstallApp: true },
|
|
1280
|
-
{ connection: { connectId, sessionId } }
|
|
1281
|
-
)
|
|
1282
|
-
);
|
|
1283
|
-
if (fingerprint !== identity.value) {
|
|
1284
|
-
await this._dropSession(connectId, signal);
|
|
1285
|
-
throw createHwkError({
|
|
1286
|
-
code: HardwareErrorCode3.DeviceMismatch,
|
|
1287
|
-
message: formatDeviceMismatchError(identity.value, fingerprint)
|
|
1288
|
-
});
|
|
1289
|
-
}
|
|
1290
|
-
await this._saveSelectedBleBinding(
|
|
1291
|
-
connectId,
|
|
1292
|
-
pending.requestId,
|
|
1293
|
-
attempt,
|
|
1294
|
-
void 0,
|
|
1295
|
-
signal,
|
|
1296
|
-
identity
|
|
1297
|
-
);
|
|
1298
|
-
}
|
|
1299
|
-
if (!attempt.bindingSaved) {
|
|
1300
|
-
await this._dropSession(connectId, signal);
|
|
1301
|
-
throw createHwkError({
|
|
1302
|
-
code: HardwareErrorCode3.UnknownError,
|
|
1303
|
-
message: "Bluetooth binding could not be saved",
|
|
1304
|
-
origin: "host"
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
return success(connectId);
|
|
1308
|
-
} catch (error) {
|
|
1309
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1310
|
-
throw error;
|
|
1311
|
-
}
|
|
1312
|
-
},
|
|
1313
|
-
{
|
|
1314
|
-
label: "bindBleDevice",
|
|
1315
|
-
rejectIfBusy: true,
|
|
1316
|
-
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1317
|
-
}
|
|
1318
|
-
);
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
return this.errorToFailure(error);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
async acquireOperation(connectId, context) {
|
|
1324
|
-
try {
|
|
1325
|
-
return await this._jobQueue.enqueue(
|
|
1326
|
-
connectId || "__ledger_acquire__",
|
|
1327
|
-
async (signal) => {
|
|
1328
|
-
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1329
|
-
const hint = context.knownConnections?.find(
|
|
1330
|
-
(connection) => connection.transport === transport
|
|
1331
|
-
);
|
|
1332
|
-
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1333
|
-
await this._ensureDevicePermission(target, void 0, signal);
|
|
1334
|
-
const attempt = {
|
|
1335
|
-
...context,
|
|
1336
|
-
extra: context.extra ? { ...context.extra } : void 0,
|
|
1337
|
-
// The operation's calls decide whether the pick is the right wallet.
|
|
1338
|
-
deferBindingSave: true
|
|
1339
|
-
};
|
|
1340
|
-
try {
|
|
1341
|
-
const resolvedConnectId = await this.ensureConnected(
|
|
1342
|
-
target,
|
|
1343
|
-
signal,
|
|
1344
|
-
true,
|
|
1345
|
-
void 0,
|
|
1346
|
-
attempt
|
|
1347
|
-
);
|
|
1348
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
1349
|
-
const result = this._createOperation(connectId, resolvedConnectId);
|
|
1350
|
-
if (result.success && attempt.pendingBinding) {
|
|
1351
|
-
this._pendingOperationBindings.set(result.payload, attempt);
|
|
1352
|
-
}
|
|
1353
|
-
return result;
|
|
1354
|
-
} catch (error) {
|
|
1355
|
-
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1356
|
-
throw error;
|
|
1357
|
-
}
|
|
1358
|
-
},
|
|
1359
|
-
{
|
|
1360
|
-
label: "acquireOperation",
|
|
1361
|
-
rejectIfBusy: true,
|
|
1362
|
-
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1363
|
-
}
|
|
1364
|
-
);
|
|
1365
|
-
} catch (error) {
|
|
1366
|
-
return this.errorToFailure(error);
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1370
|
-
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1371
|
-
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1372
|
-
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1373
|
-
const connectionType = sessionId && this._sessionTransports.get(sessionId) || (this._isBleConnection() ? "ble" : "usb");
|
|
1374
|
-
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1375
|
-
vendor: "ledger",
|
|
1376
|
-
model: "unknown",
|
|
1377
|
-
firmwareVersion: "",
|
|
1378
|
-
deviceId: "",
|
|
1379
|
-
connectId: resolvedConnectId,
|
|
1380
|
-
connectionType
|
|
1381
|
-
};
|
|
1382
|
-
const operation = this._operations.create({
|
|
1383
|
-
searchTargetId,
|
|
1384
|
-
connectId: resolvedConnectId,
|
|
1385
|
-
device,
|
|
1386
|
-
connectionType,
|
|
1387
|
-
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1388
|
-
});
|
|
1389
|
-
return success(operation.operationId);
|
|
1390
|
-
}
|
|
1391
|
-
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
928
|
+
async connectDevice(connectId) {
|
|
1392
929
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
1393
930
|
try {
|
|
1394
|
-
this.
|
|
1395
|
-
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1396
|
-
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1397
|
-
this._activeConnectionType = discoveredType;
|
|
1398
|
-
}
|
|
1399
|
-
if (this._isBleConnection() && !connectId) {
|
|
931
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
1400
932
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1401
933
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1402
934
|
});
|
|
1403
935
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
const isMultiTransport = (this.connector.availableTransports?.length ?? 0) > 1;
|
|
1407
|
-
if (!isMultiTransport && this._isBleConnection()) {
|
|
1408
|
-
const previousSessionId = this._sessions.get(connectId);
|
|
1409
|
-
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1410
|
-
if (previousSessionId) {
|
|
1411
|
-
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1412
|
-
this._sessions.delete(connectId);
|
|
1413
|
-
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1414
|
-
}
|
|
1415
|
-
} else {
|
|
1416
|
-
await this._evictAllSessions(preserveOperationId);
|
|
1417
|
-
}
|
|
1418
|
-
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1419
|
-
const stateGeneration = this._stateGeneration;
|
|
1420
|
-
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1421
|
-
let session;
|
|
1422
|
-
this._connectingConnectId = connectId;
|
|
1423
|
-
try {
|
|
1424
|
-
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1425
|
-
transportType: this._activeConnectionType ?? "usb"
|
|
1426
|
-
}) : await this.connector.connect(connectId);
|
|
1427
|
-
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1428
|
-
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1429
|
-
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1430
|
-
code: HardwareErrorCode3.UserAborted
|
|
1431
|
-
});
|
|
1432
|
-
}
|
|
1433
|
-
} finally {
|
|
1434
|
-
if (this._connectingConnectId === connectId) this._connectingConnectId = void 0;
|
|
1435
|
-
releaseOperation();
|
|
936
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
937
|
+
await this._evictAllSessions();
|
|
1436
938
|
}
|
|
1437
|
-
|
|
1438
|
-
this.
|
|
1439
|
-
this.
|
|
939
|
+
await this._ensureDevicePermission(connectId);
|
|
940
|
+
const session = await this.connector.connect(connectId);
|
|
941
|
+
this._sessions.set(connectId, session.sessionId);
|
|
1440
942
|
if (session.deviceInfo) {
|
|
1441
|
-
this._discoveredDevices.set(
|
|
943
|
+
this._discoveredDevices.set(connectId, session.deviceInfo);
|
|
1442
944
|
}
|
|
1443
|
-
const result = success(
|
|
945
|
+
const result = success(connectId);
|
|
1444
946
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
1445
947
|
return result;
|
|
1446
948
|
} catch (err) {
|
|
@@ -1453,60 +955,30 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1453
955
|
return failureResult;
|
|
1454
956
|
}
|
|
1455
957
|
}
|
|
1456
|
-
async
|
|
1457
|
-
const operation = this._operations.find(operationId);
|
|
1458
|
-
if (!operation) {
|
|
1459
|
-
this._operations.resolve(operationId);
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1463
|
-
if (!endedOperation) return;
|
|
1464
|
-
const { connectId } = operation;
|
|
958
|
+
async disconnectDevice(connectId) {
|
|
1465
959
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1466
|
-
method: "
|
|
960
|
+
method: "disconnectDevice",
|
|
1467
961
|
connectId,
|
|
1468
962
|
params: { connectId }
|
|
1469
963
|
});
|
|
1470
964
|
try {
|
|
1471
|
-
|
|
1472
|
-
|
|
965
|
+
const sessionId = this._sessions.get(connectId);
|
|
966
|
+
if (sessionId) {
|
|
967
|
+
await this.connector.disconnect(sessionId);
|
|
968
|
+
this._sessions.delete(connectId);
|
|
969
|
+
}
|
|
970
|
+
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1473
971
|
} catch (err) {
|
|
1474
972
|
const e = err;
|
|
1475
973
|
debugLog("[LedgerAdapter][RES]", {
|
|
1476
|
-
method: "
|
|
974
|
+
method: "disconnectDevice",
|
|
1477
975
|
success: false,
|
|
1478
976
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1479
977
|
});
|
|
1480
978
|
throw err;
|
|
1481
979
|
}
|
|
1482
980
|
}
|
|
1483
|
-
async
|
|
1484
|
-
const sessionIds = /* @__PURE__ */ new Set();
|
|
1485
|
-
for (const [connectId, sessionId] of this._sessions) {
|
|
1486
|
-
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1487
|
-
this._sessions.delete(connectId);
|
|
1488
|
-
sessionIds.add(sessionId);
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
for (const sessionId of sessionIds) {
|
|
1492
|
-
await this._runConnectorTeardown(
|
|
1493
|
-
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1494
|
-
);
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
async _releaseLostOperationConnection(operationId) {
|
|
1498
|
-
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1499
|
-
if (!endedOperation) return;
|
|
1500
|
-
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1501
|
-
await this._releaseOperationConnection(endedOperation);
|
|
1502
|
-
}
|
|
1503
|
-
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1504
|
-
let connectId;
|
|
1505
|
-
try {
|
|
1506
|
-
connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1507
|
-
} catch (error) {
|
|
1508
|
-
return this.errorToFailure(error);
|
|
1509
|
-
}
|
|
981
|
+
async getDeviceInfo(connectId, deviceId) {
|
|
1510
982
|
debugLog("[LedgerAdapter][REQ]", {
|
|
1511
983
|
method: "getDeviceInfo",
|
|
1512
984
|
connectId,
|
|
@@ -1541,19 +1013,11 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1541
1013
|
success: false,
|
|
1542
1014
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1543
1015
|
});
|
|
1544
|
-
|
|
1016
|
+
throw err;
|
|
1545
1017
|
}
|
|
1546
1018
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const { connection } = context;
|
|
1550
|
-
const owner = connection ? this._pendingPickBindings.get(connection.connectId) : void 0;
|
|
1551
|
-
if (!connection || !owner) return;
|
|
1552
|
-
this._pendingPickBindings.delete(connection.connectId);
|
|
1553
|
-
this._discardPendingBinding(owner, signal.aborted ? "cancelled" : "failed");
|
|
1554
|
-
if (this._sessions.get(connection.connectId) === connection.sessionId) {
|
|
1555
|
-
await this._dropSession(connection.connectId, signal);
|
|
1556
|
-
}
|
|
1019
|
+
getSupportedChains() {
|
|
1020
|
+
return ["evm", "btc", "sol", "tron"];
|
|
1557
1021
|
}
|
|
1558
1022
|
// ---------------------------------------------------------------------------
|
|
1559
1023
|
// Chain call helper
|
|
@@ -1589,23 +1053,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1589
1053
|
if (params && typeof params === "object") {
|
|
1590
1054
|
const {
|
|
1591
1055
|
autoInstallApp,
|
|
1592
|
-
operationId,
|
|
1593
1056
|
passphraseState: _passphraseState,
|
|
1594
1057
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1595
|
-
knownConnections,
|
|
1596
|
-
extra,
|
|
1597
|
-
allowDeviceSelection,
|
|
1598
|
-
supportedTransports,
|
|
1599
1058
|
...rest
|
|
1600
1059
|
} = params;
|
|
1601
1060
|
return {
|
|
1602
1061
|
commonParams: {
|
|
1603
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1604
|
-
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1605
|
-
knownConnections,
|
|
1606
|
-
extra,
|
|
1607
|
-
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0,
|
|
1608
|
-
supportedTransports
|
|
1062
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1609
1063
|
},
|
|
1610
1064
|
rest
|
|
1611
1065
|
};
|
|
@@ -1781,28 +1235,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1781
1235
|
);
|
|
1782
1236
|
}
|
|
1783
1237
|
// ---------------------------------------------------------------------------
|
|
1784
|
-
//
|
|
1785
|
-
//
|
|
1786
|
-
|
|
1787
|
-
return this.callChainWithMergedParams(
|
|
1788
|
-
connectId,
|
|
1789
|
-
deviceId,
|
|
1790
|
-
"zcash",
|
|
1791
|
-
"zcashGetFullViewingKey",
|
|
1792
|
-
params
|
|
1793
|
-
);
|
|
1794
|
-
}
|
|
1795
|
-
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1796
|
-
return this.callChainWithMergedParams(
|
|
1797
|
-
connectId,
|
|
1798
|
-
deviceId,
|
|
1799
|
-
"zcash",
|
|
1800
|
-
"zcashGetShieldedAddress",
|
|
1801
|
-
params
|
|
1802
|
-
);
|
|
1803
|
-
}
|
|
1804
|
-
// ---------------------------------------------------------------------------
|
|
1805
|
-
// App management: OS-level app install/list, no fingerprint or chain dispatch.
|
|
1238
|
+
// App management — OS-level Ledger app install / list. Bypasses fingerprint
|
|
1239
|
+
// and chain-handler dispatch; installApp progress is forwarded to the adapter
|
|
1240
|
+
// emitter via 'ui-event' AppInstallProgress events.
|
|
1806
1241
|
// ---------------------------------------------------------------------------
|
|
1807
1242
|
async installApp(connectId, appName) {
|
|
1808
1243
|
try {
|
|
@@ -1888,7 +1323,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1888
1323
|
);
|
|
1889
1324
|
}
|
|
1890
1325
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1891
|
-
|
|
1326
|
+
this.resetState();
|
|
1892
1327
|
}
|
|
1893
1328
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1894
1329
|
if (!result.isGenuine) {
|
|
@@ -1914,10 +1349,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1914
1349
|
if (relayUrl) {
|
|
1915
1350
|
try {
|
|
1916
1351
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1917
|
-
|
|
1352
|
+
this.resetState();
|
|
1918
1353
|
} catch {
|
|
1919
1354
|
this.connector.reset();
|
|
1920
|
-
|
|
1355
|
+
this.resetState();
|
|
1921
1356
|
}
|
|
1922
1357
|
}
|
|
1923
1358
|
}
|
|
@@ -1929,20 +1364,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1929
1364
|
this.emitter.off(event, listener);
|
|
1930
1365
|
}
|
|
1931
1366
|
cancel(connectId) {
|
|
1932
|
-
const namedOperationIsLive = (id) => {
|
|
1933
|
-
try {
|
|
1934
|
-
this._operations.resolve(id);
|
|
1935
|
-
return true;
|
|
1936
|
-
} catch {
|
|
1937
|
-
return false;
|
|
1938
|
-
}
|
|
1939
|
-
};
|
|
1940
|
-
if (isHardwareOperationId(connectId) && !namedOperationIsLive(connectId)) {
|
|
1941
|
-
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1942
|
-
connectId
|
|
1943
|
-
});
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
1367
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1947
1368
|
code: HardwareErrorCode3.UserAborted,
|
|
1948
1369
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1953,48 +1374,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1953
1374
|
this._lastCancelReason = void 0;
|
|
1954
1375
|
}
|
|
1955
1376
|
}, 2e3);
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
operationId = connectId;
|
|
1960
|
-
} else if (!connectId && isHardwareOperationId(activeJobId)) {
|
|
1961
|
-
operationId = activeJobId;
|
|
1962
|
-
}
|
|
1963
|
-
let resolvedConnectId = connectId;
|
|
1964
|
-
if (operationId) {
|
|
1965
|
-
try {
|
|
1966
|
-
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1967
|
-
} catch {
|
|
1968
|
-
resolvedConnectId = void 0;
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1972
|
-
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1973
|
-
if (!connectId) {
|
|
1974
|
-
this._uiRegistry.cancel();
|
|
1975
|
-
} else if (pendingOperationId) {
|
|
1976
|
-
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1977
|
-
}
|
|
1978
|
-
this._finishBleBinding("cancelled");
|
|
1979
|
-
if (!connectId) this._pendingOperationBindings.clear();
|
|
1980
|
-
else if (pendingOperationId) this._pendingOperationBindings.delete(pendingOperationId);
|
|
1981
|
-
const queueKeys = /* @__PURE__ */ new Set();
|
|
1982
|
-
if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
|
|
1983
|
-
if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
|
|
1984
|
-
if (queueKeys.size) {
|
|
1985
|
-
let cancelledAnyJob = false;
|
|
1986
|
-
for (const key of queueKeys) {
|
|
1987
|
-
cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
|
|
1988
|
-
}
|
|
1989
|
-
debugLog("[LedgerAdapter] cancel routed to queue keys", {
|
|
1990
|
-
queueKeys: [...queueKeys],
|
|
1991
|
-
cancelledAnyJob
|
|
1992
|
-
});
|
|
1377
|
+
this._uiRegistry.cancel();
|
|
1378
|
+
if (connectId) {
|
|
1379
|
+
this._jobQueue.cancelActiveAndPending(connectId, userAbortReason);
|
|
1993
1380
|
} else {
|
|
1994
1381
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1995
1382
|
}
|
|
1996
|
-
if (
|
|
1997
|
-
const sessionId = this._sessions.get(
|
|
1383
|
+
if (connectId) {
|
|
1384
|
+
const sessionId = this._sessions.get(connectId) ?? connectId;
|
|
1998
1385
|
void this.connector.cancel(sessionId);
|
|
1999
1386
|
} else {
|
|
2000
1387
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -2002,86 +1389,45 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2002
1389
|
if (this._connectingPromise) {
|
|
2003
1390
|
this._doConnectAbortController?.abort(userAbortReason);
|
|
2004
1391
|
}
|
|
2005
|
-
const connectingConnectId = this._connectingConnectId;
|
|
2006
|
-
if (connectingConnectId) void this.connector.cancel(connectingConnectId);
|
|
2007
1392
|
}
|
|
2008
1393
|
// ---------------------------------------------------------------------------
|
|
2009
1394
|
// Chain fingerprint
|
|
2010
1395
|
// ---------------------------------------------------------------------------
|
|
2011
|
-
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
2012
1396
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
2013
1397
|
try {
|
|
2014
1398
|
const fingerprint = await this._computeChainFingerprint(
|
|
2015
1399
|
chain,
|
|
2016
1400
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
2017
1401
|
);
|
|
2018
|
-
if (deviceId) {
|
|
2019
|
-
if (fingerprint !== deviceId) {
|
|
2020
|
-
return failure2(
|
|
2021
|
-
HardwareErrorCode3.DeviceMismatch,
|
|
2022
|
-
formatDeviceMismatchError(deviceId, fingerprint)
|
|
2023
|
-
);
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
1402
|
return success(fingerprint);
|
|
2027
1403
|
} catch (err) {
|
|
2028
1404
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
2029
1405
|
return this.errorToFailure(err);
|
|
2030
1406
|
}
|
|
2031
1407
|
}
|
|
2032
|
-
/**
|
|
2033
|
-
|
|
1408
|
+
/**
|
|
1409
|
+
* Verify fingerprint using an existing sessionId directly.
|
|
1410
|
+
* Safe to call inside connectorCall without causing queue deadlock.
|
|
1411
|
+
*/
|
|
1412
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
1413
|
+
if (!deviceId) return { success: true };
|
|
2034
1414
|
try {
|
|
2035
|
-
const
|
|
2036
|
-
|
|
2037
|
-
this.
|
|
2038
|
-
{
|
|
2039
|
-
selectionRequestId,
|
|
2040
|
-
connection: { transport: "ble", connectId },
|
|
2041
|
-
...identity ? { identity } : {},
|
|
2042
|
-
extra: context?.extra,
|
|
2043
|
-
operationId
|
|
2044
|
-
},
|
|
2045
|
-
signal
|
|
1415
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1416
|
+
chain,
|
|
1417
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
2046
1418
|
);
|
|
2047
|
-
if (
|
|
2048
|
-
|
|
2049
|
-
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
2050
|
-
connectId,
|
|
2051
|
-
reason: outcome.reason
|
|
2052
|
-
});
|
|
1419
|
+
if (fingerprint === deviceId) {
|
|
1420
|
+
return { success: true };
|
|
2053
1421
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
this._bindingSelectionRequestId = void 0;
|
|
1422
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
const mapped = mapLedgerError(err);
|
|
1425
|
+
if (mapped.code === HardwareErrorCode3.WrongApp || mapped.code === HardwareErrorCode3.DeviceLocked) {
|
|
1426
|
+
return { success: true };
|
|
2060
1427
|
}
|
|
1428
|
+
throw err;
|
|
2061
1429
|
}
|
|
2062
1430
|
}
|
|
2063
|
-
async _dropSession(connectId, signal) {
|
|
2064
|
-
const sessionId = this._sessions.get(connectId);
|
|
2065
|
-
this._sessions.delete(connectId);
|
|
2066
|
-
if (!sessionId) return;
|
|
2067
|
-
this._sessionTransports.delete(sessionId);
|
|
2068
|
-
const teardown = this._runConnectorTeardown(() => this.connector.disconnect(sessionId)).catch(
|
|
2069
|
-
() => void 0
|
|
2070
|
-
);
|
|
2071
|
-
if (!signal.aborted) await teardown;
|
|
2072
|
-
}
|
|
2073
|
-
/** Verify on the acquired session without re-entering the job queue. */
|
|
2074
|
-
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
2075
|
-
if (!deviceId) return { success: true };
|
|
2076
|
-
const fingerprint = await this._computeChainFingerprint(
|
|
2077
|
-
chain,
|
|
2078
|
-
(method, params) => this._callConnector(sessionId, method, params)
|
|
2079
|
-
);
|
|
2080
|
-
if (fingerprint === deviceId) {
|
|
2081
|
-
return { success: true };
|
|
2082
|
-
}
|
|
2083
|
-
return { success: false, expected: deviceId, actual: fingerprint };
|
|
2084
|
-
}
|
|
2085
1431
|
/**
|
|
2086
1432
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
2087
1433
|
*
|
|
@@ -2109,23 +1455,11 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2109
1455
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
2110
1456
|
} else if (chain === "tron") {
|
|
2111
1457
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2112
|
-
} else if (chain === "zcash") {
|
|
2113
|
-
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
2114
1458
|
} else {
|
|
2115
1459
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
2116
1460
|
}
|
|
2117
1461
|
return deriveDeviceFingerprint(address);
|
|
2118
1462
|
}
|
|
2119
|
-
/**
|
|
2120
|
-
* Operation owning the running job, so a mid-call UI request can name it.
|
|
2121
|
-
* Undefined at cold start when no operation owns the job.
|
|
2122
|
-
*/
|
|
2123
|
-
_activeOperationId() {
|
|
2124
|
-
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
2125
|
-
if (!activeJobId) return void 0;
|
|
2126
|
-
if (isHardwareOperationId(activeJobId)) return activeJobId;
|
|
2127
|
-
return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
|
|
2128
|
-
}
|
|
2129
1463
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
2130
1464
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
2131
1465
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
@@ -2135,18 +1469,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2135
1469
|
if (signal?.aborted) {
|
|
2136
1470
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2137
1471
|
}
|
|
2138
|
-
const operationId = this._activeOperationId();
|
|
2139
1472
|
const waitPromise = this._uiRegistry.wait(
|
|
2140
|
-
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2141
|
-
{ operationId }
|
|
1473
|
+
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2142
1474
|
);
|
|
2143
1475
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
2144
1476
|
type: UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2145
1477
|
payload: {
|
|
2146
1478
|
vendor: "ledger",
|
|
2147
1479
|
reason: "device-not-found",
|
|
2148
|
-
message: "Please connect and unlock your Ledger device"
|
|
2149
|
-
operationId
|
|
1480
|
+
message: "Please connect and unlock your Ledger device"
|
|
2150
1481
|
}
|
|
2151
1482
|
});
|
|
2152
1483
|
let payload;
|
|
@@ -2207,18 +1538,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2207
1538
|
return { ...params, showOnDevice: true };
|
|
2208
1539
|
}
|
|
2209
1540
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2210
|
-
const operationId = this._activeOperationId();
|
|
2211
1541
|
const waitPromise = this._uiRegistry.wait(
|
|
2212
|
-
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2213
|
-
{ operationId }
|
|
1542
|
+
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2214
1543
|
);
|
|
2215
1544
|
this.emitter.emit(UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
2216
1545
|
type: UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2217
1546
|
payload: {
|
|
2218
1547
|
vendor: "ledger",
|
|
2219
1548
|
path,
|
|
2220
|
-
accountIndex
|
|
2221
|
-
operationId
|
|
1549
|
+
accountIndex
|
|
2222
1550
|
}
|
|
2223
1551
|
});
|
|
2224
1552
|
try {
|
|
@@ -2235,14 +1563,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2235
1563
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
2236
1564
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
2237
1565
|
async _waitForInstallAppConfirm(appName) {
|
|
2238
|
-
const operationId = this._activeOperationId();
|
|
2239
1566
|
const waitPromise = this._uiRegistry.wait(
|
|
2240
|
-
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2241
|
-
{ operationId }
|
|
1567
|
+
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2242
1568
|
);
|
|
2243
1569
|
this.emitter.emit(UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
2244
1570
|
type: UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2245
|
-
payload: { vendor: "ledger", appName
|
|
1571
|
+
payload: { vendor: "ledger", appName }
|
|
2246
1572
|
});
|
|
2247
1573
|
try {
|
|
2248
1574
|
const payload = await waitPromise;
|
|
@@ -2258,17 +1584,16 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2258
1584
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
2259
1585
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
2260
1586
|
// doesn't kill caller B's await.
|
|
2261
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false
|
|
1587
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2262
1588
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
2263
|
-
this.
|
|
2264
|
-
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1589
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && !connectId) {
|
|
2265
1590
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
2266
1591
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2267
1592
|
});
|
|
2268
1593
|
}
|
|
2269
1594
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
2270
1595
|
if (!connectId && this._sessions.size > 0) {
|
|
2271
|
-
if (!this.
|
|
1596
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) {
|
|
2272
1597
|
throw Object.assign(
|
|
2273
1598
|
new Error(
|
|
2274
1599
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -2278,24 +1603,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2278
1603
|
}
|
|
2279
1604
|
return this._sessions.keys().next().value;
|
|
2280
1605
|
}
|
|
2281
|
-
if (!this._connectingPromise
|
|
2282
|
-
|
|
2283
|
-
this._doConnectAbortController
|
|
2284
|
-
const innerSignal = controller.signal;
|
|
1606
|
+
if (!this._connectingPromise) {
|
|
1607
|
+
this._doConnectAbortController = new AbortController();
|
|
1608
|
+
const innerSignal = this._doConnectAbortController.signal;
|
|
2285
1609
|
this._connectingPromise = (async () => {
|
|
2286
1610
|
try {
|
|
2287
|
-
return await this._doConnect(
|
|
2288
|
-
innerSignal,
|
|
2289
|
-
connectId,
|
|
2290
|
-
allowUsbEphemeralFallback,
|
|
2291
|
-
preserveOperationId,
|
|
2292
|
-
context
|
|
2293
|
-
);
|
|
1611
|
+
return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback);
|
|
2294
1612
|
} finally {
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
this._doConnectAbortController = null;
|
|
2298
|
-
}
|
|
1613
|
+
this._connectingPromise = null;
|
|
1614
|
+
this._doConnectAbortController = null;
|
|
2299
1615
|
}
|
|
2300
1616
|
})();
|
|
2301
1617
|
}
|
|
@@ -2304,57 +1620,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2304
1620
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
2305
1621
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
2306
1622
|
// throw DeviceNotFound so the user is kicked out of the loop.
|
|
2307
|
-
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
preserveOperationId,
|
|
2323
|
-
context,
|
|
2324
|
-
internalSignal
|
|
2325
|
-
);
|
|
2326
|
-
}
|
|
2327
|
-
if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
|
|
2328
|
-
throw createHwkError({
|
|
2329
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2330
|
-
message: "This Ledger model has no Bluetooth transport; connect it over USB"
|
|
2331
|
-
});
|
|
2332
|
-
}
|
|
2333
|
-
this._activeConnectionType = "ble";
|
|
2334
|
-
const knownBle = context?.knownConnections?.find(
|
|
2335
|
-
(connection) => connection.transport === "ble"
|
|
2336
|
-
);
|
|
2337
|
-
if (knownBle?.transport === "ble") {
|
|
2338
|
-
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2339
|
-
}
|
|
2340
|
-
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2341
|
-
throw createHwkError({
|
|
2342
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2343
|
-
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2344
|
-
});
|
|
1623
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
1624
|
+
if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) {
|
|
1625
|
+
try {
|
|
1626
|
+
return await this._connectDeviceOrThrow(targetConnectId);
|
|
1627
|
+
} catch (err) {
|
|
1628
|
+
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
1629
|
+
throw err;
|
|
1630
|
+
}
|
|
1631
|
+
this._discoveredDevices.delete(targetConnectId);
|
|
1632
|
+
if (isDeviceDisconnectedError(err)) {
|
|
1633
|
+
try {
|
|
1634
|
+
this.connector.reset?.();
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
2345
1638
|
}
|
|
2346
|
-
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2347
|
-
return this._connectFirstOrSelect(
|
|
2348
|
-
bleDevices,
|
|
2349
|
-
void 0,
|
|
2350
|
-
allowUsbEphemeralFallback,
|
|
2351
|
-
preserveOperationId,
|
|
2352
|
-
context,
|
|
2353
|
-
internalSignal
|
|
2354
|
-
);
|
|
2355
|
-
}
|
|
2356
|
-
if (this._isBleConnection() && targetConnectId) {
|
|
2357
|
-
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
2358
1639
|
}
|
|
2359
1640
|
let confirms = 0;
|
|
2360
1641
|
while (!internalSignal.aborted) {
|
|
@@ -2362,28 +1643,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2362
1643
|
type: EConnectorInteraction.Searching,
|
|
2363
1644
|
payload: { sessionId: "" }
|
|
2364
1645
|
});
|
|
2365
|
-
let devices = await this.
|
|
2366
|
-
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1646
|
+
let devices = await this.searchDevices();
|
|
2367
1647
|
if (devices.length === 0) {
|
|
2368
1648
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
2369
1649
|
await new Promise((resolve) => {
|
|
2370
1650
|
setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
2371
1651
|
});
|
|
2372
|
-
|
|
2373
|
-
devices = await this._searchDevices(void 0, internalSignal);
|
|
2374
|
-
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1652
|
+
devices = await this.searchDevices();
|
|
2375
1653
|
if (devices.length > 0) break;
|
|
2376
1654
|
}
|
|
2377
1655
|
}
|
|
2378
|
-
if (devices.length > 0
|
|
1656
|
+
if (devices.length > 0) {
|
|
2379
1657
|
try {
|
|
2380
1658
|
return await this._connectFirstOrSelect(
|
|
2381
1659
|
devices,
|
|
2382
1660
|
targetConnectId,
|
|
2383
|
-
allowUsbEphemeralFallback
|
|
2384
|
-
preserveOperationId,
|
|
2385
|
-
context,
|
|
2386
|
-
internalSignal
|
|
1661
|
+
allowUsbEphemeralFallback
|
|
2387
1662
|
);
|
|
2388
1663
|
} catch (err) {
|
|
2389
1664
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -2412,147 +1687,45 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2412
1687
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2413
1688
|
throw new Error("_doConnect aborted");
|
|
2414
1689
|
}
|
|
2415
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2416
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
1690
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
2417
1691
|
if (targetConnectId) {
|
|
2418
1692
|
const target = devices.find(
|
|
2419
1693
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
2420
1694
|
);
|
|
2421
1695
|
if (target) {
|
|
2422
|
-
return this._connectDeviceOrThrow(target.connectId
|
|
1696
|
+
return this._connectDeviceOrThrow(target.connectId);
|
|
2423
1697
|
}
|
|
2424
|
-
if (!this.
|
|
1698
|
+
if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
2425
1699
|
debugLog(
|
|
2426
1700
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
2427
1701
|
);
|
|
2428
|
-
return this._connectDeviceOrThrow(devices[0].connectId
|
|
1702
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2429
1703
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2436
|
-
}
|
|
2437
|
-
throw err;
|
|
1704
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
1705
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
1706
|
+
});
|
|
1707
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
1708
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2438
1709
|
}
|
|
1710
|
+
throw err;
|
|
2439
1711
|
}
|
|
2440
|
-
|
|
2441
|
-
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1712
|
+
if (isLedgerBleConnectionType(this.connector.connectionType)) {
|
|
2442
1713
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
2443
1714
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2444
1715
|
});
|
|
2445
1716
|
}
|
|
2446
|
-
if (
|
|
2447
|
-
|
|
2448
|
-
if (context) context.bindingSessionId = bindingSessionId;
|
|
2449
|
-
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2450
|
-
const knownUsb = context?.knownConnections?.find(
|
|
2451
|
-
(connection) => connection.transport === "usb"
|
|
2452
|
-
);
|
|
2453
|
-
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2454
|
-
const { device, requestId } = await requestBleDeviceSelection({
|
|
2455
|
-
emitter: this.emitter,
|
|
2456
|
-
registry: this._uiRegistry,
|
|
2457
|
-
signal,
|
|
2458
|
-
allowUsbFallback,
|
|
2459
|
-
scan: async () => {
|
|
2460
|
-
if (allowUsbFallback) {
|
|
2461
|
-
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2462
|
-
const candidate = usbDevices.find(
|
|
2463
|
-
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2464
|
-
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2465
|
-
if (candidate) return [candidate];
|
|
2466
|
-
}
|
|
2467
|
-
return this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal);
|
|
2468
|
-
},
|
|
2469
|
-
request: {
|
|
2470
|
-
devices,
|
|
2471
|
-
bindingSessionId,
|
|
2472
|
-
context: {
|
|
2473
|
-
kind: "bind-connection",
|
|
2474
|
-
transport: "ble",
|
|
2475
|
-
reason: context?.bindingReason ?? (targetConnectId ? "known-connection-unavailable" : "missing-binding")
|
|
2476
|
-
},
|
|
2477
|
-
extra: context?.extra,
|
|
2478
|
-
operationId: preserveOperationId
|
|
2479
|
-
}
|
|
2480
|
-
});
|
|
2481
|
-
if (device.connectionType === "usb") {
|
|
2482
|
-
this._activeConnectionType = "usb";
|
|
2483
|
-
if (context) context.selectedConnection = void 0;
|
|
2484
|
-
this._bindingSelectionRequestId = requestId;
|
|
2485
|
-
this._finishBleBinding("cancelled");
|
|
2486
|
-
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2487
|
-
}
|
|
2488
|
-
this._bindingSelectionRequestId = requestId;
|
|
2489
|
-
const connected = await this._connectDeviceOrThrow(
|
|
2490
|
-
device.connectId,
|
|
2491
|
-
preserveOperationId,
|
|
2492
|
-
signal
|
|
2493
|
-
);
|
|
2494
|
-
if (context) context.selectedConnection = { connectId: connected, requestId };
|
|
2495
|
-
if (context?.deferBindingSave) {
|
|
2496
|
-
context.pendingBinding = { connectId: connected, requestId };
|
|
2497
|
-
} else {
|
|
2498
|
-
await this._saveSelectedBleBinding(
|
|
2499
|
-
connected,
|
|
2500
|
-
requestId,
|
|
2501
|
-
context,
|
|
2502
|
-
preserveOperationId,
|
|
2503
|
-
signal
|
|
2504
|
-
);
|
|
2505
|
-
}
|
|
2506
|
-
return connected;
|
|
2507
|
-
}
|
|
2508
|
-
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2509
|
-
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2510
|
-
throw createHwkError({
|
|
2511
|
-
code: HardwareErrorCode3.DeviceNotFound,
|
|
2512
|
-
message: "Select a Ledger device before continuing"
|
|
2513
|
-
});
|
|
2514
|
-
}
|
|
2515
|
-
const requestId = this._uiRegistry.createRequestId();
|
|
2516
|
-
const operationId = this._activeOperationId();
|
|
2517
|
-
const waitPromise = this._uiRegistry.wait(
|
|
2518
|
-
UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2519
|
-
{ requestId, operationId }
|
|
2520
|
-
);
|
|
2521
|
-
this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2522
|
-
type: UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2523
|
-
payload: {
|
|
2524
|
-
devices,
|
|
2525
|
-
requestId,
|
|
2526
|
-
operationId,
|
|
2527
|
-
context: requiresBleSelection ? {
|
|
2528
|
-
kind: "bind-connection",
|
|
2529
|
-
transport: "ble",
|
|
2530
|
-
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2531
|
-
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2532
|
-
extra: context?.extra
|
|
2533
|
-
}
|
|
2534
|
-
});
|
|
2535
|
-
const { sdkConnectId } = await this._abortable(signal, waitPromise);
|
|
2536
|
-
_LedgerAdapter._throwIfAborted(signal);
|
|
2537
|
-
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2538
|
-
if (!selected) {
|
|
2539
|
-
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2540
|
-
code: HardwareErrorCode3.DeviceNotFound
|
|
2541
|
-
});
|
|
2542
|
-
}
|
|
2543
|
-
if (context && requiresBleSelection)
|
|
2544
|
-
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2545
|
-
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1717
|
+
if (devices.length > 1) {
|
|
1718
|
+
throw createMultipleUsbLedgerDevicesError();
|
|
2546
1719
|
}
|
|
2547
1720
|
if (devices.length !== 1) {
|
|
2548
1721
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
2549
1722
|
code: HardwareErrorCode3.DeviceNotFound
|
|
2550
1723
|
});
|
|
2551
1724
|
}
|
|
2552
|
-
return this._connectDeviceOrThrow(devices[0].connectId
|
|
1725
|
+
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2553
1726
|
}
|
|
2554
|
-
async _connectDeviceOrThrow(chosenConnectId
|
|
2555
|
-
const result = await this.
|
|
1727
|
+
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1728
|
+
const result = await this.connectDevice(chosenConnectId);
|
|
2556
1729
|
if (!result.success) {
|
|
2557
1730
|
const payload = result.payload;
|
|
2558
1731
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -2563,7 +1736,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2563
1736
|
}
|
|
2564
1737
|
throw rethrow;
|
|
2565
1738
|
}
|
|
2566
|
-
return
|
|
1739
|
+
return chosenConnectId;
|
|
2567
1740
|
}
|
|
2568
1741
|
/**
|
|
2569
1742
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -2593,145 +1766,33 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2593
1766
|
* lives in one place.
|
|
2594
1767
|
*/
|
|
2595
1768
|
async _callConnector(sessionId, method, params, signal) {
|
|
2596
|
-
this.
|
|
2597
|
-
if (signal?.aborted) throw this._abortReason(signal);
|
|
2598
|
-
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2599
|
-
let promise;
|
|
2600
|
-
try {
|
|
2601
|
-
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2602
|
-
} catch (error) {
|
|
2603
|
-
releaseOperation();
|
|
2604
|
-
throw error;
|
|
2605
|
-
}
|
|
1769
|
+
const promise = this.connector.call(sessionId, method, params);
|
|
2606
1770
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
2607
1771
|
return this._unwrapConnectorResult(result);
|
|
2608
1772
|
}
|
|
2609
|
-
_assertConnectorReady(method) {
|
|
2610
|
-
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2611
|
-
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
_runConnectorTeardown(task) {
|
|
2615
|
-
const previous = this._connectorTeardownTail;
|
|
2616
|
-
let releaseTail = () => void 0;
|
|
2617
|
-
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2618
|
-
releaseTail = resolve;
|
|
2619
|
-
});
|
|
2620
|
-
this._pendingConnectorTeardowns += 1;
|
|
2621
|
-
return (async () => {
|
|
2622
|
-
try {
|
|
2623
|
-
await previous;
|
|
2624
|
-
await this._waitForConnectorOperationsToDrain();
|
|
2625
|
-
await task();
|
|
2626
|
-
} finally {
|
|
2627
|
-
this._pendingConnectorTeardowns -= 1;
|
|
2628
|
-
releaseTail();
|
|
2629
|
-
}
|
|
2630
|
-
})();
|
|
2631
|
-
}
|
|
2632
|
-
_waitForConnectorOperationsToDrain() {
|
|
2633
|
-
if (this._unsettledConnectorOperations.size === 0) {
|
|
2634
|
-
return Promise.resolve();
|
|
2635
|
-
}
|
|
2636
|
-
return new Promise((resolve) => {
|
|
2637
|
-
this._connectorIdleWaiters.add(resolve);
|
|
2638
|
-
});
|
|
2639
|
-
}
|
|
2640
|
-
_retainConnectorOperation(key) {
|
|
2641
|
-
this._unsettledConnectorOperations.set(
|
|
2642
|
-
key,
|
|
2643
|
-
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2644
|
-
);
|
|
2645
|
-
let released = false;
|
|
2646
|
-
return () => {
|
|
2647
|
-
if (released) return;
|
|
2648
|
-
released = true;
|
|
2649
|
-
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2650
|
-
if (remaining > 0) {
|
|
2651
|
-
this._unsettledConnectorOperations.set(key, remaining);
|
|
2652
|
-
} else {
|
|
2653
|
-
this._unsettledConnectorOperations.delete(key);
|
|
2654
|
-
}
|
|
2655
|
-
if (this._unsettledConnectorOperations.size === 0) {
|
|
2656
|
-
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2657
|
-
this._connectorIdleWaiters.clear();
|
|
2658
|
-
}
|
|
2659
|
-
};
|
|
2660
|
-
}
|
|
2661
1773
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
throw createHwkError({
|
|
2665
|
-
code: HardwareErrorCode3.InvalidParams,
|
|
2666
|
-
message: "Conflicting Ledger operation ids",
|
|
2667
|
-
params: {
|
|
2668
|
-
positionalOperationId,
|
|
2669
|
-
commonOperationId: commonParams.operationId
|
|
2670
|
-
}
|
|
2671
|
-
});
|
|
2672
|
-
}
|
|
2673
|
-
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2674
|
-
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2675
|
-
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2676
|
-
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2677
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
2678
|
-
method,
|
|
2679
|
-
connectId: effectiveConnectId || "(empty)",
|
|
2680
|
-
params
|
|
2681
|
-
});
|
|
2682
|
-
const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
|
|
1774
|
+
debugLog("[LedgerAdapter][REQ]", { method, connectId: connectId || "(empty)", params });
|
|
1775
|
+
const queueKey = connectId || "__ledger_default__";
|
|
2683
1776
|
try {
|
|
2684
1777
|
const result = await this._jobQueue.enqueue(
|
|
2685
1778
|
queueKey,
|
|
2686
|
-
async (signal) =>
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
commonParams,
|
|
2697
|
-
installContext ?? {},
|
|
2698
|
-
operationId
|
|
2699
|
-
);
|
|
2700
|
-
} catch (error) {
|
|
2701
|
-
const status = signal.aborted ? "cancelled" : "failed";
|
|
2702
|
-
const bundleConnectId = installContext?.connection?.connectId;
|
|
2703
|
-
let keptBundlePick = false;
|
|
2704
|
-
for (const [pickedConnectId, owner] of this._pendingPickBindings) {
|
|
2705
|
-
if (pickedConnectId === bundleConnectId) {
|
|
2706
|
-
keptBundlePick = true;
|
|
2707
|
-
} else {
|
|
2708
|
-
this._pendingPickBindings.delete(pickedConnectId);
|
|
2709
|
-
this._discardPendingBinding(owner, status);
|
|
2710
|
-
await this._dropSession(pickedConnectId, signal);
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2713
|
-
if (!keptBundlePick) this._finishBleBinding(status);
|
|
2714
|
-
throw error;
|
|
2715
|
-
} finally {
|
|
2716
|
-
if (operationId) {
|
|
2717
|
-
this._activeOperationJobs.delete(operationId);
|
|
2718
|
-
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2719
|
-
await this._releaseLostOperationConnection(operationId);
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
},
|
|
1779
|
+
async (signal) => this._runConnectorCall(
|
|
1780
|
+
connectId,
|
|
1781
|
+
method,
|
|
1782
|
+
params,
|
|
1783
|
+
signal,
|
|
1784
|
+
fingerprint,
|
|
1785
|
+
permissionDeviceId,
|
|
1786
|
+
commonParams,
|
|
1787
|
+
installContext
|
|
1788
|
+
),
|
|
2724
1789
|
{
|
|
2725
1790
|
label: method,
|
|
2726
1791
|
rejectIfBusy: true,
|
|
2727
1792
|
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
2728
1793
|
}
|
|
2729
1794
|
);
|
|
2730
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
2731
|
-
method,
|
|
2732
|
-
success: true,
|
|
2733
|
-
payload: redactResultForLog(result)
|
|
2734
|
-
});
|
|
1795
|
+
debugLog("[LedgerAdapter][RES]", { method, success: true, payload: result });
|
|
2735
1796
|
return result;
|
|
2736
1797
|
} catch (err) {
|
|
2737
1798
|
const e = err;
|
|
@@ -2745,22 +1806,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2745
1806
|
}
|
|
2746
1807
|
});
|
|
2747
1808
|
throw err;
|
|
2748
|
-
} finally {
|
|
2749
|
-
releaseOperationRetention?.();
|
|
2750
1809
|
}
|
|
2751
1810
|
}
|
|
2752
|
-
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2753
|
-
_abortReason(signal) {
|
|
2754
|
-
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2755
|
-
}
|
|
2756
1811
|
/**
|
|
2757
1812
|
* Race a promise against an abort signal. On abort, rejects with
|
|
2758
1813
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
2759
1814
|
*/
|
|
2760
1815
|
_abortable(signal, promise) {
|
|
2761
|
-
const getAbortReason = () => this.
|
|
1816
|
+
const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2762
1817
|
if (signal.aborted) {
|
|
2763
|
-
void promise.catch(() => void 0);
|
|
2764
1818
|
return Promise.reject(getAbortReason());
|
|
2765
1819
|
}
|
|
2766
1820
|
return new Promise((resolve, reject) => {
|
|
@@ -2787,15 +1841,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2787
1841
|
}
|
|
2788
1842
|
}
|
|
2789
1843
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
2790
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext
|
|
1844
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2791
1845
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
);
|
|
2798
|
-
}
|
|
1846
|
+
await this._ensureDevicePermission(
|
|
1847
|
+
connectId,
|
|
1848
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
1849
|
+
signal
|
|
1850
|
+
);
|
|
2799
1851
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2800
1852
|
let effectiveParams = params;
|
|
2801
1853
|
if (method === "btcGetPublicKey") {
|
|
@@ -2809,67 +1861,19 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2809
1861
|
effectiveParams = gatedParams;
|
|
2810
1862
|
}
|
|
2811
1863
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2815
|
-
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2816
|
-
(connection) => connection.transport === knownTransport
|
|
2817
|
-
);
|
|
2818
|
-
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2819
|
-
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && selectedBleTarget?.chain === fingerprint.chain && selectedBleTarget.fingerprint === fingerprint.deviceId ? selectedBleTarget.connectId : inputConnectId;
|
|
2820
|
-
const checksWallet = Boolean(
|
|
2821
|
-
fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId
|
|
2822
|
-
);
|
|
2823
|
-
const connectionAttempt = {
|
|
2824
|
-
...commonParams,
|
|
2825
|
-
deferBindingSave: checksWallet
|
|
2826
|
-
};
|
|
2827
|
-
const bundleConnection = installContext?.connection;
|
|
2828
|
-
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2829
|
-
throw createHwkError({
|
|
2830
|
-
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2831
|
-
message: "Ledger all-network connection ended"
|
|
2832
|
-
});
|
|
2833
|
-
}
|
|
2834
|
-
const resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2835
|
-
preferredConnectId,
|
|
1864
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1865
|
+
connectId,
|
|
2836
1866
|
signal,
|
|
2837
|
-
allowUsbEphemeralFallback
|
|
2838
|
-
void 0,
|
|
2839
|
-
connectionAttempt
|
|
1867
|
+
allowUsbEphemeralFallback
|
|
2840
1868
|
);
|
|
2841
|
-
const walletCheck = installContext?.walletCheck;
|
|
2842
|
-
if (walletCheck && !checksWallet && (walletCheck.verified === false || walletCheck.expected && !walletCheck.verified)) {
|
|
2843
|
-
throw createHwkError({
|
|
2844
|
-
code: HardwareErrorCode3.DeviceMismatch,
|
|
2845
|
-
message: "Ledger wallet is not verified on this connection; refusing to derive an unverifiable address"
|
|
2846
|
-
});
|
|
2847
|
-
}
|
|
2848
1869
|
const sessionId = this._sessions.get(resolvedConnectId);
|
|
2849
|
-
if (sessionId && installContext && !installContext.connection) {
|
|
2850
|
-
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2851
|
-
}
|
|
2852
1870
|
if (!sessionId) {
|
|
2853
|
-
if (operationId) {
|
|
2854
|
-
this._operations.end(operationId, "disconnect");
|
|
2855
|
-
throw createHwkError({
|
|
2856
|
-
code: HardwareErrorCode3.OperationEnded,
|
|
2857
|
-
message: "Ledger operation connection is no longer active",
|
|
2858
|
-
params: { operationId, reason: "disconnect" }
|
|
2859
|
-
});
|
|
2860
|
-
}
|
|
2861
1871
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
2862
1872
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
2863
1873
|
});
|
|
2864
1874
|
}
|
|
2865
1875
|
try {
|
|
2866
|
-
|
|
2867
|
-
if (!operationId && connectionAttempt.pendingBinding) {
|
|
2868
|
-
this._pendingPickBindings.set(resolvedConnectId, connectionAttempt);
|
|
2869
|
-
}
|
|
2870
|
-
const bindingOwner = operationId ? this._pendingOperationBindings.get(operationId) : this._pendingPickBindings.get(resolvedConnectId);
|
|
2871
|
-
const pendingBinding = bindingOwner?.pendingBinding?.connectId === resolvedConnectId ? bindingOwner.pendingBinding : void 0;
|
|
2872
|
-
if (fingerprint && checksWallet) {
|
|
1876
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2873
1877
|
const fp = await this._abortable(
|
|
2874
1878
|
signal,
|
|
2875
1879
|
this._verifyDeviceFingerprintWithSession(
|
|
@@ -2879,68 +1883,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2879
1883
|
)
|
|
2880
1884
|
);
|
|
2881
1885
|
if (!fp.success) {
|
|
2882
|
-
if (pendingBinding) {
|
|
2883
|
-
this._discardPendingBinding(bindingOwner, "failed");
|
|
2884
|
-
this._pendingPickBindings.delete(resolvedConnectId);
|
|
2885
|
-
}
|
|
2886
|
-
if (operationId) {
|
|
2887
|
-
this._pendingOperationBindings.delete(operationId);
|
|
2888
|
-
this._operations.end(operationId, "explicit");
|
|
2889
|
-
await this._dropSession(resolvedConnectId, signal);
|
|
2890
|
-
}
|
|
2891
1886
|
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2892
1887
|
code: HardwareErrorCode3.DeviceMismatch
|
|
2893
1888
|
});
|
|
2894
1889
|
}
|
|
2895
|
-
if (walletCheck) walletCheck.verified = true;
|
|
2896
|
-
}
|
|
2897
|
-
if (pendingBinding && bindingOwner) {
|
|
2898
|
-
bindingOwner.pendingBinding = void 0;
|
|
2899
|
-
if (operationId) this._pendingOperationBindings.delete(operationId);
|
|
2900
|
-
else this._pendingPickBindings.delete(resolvedConnectId);
|
|
2901
|
-
await this._saveSelectedBleBinding(
|
|
2902
|
-
pendingBinding.connectId,
|
|
2903
|
-
pendingBinding.requestId,
|
|
2904
|
-
bindingOwner,
|
|
2905
|
-
operationId,
|
|
2906
|
-
signal,
|
|
2907
|
-
fingerprint && checksWallet ? {
|
|
2908
|
-
vendor: "ledger",
|
|
2909
|
-
type: "chainFingerprint",
|
|
2910
|
-
chain: fingerprint.chain,
|
|
2911
|
-
value: fingerprint.deviceId
|
|
2912
|
-
} : void 0
|
|
2913
|
-
);
|
|
2914
|
-
}
|
|
2915
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId && !operationId && isBleSession && connectionAttempt.selectedConnection?.connectId === resolvedConnectId) {
|
|
2916
|
-
this._selectedBleReconnectTargets.set(connectId, {
|
|
2917
|
-
connectId: resolvedConnectId,
|
|
2918
|
-
chain: fingerprint.chain,
|
|
2919
|
-
fingerprint: fingerprint.deviceId
|
|
2920
|
-
});
|
|
2921
1890
|
}
|
|
2922
|
-
|
|
2923
|
-
const result = await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2924
|
-
if (walletCheck) walletCheck.derived = true;
|
|
2925
|
-
return result;
|
|
1891
|
+
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2926
1892
|
} catch (err) {
|
|
2927
1893
|
if (signal.aborted) throw err;
|
|
2928
|
-
if (isLostConnectionError(err)) {
|
|
2929
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2930
|
-
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2931
|
-
else {
|
|
2932
|
-
this._sessions.delete(resolvedConnectId);
|
|
2933
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2934
|
-
}
|
|
2935
|
-
const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2936
|
-
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2937
|
-
throw createHwkError({
|
|
2938
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(err).code,
|
|
2939
|
-
message: "Ledger operation connection was lost; start a new operation",
|
|
2940
|
-
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId }) : interactionParams,
|
|
2941
|
-
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2942
|
-
});
|
|
2943
|
-
}
|
|
2944
1894
|
const errObj = err;
|
|
2945
1895
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
2946
1896
|
message: errObj?.message,
|
|
@@ -2952,64 +1902,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2952
1902
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
2953
1903
|
isStuckApp: isStuckAppStateError(err)
|
|
2954
1904
|
});
|
|
2955
|
-
const assertSessionCurrent = (message) => {
|
|
2956
|
-
if (this._sessions.get(resolvedConnectId) === sessionId) return;
|
|
2957
|
-
throw createHwkError({
|
|
2958
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2959
|
-
message
|
|
2960
|
-
});
|
|
2961
|
-
};
|
|
2962
|
-
if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2963
|
-
await this._waitForDeviceConnect(signal);
|
|
2964
|
-
assertSessionCurrent("Ledger connection ended while waiting for unlock");
|
|
2965
|
-
if (walletCheck?.derived) walletCheck.verified = false;
|
|
2966
|
-
return this._runConnectorCall(
|
|
2967
|
-
resolvedConnectId,
|
|
2968
|
-
method,
|
|
2969
|
-
effectiveParams,
|
|
2970
|
-
signal,
|
|
2971
|
-
fingerprint,
|
|
2972
|
-
permissionDeviceId,
|
|
2973
|
-
commonParams,
|
|
2974
|
-
installContext,
|
|
2975
|
-
operationId,
|
|
2976
|
-
lockedRetryBudget - 1
|
|
2977
|
-
);
|
|
2978
|
-
}
|
|
2979
|
-
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2980
|
-
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2981
|
-
assertSessionCurrent("Ledger connection ended during the app transition");
|
|
2982
|
-
try {
|
|
2983
|
-
const retried = await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2984
|
-
if (walletCheck) walletCheck.derived = true;
|
|
2985
|
-
return retried;
|
|
2986
|
-
} catch (retryErr) {
|
|
2987
|
-
if (isStuckAppStateError(retryErr)) throw err;
|
|
2988
|
-
if (isLostConnectionError(retryErr)) {
|
|
2989
|
-
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2990
|
-
else {
|
|
2991
|
-
this._sessions.delete(resolvedConnectId);
|
|
2992
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
2993
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2994
|
-
}
|
|
2995
|
-
const ambiguous = !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2996
|
-
throw createHwkError({
|
|
2997
|
-
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(retryErr).code,
|
|
2998
|
-
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2999
|
-
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId, reason: "disconnect" }) : { operationId, reason: "disconnect" },
|
|
3000
|
-
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
3001
|
-
});
|
|
3002
|
-
}
|
|
3003
|
-
throw retryErr;
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3006
|
-
if (!operationId && errObj?.code === HardwareErrorCode3.DeviceMismatch) {
|
|
3007
|
-
this._sessions.delete(resolvedConnectId);
|
|
3008
|
-
this._sessionTransports.delete(sessionId);
|
|
3009
|
-
this._discoveredDevices.delete(resolvedConnectId);
|
|
3010
|
-
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
3011
|
-
throw err;
|
|
3012
|
-
}
|
|
3013
1905
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
3014
1906
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
|
|
3015
1907
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -3065,14 +1957,157 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3065
1957
|
fingerprint,
|
|
3066
1958
|
permissionDeviceId,
|
|
3067
1959
|
commonParams,
|
|
3068
|
-
installContext
|
|
3069
|
-
|
|
1960
|
+
installContext
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
if (isStuckAppStateError(err)) {
|
|
1965
|
+
try {
|
|
1966
|
+
this._sessions.delete(resolvedConnectId);
|
|
1967
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
1968
|
+
this.connector.reset?.();
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1971
|
+
debugLog(
|
|
1972
|
+
"[LedgerAdapter] stuck-app retry: method=",
|
|
1973
|
+
method,
|
|
1974
|
+
"delayMs=",
|
|
1975
|
+
_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS,
|
|
1976
|
+
"_tag=",
|
|
1977
|
+
err?._tag
|
|
1978
|
+
);
|
|
1979
|
+
try {
|
|
1980
|
+
const retryResult = await this._retryAfterStuckApp(
|
|
1981
|
+
resolvedConnectId,
|
|
1982
|
+
method,
|
|
1983
|
+
effectiveParams,
|
|
1984
|
+
signal,
|
|
1985
|
+
err,
|
|
1986
|
+
fingerprint
|
|
1987
|
+
);
|
|
1988
|
+
debugLog("[LedgerAdapter] stuck-app retry succeeded: method=", method);
|
|
1989
|
+
return retryResult;
|
|
1990
|
+
} catch (retryErr) {
|
|
1991
|
+
if (signal.aborted) throw retryErr;
|
|
1992
|
+
if (isStuckAppStateError(retryErr)) {
|
|
1993
|
+
debugLog("[LedgerAdapter] stuck-app retry exhausted (2nd 6901): method=", method);
|
|
1994
|
+
throw err;
|
|
1995
|
+
}
|
|
1996
|
+
debugLog(
|
|
1997
|
+
"[LedgerAdapter] stuck-app retry threw non-stuck error: method=",
|
|
1998
|
+
method,
|
|
1999
|
+
"retryErrTag=",
|
|
2000
|
+
retryErr?._tag
|
|
3070
2001
|
);
|
|
2002
|
+
throw retryErr;
|
|
3071
2003
|
}
|
|
3072
2004
|
}
|
|
2005
|
+
if (isDeviceLockedError(err) || isDeviceNotAdvertisingError(err) || isDeviceDisconnectedError(err)) {
|
|
2006
|
+
let lastErr = err;
|
|
2007
|
+
for (let attempt = 0; attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET; attempt += 1) {
|
|
2008
|
+
if (signal.aborted) throw lastErr;
|
|
2009
|
+
try {
|
|
2010
|
+
this._sessions.delete(resolvedConnectId);
|
|
2011
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2012
|
+
if (isDeviceDisconnectedError(lastErr)) {
|
|
2013
|
+
try {
|
|
2014
|
+
this.connector.reset?.();
|
|
2015
|
+
} catch {
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
const reConnectId = await this.ensureConnected(
|
|
2019
|
+
resolvedConnectId,
|
|
2020
|
+
signal,
|
|
2021
|
+
allowUsbEphemeralFallback
|
|
2022
|
+
);
|
|
2023
|
+
const reSessionId = this._sessions.get(reConnectId);
|
|
2024
|
+
if (!reSessionId) throw lastErr;
|
|
2025
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2026
|
+
const fp = await this._abortable(
|
|
2027
|
+
signal,
|
|
2028
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2029
|
+
reSessionId,
|
|
2030
|
+
fingerprint.deviceId,
|
|
2031
|
+
fingerprint.chain
|
|
2032
|
+
)
|
|
2033
|
+
);
|
|
2034
|
+
if (!fp.success) {
|
|
2035
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2036
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
return await this._callConnector(reSessionId, method, effectiveParams, signal);
|
|
2041
|
+
} catch (retryErr) {
|
|
2042
|
+
if (signal.aborted) throw retryErr;
|
|
2043
|
+
lastErr = retryErr;
|
|
2044
|
+
const canRetry = attempt < _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET - 1 && (isDeviceLockedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isDeviceDisconnectedError(retryErr));
|
|
2045
|
+
if (!canRetry) {
|
|
2046
|
+
throw retryErr;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
throw lastErr;
|
|
2051
|
+
}
|
|
2052
|
+
if (isTimeoutError(err)) {
|
|
2053
|
+
debugLog("[LedgerAdapter] timeout, retrying with fresh connection...");
|
|
2054
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2055
|
+
return this._retryWithFreshConnection(
|
|
2056
|
+
resolvedConnectId,
|
|
2057
|
+
method,
|
|
2058
|
+
effectiveParams,
|
|
2059
|
+
signal,
|
|
2060
|
+
err,
|
|
2061
|
+
fingerprint
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
if (isConnectionLevelError(err)) {
|
|
2065
|
+
debugLog("[LedgerAdapter] connection-level fail-closed reset");
|
|
2066
|
+
this._sessions.delete(resolvedConnectId);
|
|
2067
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2068
|
+
this.connector.reset();
|
|
2069
|
+
const codeNum = err?.code;
|
|
2070
|
+
throw Object.assign(err, {
|
|
2071
|
+
code: codeNum ?? HardwareErrorCode3.DeviceDisconnected
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
3073
2074
|
throw err;
|
|
3074
2075
|
}
|
|
3075
2076
|
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Stuck-app recovery: pause for the device's UI transition, then retry once.
|
|
2079
|
+
*
|
|
2080
|
+
* Caller has already cleared the session + reset connector. We wait so Stax
|
|
2081
|
+
* finishes its post-CloseApp animation, then go through ensureConnected +
|
|
2082
|
+
* fingerprint check + call exactly once. Caller decides what to do on a
|
|
2083
|
+
* second stuck-app hit.
|
|
2084
|
+
*/
|
|
2085
|
+
async _retryAfterStuckApp(resolvedConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2086
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2087
|
+
const retryConnectId = await this.ensureConnected(
|
|
2088
|
+
resolvedConnectId,
|
|
2089
|
+
signal,
|
|
2090
|
+
!!fingerprint?.deviceId && !fingerprint.skipFingerprint
|
|
2091
|
+
);
|
|
2092
|
+
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2093
|
+
if (!retrySessionId) throw originalErr;
|
|
2094
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2095
|
+
const fp = await this._abortable(
|
|
2096
|
+
signal,
|
|
2097
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2098
|
+
retrySessionId,
|
|
2099
|
+
fingerprint.deviceId,
|
|
2100
|
+
fingerprint.chain
|
|
2101
|
+
)
|
|
2102
|
+
);
|
|
2103
|
+
if (!fp.success) {
|
|
2104
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2105
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return this._callConnector(retrySessionId, method, params, signal);
|
|
2110
|
+
}
|
|
3076
2111
|
_sleepAbortable(ms, signal) {
|
|
3077
2112
|
return new Promise((resolve, reject) => {
|
|
3078
2113
|
if (signal.aborted) {
|
|
@@ -3091,22 +2126,111 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3091
2126
|
});
|
|
3092
2127
|
}
|
|
3093
2128
|
/**
|
|
3094
|
-
*
|
|
3095
|
-
*
|
|
2129
|
+
* Clear stale session, reconnect, and retry the call.
|
|
2130
|
+
*
|
|
2131
|
+
* Timeout recovery starts with a full connector reset. After an APDU
|
|
2132
|
+
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
2133
|
+
* on the same DMK can poison the next chain switch.
|
|
2134
|
+
*/
|
|
2135
|
+
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2136
|
+
this.connector.reset();
|
|
2137
|
+
this._sessions.clear();
|
|
2138
|
+
this._discoveredDevices.clear();
|
|
2139
|
+
this._connectingPromise = null;
|
|
2140
|
+
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2141
|
+
const retryConnectId = await this.ensureConnected(
|
|
2142
|
+
targetConnectId,
|
|
2143
|
+
signal,
|
|
2144
|
+
allowUsbEphemeralFallback
|
|
2145
|
+
);
|
|
2146
|
+
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2147
|
+
if (!retrySessionId) {
|
|
2148
|
+
throw originalErr;
|
|
2149
|
+
}
|
|
2150
|
+
try {
|
|
2151
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2152
|
+
const fp = await this._abortable(
|
|
2153
|
+
signal,
|
|
2154
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2155
|
+
retrySessionId,
|
|
2156
|
+
fingerprint.deviceId,
|
|
2157
|
+
fingerprint.chain
|
|
2158
|
+
)
|
|
2159
|
+
);
|
|
2160
|
+
if (!fp.success) {
|
|
2161
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2162
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return await this._callConnector(retrySessionId, method, params, signal);
|
|
2167
|
+
} catch (retryErr) {
|
|
2168
|
+
if (signal.aborted) throw retryErr;
|
|
2169
|
+
this.connector.reset();
|
|
2170
|
+
this._sessions.clear();
|
|
2171
|
+
this._discoveredDevices.clear();
|
|
2172
|
+
this._connectingPromise = null;
|
|
2173
|
+
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
2174
|
+
throw retryErr;
|
|
2175
|
+
}
|
|
2176
|
+
debugLog(
|
|
2177
|
+
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
2178
|
+
);
|
|
2179
|
+
this.connector.reset();
|
|
2180
|
+
this._sessions.clear();
|
|
2181
|
+
this._discoveredDevices.clear();
|
|
2182
|
+
this._connectingPromise = null;
|
|
2183
|
+
const finalConnectId = await this.ensureConnected(
|
|
2184
|
+
targetConnectId,
|
|
2185
|
+
signal,
|
|
2186
|
+
allowUsbEphemeralFallback
|
|
2187
|
+
);
|
|
2188
|
+
const finalSessionId = this._sessions.get(finalConnectId);
|
|
2189
|
+
if (!finalSessionId) {
|
|
2190
|
+
throw originalErr;
|
|
2191
|
+
}
|
|
2192
|
+
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2193
|
+
const fp = await this._abortable(
|
|
2194
|
+
signal,
|
|
2195
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2196
|
+
finalSessionId,
|
|
2197
|
+
fingerprint.deviceId,
|
|
2198
|
+
fingerprint.chain
|
|
2199
|
+
)
|
|
2200
|
+
);
|
|
2201
|
+
if (!fp.success) {
|
|
2202
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2203
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return this._callConnector(finalSessionId, method, params, signal);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
2212
|
+
*
|
|
2213
|
+
* Emits `REQUEST_DEVICE_PERMISSION` and awaits the consumer's
|
|
2214
|
+
* `RECEIVE_DEVICE_PERMISSION` reply (60s budget covers "probe → system
|
|
2215
|
+
* prompt → user tap" plus a generous margin). If the consumer never wires
|
|
2216
|
+
* a handler or never replies, the wait times out and the operation fails
|
|
2217
|
+
* fast so scanners/callers don't hang silently.
|
|
2218
|
+
*
|
|
2219
|
+
* - No connectId (searchDevices): environment-level permission
|
|
2220
|
+
* - With connectId (business methods): device-level permission
|
|
3096
2221
|
*/
|
|
3097
2222
|
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
3098
2223
|
if (signal?.aborted) {
|
|
3099
2224
|
_LedgerAdapter._throwIfAborted(signal);
|
|
3100
2225
|
}
|
|
3101
2226
|
const transportType = this.activeTransport ?? "hid";
|
|
3102
|
-
const operationId = this._activeOperationId();
|
|
3103
2227
|
const waitPromise = this._uiRegistry.wait(
|
|
3104
2228
|
UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3105
|
-
{ timeoutMs: 6e4
|
|
2229
|
+
{ timeoutMs: 6e4 }
|
|
3106
2230
|
);
|
|
3107
2231
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
3108
2232
|
type: UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
3109
|
-
payload: { transportType, connectId, deviceId
|
|
2233
|
+
payload: { transportType, connectId, deviceId }
|
|
3110
2234
|
});
|
|
3111
2235
|
let response;
|
|
3112
2236
|
const onAbort = () => {
|
|
@@ -3152,15 +2276,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3152
2276
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
3153
2277
|
const e = err;
|
|
3154
2278
|
const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
3155
|
-
return ledgerFailure(
|
|
3156
|
-
e.code,
|
|
3157
|
-
e.message ?? "Unknown error",
|
|
3158
|
-
e.appName,
|
|
3159
|
-
tag,
|
|
3160
|
-
params,
|
|
3161
|
-
void 0,
|
|
3162
|
-
isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
|
|
3163
|
-
);
|
|
2279
|
+
return ledgerFailure(e.code, e.message ?? "Unknown error", e.appName, tag, params);
|
|
3164
2280
|
}
|
|
3165
2281
|
const mapped = mapLedgerError(err);
|
|
3166
2282
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -3187,7 +2303,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
3187
2303
|
deviceId: device.deviceId,
|
|
3188
2304
|
connectId: device.connectId,
|
|
3189
2305
|
label: device.name,
|
|
3190
|
-
connectionType:
|
|
2306
|
+
connectionType: this.connector.connectionType,
|
|
3191
2307
|
rssi: device.rssi,
|
|
3192
2308
|
isConnectable: device.isConnectable,
|
|
3193
2309
|
serialNumber: device.serialNumber,
|
|
@@ -3213,8 +2329,8 @@ var LedgerAdapter = _LedgerAdapter;
|
|
|
3213
2329
|
|
|
3214
2330
|
// src/connector/LedgerConnectorBase.ts
|
|
3215
2331
|
import {
|
|
3216
|
-
EConnectorInteraction as
|
|
3217
|
-
HardwareErrorCode as
|
|
2332
|
+
EConnectorInteraction as EConnectorInteraction4,
|
|
2333
|
+
HardwareErrorCode as HardwareErrorCode8,
|
|
3218
2334
|
isKnownNonTargetHardwareVendor,
|
|
3219
2335
|
serializeConnectorError
|
|
3220
2336
|
} from "@onekeyfe/hwk-adapter-core";
|
|
@@ -3487,7 +2603,7 @@ var LedgerDeviceManager = class {
|
|
|
3487
2603
|
|
|
3488
2604
|
// src/signer/SignerManager.ts
|
|
3489
2605
|
import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
|
|
3490
|
-
import { ContextModuleBuilder
|
|
2606
|
+
import { ContextModuleBuilder } from "@ledgerhq/context-module";
|
|
3491
2607
|
|
|
3492
2608
|
// src/signer/SignerEth.ts
|
|
3493
2609
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
@@ -3753,7 +2869,7 @@ var SignerManager = class _SignerManager {
|
|
|
3753
2869
|
return (args) => new SignerEthBuilder(args);
|
|
3754
2870
|
}
|
|
3755
2871
|
static _createContextModule() {
|
|
3756
|
-
const contextModule = new ContextModuleBuilder({}).
|
|
2872
|
+
const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3757
2873
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
3758
2874
|
}
|
|
3759
2875
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -3783,33 +2899,12 @@ function collapseSignerInteraction(interaction) {
|
|
|
3783
2899
|
switch (interaction) {
|
|
3784
2900
|
case "confirm-open-app":
|
|
3785
2901
|
return EConnectorInteraction2.ConfirmOpenApp;
|
|
3786
|
-
case "unlock-device":
|
|
3787
|
-
return EConnectorInteraction2.UnlockDevice;
|
|
3788
|
-
case "interaction-complete":
|
|
3789
|
-
return EConnectorInteraction2.InteractionComplete;
|
|
3790
|
-
default:
|
|
3791
|
-
return EConnectorInteraction2.ConfirmOnDevice;
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
function wireSignerToSession(ctx, sessionId, chain, signer) {
|
|
3795
|
-
signer.onInteraction = (interaction) => {
|
|
3796
|
-
debugLog(`[LedgerConnector] ${chain}.onInteraction:`, interaction);
|
|
3797
|
-
ctx.emit("ui-event", {
|
|
3798
|
-
type: collapseSignerInteraction(interaction),
|
|
3799
|
-
payload: { sessionId }
|
|
3800
|
-
});
|
|
3801
|
-
};
|
|
3802
|
-
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
3803
|
-
return signer;
|
|
3804
|
-
}
|
|
3805
|
-
async function runSignerCall(ctx, sessionId, call) {
|
|
3806
|
-
try {
|
|
3807
|
-
return await call();
|
|
3808
|
-
} catch (err) {
|
|
3809
|
-
ctx.invalidateSession(sessionId);
|
|
3810
|
-
throw ctx.wrapError(err);
|
|
3811
|
-
} finally {
|
|
3812
|
-
ctx.clearCanceller(sessionId);
|
|
2902
|
+
case "unlock-device":
|
|
2903
|
+
return EConnectorInteraction2.UnlockDevice;
|
|
2904
|
+
case "interaction-complete":
|
|
2905
|
+
return EConnectorInteraction2.InteractionComplete;
|
|
2906
|
+
default:
|
|
2907
|
+
return EConnectorInteraction2.ConfirmOnDevice;
|
|
3813
2908
|
}
|
|
3814
2909
|
}
|
|
3815
2910
|
|
|
@@ -4036,21 +3131,7 @@ function _purposeToTemplate(purpose, DDT) {
|
|
|
4036
3131
|
});
|
|
4037
3132
|
}
|
|
4038
3133
|
}
|
|
4039
|
-
function assertBitcoinMainnet(coin) {
|
|
4040
|
-
if (coin !== void 0 && !["btc", "bitcoin"].includes(coin.toLowerCase())) {
|
|
4041
|
-
throw Object.assign(new Error(`Ledger BTC supports Bitcoin mainnet only, not ${coin}`), {
|
|
4042
|
-
code: HardwareErrorCode5.ChainNotSupported
|
|
4043
|
-
});
|
|
4044
|
-
}
|
|
4045
|
-
}
|
|
4046
3134
|
async function btcGetAddress(ctx, sessionId, params) {
|
|
4047
|
-
assertBitcoinMainnet(params.coin);
|
|
4048
|
-
if (normalizePath(params.path).split("/").length !== 3) {
|
|
4049
|
-
throw Object.assign(
|
|
4050
|
-
new Error("Ledger btcGetAddress takes an account path (e.g. m/84'/0'/0') plus addressIndex"),
|
|
4051
|
-
{ code: HardwareErrorCode5.InvalidParams }
|
|
4052
|
-
);
|
|
4053
|
-
}
|
|
4054
3135
|
const btcSigner = await _createBtcSigner(ctx, sessionId);
|
|
4055
3136
|
const path = normalizePath(params.path);
|
|
4056
3137
|
try {
|
|
@@ -4081,7 +3162,6 @@ async function btcGetAddress(ctx, sessionId, params) {
|
|
|
4081
3162
|
}
|
|
4082
3163
|
}
|
|
4083
3164
|
async function btcGetPublicKey(ctx, sessionId, params) {
|
|
4084
|
-
assertBitcoinMainnet(params.coin);
|
|
4085
3165
|
const btcSigner = await _createBtcSigner(ctx, sessionId);
|
|
4086
3166
|
const path = normalizePath(params.path);
|
|
4087
3167
|
debugLog("[LedgerConnector] btcGetPublicKey called, path:", path, "sessionId:", sessionId);
|
|
@@ -4259,8 +3339,7 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
4259
3339
|
}
|
|
4260
3340
|
|
|
4261
3341
|
// src/connector/chains/sol.ts
|
|
4262
|
-
import { bytesToHex, hexToBytes as hexToBytes3
|
|
4263
|
-
import bs58 from "bs58";
|
|
3342
|
+
import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
|
|
4264
3343
|
|
|
4265
3344
|
// src/signer/SignerSol.ts
|
|
4266
3345
|
var SignerSol = class {
|
|
@@ -4344,21 +3423,8 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
4344
3423
|
const path = normalizePath(params.path);
|
|
4345
3424
|
const messageBytes = hexToBytes3(params.message);
|
|
4346
3425
|
try {
|
|
4347
|
-
if (params.messageVersion === 1) {
|
|
4348
|
-
const preparedMessage = prepareSolanaOffchainMessageV1({
|
|
4349
|
-
message: messageBytes,
|
|
4350
|
-
requiredSigners: params.requiredSigners
|
|
4351
|
-
});
|
|
4352
|
-
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4353
|
-
"@ledgerhq/device-signer-kit-solana"
|
|
4354
|
-
);
|
|
4355
|
-
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4356
|
-
version: SignMessageVersion.Raw
|
|
4357
|
-
});
|
|
4358
|
-
return { signature: decodeBase58Signature(result2.signature) };
|
|
4359
|
-
}
|
|
4360
3426
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
4361
|
-
return { signature:
|
|
3427
|
+
return { signature: result.signature };
|
|
4362
3428
|
} catch (err) {
|
|
4363
3429
|
ctx.invalidateSession(sessionId);
|
|
4364
3430
|
throw ctx.wrapError(err);
|
|
@@ -4366,27 +3432,11 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
4366
3432
|
ctx.clearCanceller(sessionId);
|
|
4367
3433
|
}
|
|
4368
3434
|
}
|
|
4369
|
-
function decodeBase58Signature(signature) {
|
|
4370
|
-
const bytes = bs58.decode(signature);
|
|
4371
|
-
if (bytes.length !== 64) {
|
|
4372
|
-
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4373
|
-
}
|
|
4374
|
-
return bytesToHex(bytes);
|
|
4375
|
-
}
|
|
4376
|
-
function decodeBase58EnvelopeSignature(envelope) {
|
|
4377
|
-
const bytes = bs58.decode(envelope);
|
|
4378
|
-
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4379
|
-
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4380
|
-
}
|
|
4381
|
-
return bytesToHex(bytes.subarray(1, 65));
|
|
4382
|
-
}
|
|
4383
3435
|
async function _createSolSigner(ctx, sessionId) {
|
|
4384
3436
|
const dmk = await ctx.getOrCreateDmk();
|
|
4385
|
-
const { ContextModuleBuilder: ContextModuleBuilder2
|
|
4386
|
-
"@ledgerhq/context-module"
|
|
4387
|
-
);
|
|
3437
|
+
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit("@ledgerhq/context-module");
|
|
4388
3438
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
4389
|
-
const contextModule = new ContextModuleBuilder2({}).
|
|
3439
|
+
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4390
3440
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
4391
3441
|
const signer = new SignerSol(sdkSigner);
|
|
4392
3442
|
signer.onInteraction = (interaction) => {
|
|
@@ -4402,147 +3452,361 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
4402
3452
|
return signer;
|
|
4403
3453
|
}
|
|
4404
3454
|
|
|
4405
|
-
// src/connector/chains/
|
|
4406
|
-
import {
|
|
3455
|
+
// src/connector/chains/tron.ts
|
|
3456
|
+
import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
|
|
3457
|
+
import Trx from "@ledgerhq/hw-app-trx";
|
|
4407
3458
|
|
|
4408
|
-
// src/
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
3459
|
+
// src/connector/chains/legacyChainCall.ts
|
|
3460
|
+
import { EConnectorInteraction as EConnectorInteraction3 } from "@onekeyfe/hwk-adapter-core";
|
|
3461
|
+
|
|
3462
|
+
// src/app/AppManager.ts
|
|
3463
|
+
import {
|
|
3464
|
+
CloseAppCommand,
|
|
3465
|
+
GetAppAndVersionCommand,
|
|
3466
|
+
OpenAppCommand,
|
|
3467
|
+
isSuccessCommandResult
|
|
3468
|
+
} from "@ledgerhq/device-management-kit";
|
|
3469
|
+
import { HardwareErrorCode as HardwareErrorCode6 } from "@onekeyfe/hwk-adapter-core";
|
|
3470
|
+
var APP_NAME_MAP = {
|
|
3471
|
+
ETH: "Ethereum",
|
|
3472
|
+
BTC: "Bitcoin",
|
|
3473
|
+
SOL: "Solana",
|
|
3474
|
+
TRX: "Tron",
|
|
3475
|
+
XRP: "XRP",
|
|
3476
|
+
ADA: "Cardano",
|
|
3477
|
+
DOT: "Polkadot",
|
|
3478
|
+
ATOM: "Cosmos"
|
|
3479
|
+
};
|
|
3480
|
+
var DASHBOARD_APP_NAME = "BOLOS";
|
|
3481
|
+
var AppManager = class {
|
|
3482
|
+
constructor(dmk, options) {
|
|
3483
|
+
this._dmk = dmk;
|
|
3484
|
+
this._waitMs = options?.waitMs ?? 1e3;
|
|
3485
|
+
this._maxRetries = options?.maxRetries ?? 10;
|
|
4413
3486
|
}
|
|
4414
|
-
/**
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
3487
|
+
/**
|
|
3488
|
+
* Return the Ledger app name for a given chain ticker,
|
|
3489
|
+
* or undefined if the chain is not supported.
|
|
3490
|
+
*/
|
|
3491
|
+
static getAppName(chain) {
|
|
3492
|
+
return APP_NAME_MAP[chain];
|
|
3493
|
+
}
|
|
3494
|
+
/**
|
|
3495
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
3496
|
+
*
|
|
3497
|
+
* Flow:
|
|
3498
|
+
* 1. Check the currently running app.
|
|
3499
|
+
* 2. If it is already the target, return immediately.
|
|
3500
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
3501
|
+
* 4. Open the target app.
|
|
3502
|
+
* 5. Poll until the device confirms the target app is running.
|
|
3503
|
+
*/
|
|
3504
|
+
/**
|
|
3505
|
+
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
3506
|
+
* device is about to display "Open <app>" on screen and wait for the
|
|
3507
|
+
* user's button press. UI consumers should show their "open app" prompt
|
|
3508
|
+
* in response. NOT called when the target app is already open (no user
|
|
3509
|
+
* interaction needed in that case).
|
|
3510
|
+
*
|
|
3511
|
+
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
3512
|
+
* has physically confirmed on the device, so anything that runs AFTER
|
|
3513
|
+
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
3514
|
+
* Hence the callback must fire BEFORE that await.
|
|
3515
|
+
*/
|
|
3516
|
+
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
3517
|
+
const currentApp = await this._getCurrentApp(sessionId);
|
|
3518
|
+
if (currentApp === targetAppName) {
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3521
|
+
if (!this._isDashboard(currentApp)) {
|
|
3522
|
+
await this._closeCurrentApp(sessionId);
|
|
3523
|
+
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
3524
|
+
}
|
|
3525
|
+
onConfirmOnDevice?.();
|
|
3526
|
+
await this._openApp(sessionId, targetAppName);
|
|
3527
|
+
await this._waitForApp(sessionId, targetAppName);
|
|
3528
|
+
}
|
|
3529
|
+
// ---------------------------------------------------------------------------
|
|
3530
|
+
// Private helpers
|
|
3531
|
+
// ---------------------------------------------------------------------------
|
|
3532
|
+
async _getCurrentApp(sessionId) {
|
|
3533
|
+
const result = await this._dmk.sendCommand({
|
|
3534
|
+
sessionId,
|
|
3535
|
+
command: new GetAppAndVersionCommand()
|
|
3536
|
+
});
|
|
3537
|
+
if (isSuccessCommandResult(result)) {
|
|
3538
|
+
debugLog("[AppManager] currentApp:", result.data.name);
|
|
3539
|
+
return result.data.name;
|
|
3540
|
+
}
|
|
3541
|
+
const errResult = result;
|
|
3542
|
+
const dmkErr = errResult.error ?? {};
|
|
3543
|
+
const original = dmkErr.originalError;
|
|
3544
|
+
debugLog(
|
|
3545
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
3546
|
+
sessionId,
|
|
3547
|
+
"tag=",
|
|
3548
|
+
dmkErr._tag,
|
|
3549
|
+
"errorCode=",
|
|
3550
|
+
dmkErr.errorCode,
|
|
3551
|
+
"message=",
|
|
3552
|
+
dmkErr.message,
|
|
3553
|
+
"originalErrorMessage=",
|
|
3554
|
+
original?.message ?? String(original ?? "")
|
|
3555
|
+
);
|
|
3556
|
+
throw Object.assign(
|
|
3557
|
+
new Error(
|
|
3558
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
3559
|
+
),
|
|
3560
|
+
{
|
|
3561
|
+
_tag: dmkErr._tag,
|
|
3562
|
+
errorCode: dmkErr.errorCode,
|
|
3563
|
+
originalError: original
|
|
3564
|
+
}
|
|
4422
3565
|
);
|
|
4423
3566
|
}
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
3567
|
+
async _openApp(sessionId, appName) {
|
|
3568
|
+
const result = await this._dmk.sendCommand({
|
|
3569
|
+
sessionId,
|
|
3570
|
+
command: new OpenAppCommand({ appName })
|
|
3571
|
+
});
|
|
3572
|
+
if (!isSuccessCommandResult(result)) {
|
|
3573
|
+
const dmkErr = result.error;
|
|
3574
|
+
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
3575
|
+
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
3576
|
+
debugLog(
|
|
3577
|
+
"[AppManager] openApp failed:",
|
|
3578
|
+
appName,
|
|
3579
|
+
"errorCode:",
|
|
3580
|
+
errorCode,
|
|
3581
|
+
"tag:",
|
|
3582
|
+
dmkErr._tag
|
|
3583
|
+
);
|
|
3584
|
+
let code;
|
|
3585
|
+
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
3586
|
+
code = HardwareErrorCode6.AppNotInstalled;
|
|
3587
|
+
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
3588
|
+
code = HardwareErrorCode6.UserRejected;
|
|
3589
|
+
}
|
|
3590
|
+
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
3591
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
3592
|
+
code,
|
|
3593
|
+
errorCode,
|
|
3594
|
+
statusCode: errorCode,
|
|
3595
|
+
appName,
|
|
3596
|
+
originalError: dmkErr
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
async _closeCurrentApp(sessionId) {
|
|
3601
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
3602
|
+
await this._dmk.sendCommand({
|
|
3603
|
+
sessionId,
|
|
3604
|
+
command: new CloseAppCommand()
|
|
3605
|
+
});
|
|
3606
|
+
}
|
|
3607
|
+
/**
|
|
3608
|
+
* Poll the device until the expected app is reported as running,
|
|
3609
|
+
* or throw after `_maxRetries` attempts.
|
|
3610
|
+
*/
|
|
3611
|
+
async _waitForApp(sessionId, expectedAppName) {
|
|
3612
|
+
let lastSeen = "";
|
|
3613
|
+
for (let i = 0; i < this._maxRetries; i++) {
|
|
3614
|
+
await this._wait();
|
|
3615
|
+
const current = await this._getCurrentApp(sessionId);
|
|
3616
|
+
lastSeen = current;
|
|
3617
|
+
if (current === expectedAppName) {
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
debugLog(
|
|
3622
|
+
"[AppManager] waitForApp exhausted: expected=",
|
|
3623
|
+
expectedAppName,
|
|
3624
|
+
"lastSeen=",
|
|
3625
|
+
lastSeen
|
|
3626
|
+
);
|
|
3627
|
+
throw new Error(
|
|
3628
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
4432
3629
|
);
|
|
4433
3630
|
}
|
|
3631
|
+
_isDashboard(appName) {
|
|
3632
|
+
return appName === DASHBOARD_APP_NAME;
|
|
3633
|
+
}
|
|
3634
|
+
_wait() {
|
|
3635
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3636
|
+
}
|
|
4434
3637
|
};
|
|
4435
3638
|
|
|
4436
|
-
// src/connector/chains/
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
const path = normalizePath(params.path);
|
|
4440
|
-
const mode = params.mode ?? "ufvk";
|
|
4441
|
-
return runSignerCall(ctx, sessionId, async () => {
|
|
4442
|
-
const result = await signer.getFullViewingKey(path, { mode });
|
|
4443
|
-
if (result.mode === "ufvk") {
|
|
4444
|
-
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
4445
|
-
}
|
|
4446
|
-
return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
|
|
4447
|
-
});
|
|
3639
|
+
// src/connector/chains/legacyChainCall.ts
|
|
3640
|
+
function isLegacyWrongAppError(err, _appName) {
|
|
3641
|
+
return isWrongAppError(err);
|
|
4448
3642
|
}
|
|
4449
|
-
async function
|
|
4450
|
-
const
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
3643
|
+
async function withLegacyChainCall(ctx, sessionId, options, action) {
|
|
3644
|
+
const { appName, needsConfirmation } = options;
|
|
3645
|
+
let openAppPromptShown = false;
|
|
3646
|
+
const onAppOpenPrompt = () => {
|
|
3647
|
+
openAppPromptShown = true;
|
|
3648
|
+
ctx.emit("ui-event", {
|
|
3649
|
+
type: EConnectorInteraction3.ConfirmOpenApp,
|
|
3650
|
+
payload: { sessionId }
|
|
4455
3651
|
});
|
|
4456
|
-
|
|
4457
|
-
|
|
3652
|
+
};
|
|
3653
|
+
const closeOpenAppUiIfShown = () => {
|
|
3654
|
+
if (openAppPromptShown) {
|
|
3655
|
+
ctx.emit("ui-event", {
|
|
3656
|
+
type: EConnectorInteraction3.InteractionComplete,
|
|
3657
|
+
payload: { sessionId }
|
|
3658
|
+
});
|
|
3659
|
+
openAppPromptShown = false;
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
try {
|
|
3663
|
+
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3664
|
+
} catch (err) {
|
|
3665
|
+
debugLog(
|
|
3666
|
+
"[LegacyChainCall] pre-flight ensureAppOpen failed:",
|
|
3667
|
+
appName,
|
|
3668
|
+
err?.message
|
|
3669
|
+
);
|
|
3670
|
+
closeOpenAppUiIfShown();
|
|
3671
|
+
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3672
|
+
}
|
|
3673
|
+
const runOnce = async () => {
|
|
3674
|
+
let confirmEmitted = false;
|
|
3675
|
+
if (needsConfirmation) {
|
|
3676
|
+
ctx.emit("ui-event", {
|
|
3677
|
+
type: EConnectorInteraction3.ConfirmOnDevice,
|
|
3678
|
+
payload: { sessionId }
|
|
3679
|
+
});
|
|
3680
|
+
confirmEmitted = true;
|
|
3681
|
+
}
|
|
3682
|
+
try {
|
|
3683
|
+
return await action(sessionId);
|
|
3684
|
+
} finally {
|
|
3685
|
+
if (confirmEmitted || openAppPromptShown) {
|
|
3686
|
+
ctx.emit("ui-event", {
|
|
3687
|
+
type: EConnectorInteraction3.InteractionComplete,
|
|
3688
|
+
payload: { sessionId }
|
|
3689
|
+
});
|
|
3690
|
+
openAppPromptShown = false;
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
try {
|
|
3695
|
+
return await runOnce();
|
|
3696
|
+
} catch (err) {
|
|
3697
|
+
if (!isLegacyWrongAppError(err, appName)) {
|
|
3698
|
+
debugLog("[LegacyChainCall] non-wrong-app failure:", appName, err?.message);
|
|
3699
|
+
ctx.invalidateSession(sessionId);
|
|
3700
|
+
throw ctx.wrapError(err, { defaultAppName: appName });
|
|
3701
|
+
}
|
|
3702
|
+
debugLog("[LegacyChainCall] wrong-app detected, retrying:", appName);
|
|
3703
|
+
try {
|
|
3704
|
+
await _ensureAppOpen(ctx, sessionId, appName, onAppOpenPrompt);
|
|
3705
|
+
} catch (switchErr) {
|
|
3706
|
+
debugLog(
|
|
3707
|
+
"[LegacyChainCall] retry ensureAppOpen failed:",
|
|
3708
|
+
appName,
|
|
3709
|
+
switchErr?.message
|
|
3710
|
+
);
|
|
3711
|
+
closeOpenAppUiIfShown();
|
|
3712
|
+
throw ctx.wrapError(switchErr, { defaultAppName: appName });
|
|
3713
|
+
}
|
|
3714
|
+
ctx.clearAllSigners();
|
|
3715
|
+
const result = await runOnce();
|
|
3716
|
+
debugLog("[LegacyChainCall] retry succeeded:", appName);
|
|
3717
|
+
return result;
|
|
3718
|
+
}
|
|
4458
3719
|
}
|
|
4459
|
-
async function
|
|
3720
|
+
async function _ensureAppOpen(ctx, sessionId, appName, onPrompt) {
|
|
4460
3721
|
const dmk = await ctx.getOrCreateDmk();
|
|
4461
|
-
const
|
|
4462
|
-
|
|
4463
|
-
return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
|
|
3722
|
+
const appManager = new AppManager(dmk);
|
|
3723
|
+
await appManager.ensureAppOpen(sessionId, appName, onPrompt);
|
|
4464
3724
|
}
|
|
4465
3725
|
|
|
4466
|
-
// src/
|
|
4467
|
-
import
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
this._sdk = _sdk;
|
|
4474
|
-
}
|
|
4475
|
-
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4476
|
-
async getAddress(derivationPath, options) {
|
|
4477
|
-
const action = this._sdk.getAddress(derivationPath, options);
|
|
4478
|
-
const result = await deviceActionToPromise(
|
|
4479
|
-
action,
|
|
4480
|
-
this.onInteraction,
|
|
4481
|
-
void 0,
|
|
4482
|
-
this.onRegisterCanceller
|
|
4483
|
-
);
|
|
4484
|
-
return { address: result.address, publicKey: result.publicKey };
|
|
3726
|
+
// src/transport/DmkTransport.ts
|
|
3727
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
3728
|
+
var DmkTransport = class extends Transport {
|
|
3729
|
+
constructor(dmk, sessionId) {
|
|
3730
|
+
super();
|
|
3731
|
+
this._dmk = dmk;
|
|
3732
|
+
this._sessionId = sessionId;
|
|
4485
3733
|
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
3734
|
+
async exchange(apdu) {
|
|
3735
|
+
const response = await this._dmk.sendApdu({
|
|
3736
|
+
sessionId: this._sessionId,
|
|
3737
|
+
apdu: new Uint8Array(apdu)
|
|
3738
|
+
});
|
|
3739
|
+
const { data, statusCode } = response;
|
|
3740
|
+
const result = Buffer.alloc(data.length + 2);
|
|
3741
|
+
if (data.length > 0) {
|
|
3742
|
+
result.set(data, 0);
|
|
3743
|
+
}
|
|
3744
|
+
result.set(statusCode, data.length);
|
|
3745
|
+
return result;
|
|
4495
3746
|
}
|
|
4496
|
-
|
|
4497
|
-
async signPersonalMessage(derivationPath, message, options) {
|
|
4498
|
-
const action = this._sdk.signPersonalMessage(derivationPath, message, options);
|
|
4499
|
-
return deviceActionToPromise(
|
|
4500
|
-
action,
|
|
4501
|
-
this.onInteraction,
|
|
4502
|
-
void 0,
|
|
4503
|
-
this.onRegisterCanceller
|
|
4504
|
-
);
|
|
3747
|
+
async close() {
|
|
4505
3748
|
}
|
|
4506
3749
|
};
|
|
4507
3750
|
|
|
4508
3751
|
// src/connector/chains/tron.ts
|
|
4509
3752
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4510
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4511
3753
|
const path = normalizePath(params.path);
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
3754
|
+
const showOnDevice = params.showOnDevice ?? false;
|
|
3755
|
+
return withLegacyChainCall(
|
|
3756
|
+
ctx,
|
|
3757
|
+
sessionId,
|
|
3758
|
+
{
|
|
3759
|
+
appName: "Tron",
|
|
3760
|
+
// Only show "confirm on device" UI when the device is actually going
|
|
3761
|
+
// to display the address for the user to verify.
|
|
3762
|
+
needsConfirmation: showOnDevice
|
|
3763
|
+
},
|
|
3764
|
+
async (sid) => {
|
|
3765
|
+
const trx = await _createTrx(ctx, sid);
|
|
3766
|
+
const result = await trx.getAddress(path, showOnDevice);
|
|
3767
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
3768
|
+
}
|
|
3769
|
+
);
|
|
4518
3770
|
}
|
|
4519
3771
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
4520
3772
|
if (!params.rawTxHex) {
|
|
4521
3773
|
throw Object.assign(
|
|
4522
3774
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
4523
|
-
{ code:
|
|
3775
|
+
{ code: HardwareErrorCode7.InvalidParams }
|
|
4524
3776
|
);
|
|
4525
3777
|
}
|
|
4526
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4527
3778
|
const path = normalizePath(params.path);
|
|
4528
|
-
return
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
3779
|
+
return withLegacyChainCall(
|
|
3780
|
+
ctx,
|
|
3781
|
+
sessionId,
|
|
3782
|
+
{ appName: "Tron", needsConfirmation: true },
|
|
3783
|
+
async (sid) => {
|
|
3784
|
+
const trx = await _createTrx(ctx, sid);
|
|
3785
|
+
const signature = await trx.signTransaction(
|
|
3786
|
+
path,
|
|
3787
|
+
params.rawTxHex,
|
|
3788
|
+
params.tokenSignatures ?? []
|
|
3789
|
+
);
|
|
3790
|
+
return { signature };
|
|
3791
|
+
}
|
|
3792
|
+
);
|
|
4532
3793
|
}
|
|
4533
3794
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4534
|
-
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
4535
3795
|
const path = normalizePath(params.path);
|
|
4536
|
-
return
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
3796
|
+
return withLegacyChainCall(
|
|
3797
|
+
ctx,
|
|
3798
|
+
sessionId,
|
|
3799
|
+
{ appName: "Tron", needsConfirmation: true },
|
|
3800
|
+
async (sid) => {
|
|
3801
|
+
const trx = await _createTrx(ctx, sid);
|
|
3802
|
+
const signature = await trx.signPersonalMessage(path, params.messageHex);
|
|
3803
|
+
return { signature };
|
|
3804
|
+
}
|
|
3805
|
+
);
|
|
4540
3806
|
}
|
|
4541
|
-
async function
|
|
3807
|
+
async function _createTrx(ctx, sessionId) {
|
|
4542
3808
|
const dmk = await ctx.getOrCreateDmk();
|
|
4543
|
-
|
|
4544
|
-
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4545
|
-
return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
|
|
3809
|
+
return new Trx(new DmkTransport(dmk, sessionId));
|
|
4546
3810
|
}
|
|
4547
3811
|
|
|
4548
3812
|
// src/device-apps/customActions.ts
|
|
@@ -4721,7 +3985,7 @@ var DeviceApps = class {
|
|
|
4721
3985
|
seTargetId: v.seTargetId,
|
|
4722
3986
|
mcuTargetId: v.mcuTargetId,
|
|
4723
3987
|
seVersion: v.seVersion,
|
|
4724
|
-
seFlagsHex:
|
|
3988
|
+
seFlagsHex: bytesToHex2(v.seFlags),
|
|
4725
3989
|
mcuSephVersion: v.mcuSephVersion,
|
|
4726
3990
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
4727
3991
|
hwVersion: v.hwVersion
|
|
@@ -4795,7 +4059,7 @@ var DeviceApps = class {
|
|
|
4795
4059
|
}
|
|
4796
4060
|
}
|
|
4797
4061
|
};
|
|
4798
|
-
function
|
|
4062
|
+
function bytesToHex2(bytes) {
|
|
4799
4063
|
if (!bytes) return "";
|
|
4800
4064
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4801
4065
|
}
|
|
@@ -4838,11 +4102,10 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4838
4102
|
evm: "Ethereum",
|
|
4839
4103
|
btc: "Bitcoin",
|
|
4840
4104
|
sol: "Solana",
|
|
4841
|
-
tron: "Tron"
|
|
4842
|
-
zcash: "Zcash"
|
|
4105
|
+
tron: "Tron"
|
|
4843
4106
|
};
|
|
4844
4107
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4845
|
-
Object.values(
|
|
4108
|
+
Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
|
|
4846
4109
|
);
|
|
4847
4110
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4848
4111
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4879,10 +4142,6 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4879
4142
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4880
4143
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4881
4144
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4882
|
-
case "@ledgerhq/device-signer-kit-tron":
|
|
4883
|
-
return import("@ledgerhq/device-signer-kit-tron");
|
|
4884
|
-
case "@ledgerhq/device-signer-kit-zcash":
|
|
4885
|
-
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4886
4145
|
case "@ledgerhq/context-module":
|
|
4887
4146
|
return import("@ledgerhq/context-module");
|
|
4888
4147
|
default:
|
|
@@ -4942,7 +4201,6 @@ var LedgerConnectorBase = class {
|
|
|
4942
4201
|
this._ctx = {
|
|
4943
4202
|
emit: (event, data) => this._emit(event, data),
|
|
4944
4203
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4945
|
-
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4946
4204
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4947
4205
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4948
4206
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -5042,7 +4300,7 @@ var LedgerConnectorBase = class {
|
|
|
5042
4300
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
5043
4301
|
);
|
|
5044
4302
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
5045
|
-
err.code =
|
|
4303
|
+
err.code = HardwareErrorCode8.BlePairingTimeout;
|
|
5046
4304
|
reject(err);
|
|
5047
4305
|
}, HANG_CEILING_MS);
|
|
5048
4306
|
});
|
|
@@ -5075,7 +4333,7 @@ var LedgerConnectorBase = class {
|
|
|
5075
4333
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
5076
4334
|
);
|
|
5077
4335
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
5078
|
-
err.code =
|
|
4336
|
+
err.code = HardwareErrorCode8.DeviceNotFound;
|
|
5079
4337
|
throw err;
|
|
5080
4338
|
};
|
|
5081
4339
|
const doConnect = async (path) => {
|
|
@@ -5132,14 +4390,14 @@ var LedgerConnectorBase = class {
|
|
|
5132
4390
|
this._resetSignersAndSessions();
|
|
5133
4391
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
5134
4392
|
const tag = err?._tag;
|
|
5135
|
-
if (isKnownConnectionTag(tag)
|
|
4393
|
+
if (isKnownConnectionTag(tag)) {
|
|
5136
4394
|
throw err;
|
|
5137
4395
|
}
|
|
5138
4396
|
const wrapped = new Error(
|
|
5139
4397
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
5140
4398
|
);
|
|
5141
4399
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
5142
|
-
wrapped.code =
|
|
4400
|
+
wrapped.code = HardwareErrorCode8.BlePairingTimeout;
|
|
5143
4401
|
wrapped.originalError = err;
|
|
5144
4402
|
throw wrapped;
|
|
5145
4403
|
}
|
|
@@ -5217,7 +4475,7 @@ var LedgerConnectorBase = class {
|
|
|
5217
4475
|
this._unwatchSessionState(sessionId);
|
|
5218
4476
|
this._signerManager?.invalidate(sessionId);
|
|
5219
4477
|
this._cancellers.get(sessionId)?.({
|
|
5220
|
-
code:
|
|
4478
|
+
code: HardwareErrorCode8.DeviceDisconnected,
|
|
5221
4479
|
tag: "DeviceDisconnected",
|
|
5222
4480
|
message: "Device disconnected"
|
|
5223
4481
|
});
|
|
@@ -5243,7 +4501,7 @@ var LedgerConnectorBase = class {
|
|
|
5243
4501
|
success: false,
|
|
5244
4502
|
error: serializeConnectorError(
|
|
5245
4503
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
5246
|
-
code:
|
|
4504
|
+
code: HardwareErrorCode8.DeviceAppStuck,
|
|
5247
4505
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
5248
4506
|
originalError: err
|
|
5249
4507
|
})
|
|
@@ -5255,7 +4513,7 @@ var LedgerConnectorBase = class {
|
|
|
5255
4513
|
success: false,
|
|
5256
4514
|
error: serializeConnectorError(
|
|
5257
4515
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
5258
|
-
code:
|
|
4516
|
+
code: HardwareErrorCode8.TransportError,
|
|
5259
4517
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
5260
4518
|
originalError: err
|
|
5261
4519
|
})
|
|
@@ -5301,11 +4559,6 @@ var LedgerConnectorBase = class {
|
|
|
5301
4559
|
return solSignTransaction(ctx, sessionId, params);
|
|
5302
4560
|
case "solSignMessage":
|
|
5303
4561
|
return solSignMessage(ctx, sessionId, params);
|
|
5304
|
-
// ZCASH
|
|
5305
|
-
case "zcashGetFullViewingKey":
|
|
5306
|
-
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5307
|
-
case "zcashGetShieldedAddress":
|
|
5308
|
-
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
5309
4562
|
// TRON
|
|
5310
4563
|
case "tronGetAddress":
|
|
5311
4564
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -5330,7 +4583,7 @@ var LedgerConnectorBase = class {
|
|
|
5330
4583
|
try {
|
|
5331
4584
|
return await apps.install(p.appName, ({ progress }) => {
|
|
5332
4585
|
ctx.emit("ui-event", {
|
|
5333
|
-
type:
|
|
4586
|
+
type: EConnectorInteraction4.AppInstallProgress,
|
|
5334
4587
|
payload: {
|
|
5335
4588
|
sessionId,
|
|
5336
4589
|
appName: p.appName,
|
|
@@ -5339,7 +4592,7 @@ var LedgerConnectorBase = class {
|
|
|
5339
4592
|
});
|
|
5340
4593
|
});
|
|
5341
4594
|
} catch (err) {
|
|
5342
|
-
ctx.
|
|
4595
|
+
ctx.invalidateSession(sessionId);
|
|
5343
4596
|
throw ctx.wrapError(err);
|
|
5344
4597
|
} finally {
|
|
5345
4598
|
ctx.clearCanceller(sessionId);
|
|
@@ -5429,7 +4682,7 @@ var LedgerConnectorBase = class {
|
|
|
5429
4682
|
);
|
|
5430
4683
|
return { isGenuine: output.isGenuine, deviceId };
|
|
5431
4684
|
} catch (err) {
|
|
5432
|
-
ctx.
|
|
4685
|
+
ctx.invalidateSession(sessionId);
|
|
5433
4686
|
throw ctx.wrapError(err);
|
|
5434
4687
|
} finally {
|
|
5435
4688
|
ctx.clearCanceller(sessionId);
|
|
@@ -5548,26 +4801,9 @@ var LedgerConnectorBase = class {
|
|
|
5548
4801
|
}
|
|
5549
4802
|
return this._deviceAppsManager;
|
|
5550
4803
|
}
|
|
5551
|
-
// DeviceAppsManager is a per-call factory with no cached session state, so
|
|
5552
|
-
// there is nothing to invalidate for it here.
|
|
5553
4804
|
_invalidateSession(sessionId) {
|
|
5554
4805
|
this._signerManager?.invalidate(sessionId);
|
|
5555
4806
|
}
|
|
5556
|
-
/**
|
|
5557
|
-
* Fire the canceller so DMK closes the secure-channel WebSocket. The DMK device
|
|
5558
|
-
* session is kept: unlock/retry recovery reuses it.
|
|
5559
|
-
*/
|
|
5560
|
-
_teardownSecureChannelSession(sessionId) {
|
|
5561
|
-
const cancel = this._cancellers.get(sessionId);
|
|
5562
|
-
this._cancellers.delete(sessionId);
|
|
5563
|
-
if (cancel) {
|
|
5564
|
-
try {
|
|
5565
|
-
cancel();
|
|
5566
|
-
} catch {
|
|
5567
|
-
}
|
|
5568
|
-
}
|
|
5569
|
-
this._invalidateSession(sessionId);
|
|
5570
|
-
}
|
|
5571
4807
|
/**
|
|
5572
4808
|
* Replace an old session with a new one after app switch.
|
|
5573
4809
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -5652,7 +4888,7 @@ var LedgerConnectorBase = class {
|
|
|
5652
4888
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
5653
4889
|
*/
|
|
5654
4890
|
_ctxForMethod(method) {
|
|
5655
|
-
const prefix = /^(evm|btc|sol|tron
|
|
4891
|
+
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5656
4892
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
5657
4893
|
if (!defaultAppName) return this._ctx;
|
|
5658
4894
|
return {
|
|
@@ -5686,209 +4922,6 @@ var LedgerConnectorBase = class {
|
|
|
5686
4922
|
return error;
|
|
5687
4923
|
}
|
|
5688
4924
|
};
|
|
5689
|
-
|
|
5690
|
-
// src/transport/DmkTransport.ts
|
|
5691
|
-
import Transport from "@ledgerhq/hw-transport";
|
|
5692
|
-
var DmkTransport = class extends Transport {
|
|
5693
|
-
constructor(dmk, sessionId) {
|
|
5694
|
-
super();
|
|
5695
|
-
this._dmk = dmk;
|
|
5696
|
-
this._sessionId = sessionId;
|
|
5697
|
-
}
|
|
5698
|
-
async exchange(apdu) {
|
|
5699
|
-
const response = await this._dmk.sendApdu({
|
|
5700
|
-
sessionId: this._sessionId,
|
|
5701
|
-
apdu: new Uint8Array(apdu)
|
|
5702
|
-
});
|
|
5703
|
-
const { data, statusCode } = response;
|
|
5704
|
-
const result = Buffer.alloc(data.length + 2);
|
|
5705
|
-
if (data.length > 0) {
|
|
5706
|
-
result.set(data, 0);
|
|
5707
|
-
}
|
|
5708
|
-
result.set(statusCode, data.length);
|
|
5709
|
-
return result;
|
|
5710
|
-
}
|
|
5711
|
-
async close() {
|
|
5712
|
-
}
|
|
5713
|
-
};
|
|
5714
|
-
|
|
5715
|
-
// src/app/AppManager.ts
|
|
5716
|
-
import {
|
|
5717
|
-
CloseAppCommand,
|
|
5718
|
-
GetAppAndVersionCommand,
|
|
5719
|
-
OpenAppCommand,
|
|
5720
|
-
isSuccessCommandResult
|
|
5721
|
-
} from "@ledgerhq/device-management-kit";
|
|
5722
|
-
import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
|
|
5723
|
-
var APP_NAME_MAP = {
|
|
5724
|
-
ETH: "Ethereum",
|
|
5725
|
-
BTC: "Bitcoin",
|
|
5726
|
-
SOL: "Solana",
|
|
5727
|
-
TRX: "Tron",
|
|
5728
|
-
ZEC: "Zcash",
|
|
5729
|
-
XRP: "XRP",
|
|
5730
|
-
ADA: "Cardano",
|
|
5731
|
-
DOT: "Polkadot",
|
|
5732
|
-
ATOM: "Cosmos"
|
|
5733
|
-
};
|
|
5734
|
-
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5735
|
-
var AppManager = class {
|
|
5736
|
-
constructor(dmk, options) {
|
|
5737
|
-
this._dmk = dmk;
|
|
5738
|
-
this._waitMs = options?.waitMs ?? 1e3;
|
|
5739
|
-
this._maxRetries = options?.maxRetries ?? 10;
|
|
5740
|
-
}
|
|
5741
|
-
/**
|
|
5742
|
-
* Return the Ledger app name for a given chain ticker,
|
|
5743
|
-
* or undefined if the chain is not supported.
|
|
5744
|
-
*/
|
|
5745
|
-
static getAppName(chain) {
|
|
5746
|
-
return APP_NAME_MAP[chain];
|
|
5747
|
-
}
|
|
5748
|
-
/**
|
|
5749
|
-
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5750
|
-
*
|
|
5751
|
-
* Flow:
|
|
5752
|
-
* 1. Check the currently running app.
|
|
5753
|
-
* 2. If it is already the target, return immediately.
|
|
5754
|
-
* 3. If a different app is running (not dashboard), close it first.
|
|
5755
|
-
* 4. Open the target app.
|
|
5756
|
-
* 5. Poll until the device confirms the target app is running.
|
|
5757
|
-
*/
|
|
5758
|
-
/**
|
|
5759
|
-
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5760
|
-
* device is about to display "Open <app>" on screen and wait for the
|
|
5761
|
-
* user's button press. UI consumers should show their "open app" prompt
|
|
5762
|
-
* in response. NOT called when the target app is already open (no user
|
|
5763
|
-
* interaction needed in that case).
|
|
5764
|
-
*
|
|
5765
|
-
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5766
|
-
* has physically confirmed on the device, so anything that runs AFTER
|
|
5767
|
-
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5768
|
-
* Hence the callback must fire BEFORE that await.
|
|
5769
|
-
*/
|
|
5770
|
-
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5771
|
-
const currentApp = await this._getCurrentApp(sessionId);
|
|
5772
|
-
if (currentApp === targetAppName) {
|
|
5773
|
-
return;
|
|
5774
|
-
}
|
|
5775
|
-
if (!this._isDashboard(currentApp)) {
|
|
5776
|
-
await this._closeCurrentApp(sessionId);
|
|
5777
|
-
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5778
|
-
}
|
|
5779
|
-
onConfirmOnDevice?.();
|
|
5780
|
-
await this._openApp(sessionId, targetAppName);
|
|
5781
|
-
await this._waitForApp(sessionId, targetAppName);
|
|
5782
|
-
}
|
|
5783
|
-
// ---------------------------------------------------------------------------
|
|
5784
|
-
// Private helpers
|
|
5785
|
-
// ---------------------------------------------------------------------------
|
|
5786
|
-
async _getCurrentApp(sessionId) {
|
|
5787
|
-
const result = await this._dmk.sendCommand({
|
|
5788
|
-
sessionId,
|
|
5789
|
-
command: new GetAppAndVersionCommand()
|
|
5790
|
-
});
|
|
5791
|
-
if (isSuccessCommandResult(result)) {
|
|
5792
|
-
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5793
|
-
return result.data.name;
|
|
5794
|
-
}
|
|
5795
|
-
const errResult = result;
|
|
5796
|
-
const dmkErr = errResult.error ?? {};
|
|
5797
|
-
const original = dmkErr.originalError;
|
|
5798
|
-
debugLog(
|
|
5799
|
-
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5800
|
-
sessionId,
|
|
5801
|
-
"tag=",
|
|
5802
|
-
dmkErr._tag,
|
|
5803
|
-
"errorCode=",
|
|
5804
|
-
dmkErr.errorCode,
|
|
5805
|
-
"message=",
|
|
5806
|
-
dmkErr.message,
|
|
5807
|
-
"originalErrorMessage=",
|
|
5808
|
-
original?.message ?? String(original ?? "")
|
|
5809
|
-
);
|
|
5810
|
-
throw Object.assign(
|
|
5811
|
-
new Error(
|
|
5812
|
-
dmkErr.message ?? "Failed to get current app from device"
|
|
5813
|
-
),
|
|
5814
|
-
{
|
|
5815
|
-
_tag: dmkErr._tag,
|
|
5816
|
-
errorCode: dmkErr.errorCode,
|
|
5817
|
-
originalError: original
|
|
5818
|
-
}
|
|
5819
|
-
);
|
|
5820
|
-
}
|
|
5821
|
-
async _openApp(sessionId, appName) {
|
|
5822
|
-
const result = await this._dmk.sendCommand({
|
|
5823
|
-
sessionId,
|
|
5824
|
-
command: new OpenAppCommand({ appName })
|
|
5825
|
-
});
|
|
5826
|
-
if (!isSuccessCommandResult(result)) {
|
|
5827
|
-
const dmkErr = result.error;
|
|
5828
|
-
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5829
|
-
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5830
|
-
debugLog(
|
|
5831
|
-
"[AppManager] openApp failed:",
|
|
5832
|
-
appName,
|
|
5833
|
-
"errorCode:",
|
|
5834
|
-
errorCode,
|
|
5835
|
-
"tag:",
|
|
5836
|
-
dmkErr._tag
|
|
5837
|
-
);
|
|
5838
|
-
let code;
|
|
5839
|
-
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5840
|
-
code = HardwareErrorCode8.AppNotInstalled;
|
|
5841
|
-
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5842
|
-
code = HardwareErrorCode8.UserRejected;
|
|
5843
|
-
}
|
|
5844
|
-
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5845
|
-
_tag: ERROR_TAG.OpenAppCommand,
|
|
5846
|
-
code,
|
|
5847
|
-
errorCode,
|
|
5848
|
-
statusCode: errorCode,
|
|
5849
|
-
appName,
|
|
5850
|
-
originalError: dmkErr
|
|
5851
|
-
});
|
|
5852
|
-
}
|
|
5853
|
-
}
|
|
5854
|
-
async _closeCurrentApp(sessionId) {
|
|
5855
|
-
debugLog("[AppManager] closeCurrentApp");
|
|
5856
|
-
await this._dmk.sendCommand({
|
|
5857
|
-
sessionId,
|
|
5858
|
-
command: new CloseAppCommand()
|
|
5859
|
-
});
|
|
5860
|
-
}
|
|
5861
|
-
/**
|
|
5862
|
-
* Poll the device until the expected app is reported as running,
|
|
5863
|
-
* or throw after `_maxRetries` attempts.
|
|
5864
|
-
*/
|
|
5865
|
-
async _waitForApp(sessionId, expectedAppName) {
|
|
5866
|
-
let lastSeen = "";
|
|
5867
|
-
for (let i = 0; i < this._maxRetries; i++) {
|
|
5868
|
-
await this._wait();
|
|
5869
|
-
const current = await this._getCurrentApp(sessionId);
|
|
5870
|
-
lastSeen = current;
|
|
5871
|
-
if (current === expectedAppName) {
|
|
5872
|
-
return;
|
|
5873
|
-
}
|
|
5874
|
-
}
|
|
5875
|
-
debugLog(
|
|
5876
|
-
"[AppManager] waitForApp exhausted: expected=",
|
|
5877
|
-
expectedAppName,
|
|
5878
|
-
"lastSeen=",
|
|
5879
|
-
lastSeen
|
|
5880
|
-
);
|
|
5881
|
-
throw new Error(
|
|
5882
|
-
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5883
|
-
);
|
|
5884
|
-
}
|
|
5885
|
-
_isDashboard(appName) {
|
|
5886
|
-
return appName === DASHBOARD_APP_NAME;
|
|
5887
|
-
}
|
|
5888
|
-
_wait() {
|
|
5889
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5890
|
-
}
|
|
5891
|
-
};
|
|
5892
4925
|
export {
|
|
5893
4926
|
AppManager,
|
|
5894
4927
|
DmkTransport,
|
|
@@ -5899,7 +4932,6 @@ export {
|
|
|
5899
4932
|
SignerEth,
|
|
5900
4933
|
SignerManager,
|
|
5901
4934
|
SignerSol,
|
|
5902
|
-
SignerZcash,
|
|
5903
4935
|
debugLog,
|
|
5904
4936
|
deviceActionToPromise,
|
|
5905
4937
|
isDeviceLockedError,
|