@onekeyfe/hwk-ledger-adapter 1.2.7 → 1.2.8-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +86 -39
- package/dist/index.d.ts +86 -39
- package/dist/index.js +1781 -834
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1797 -829
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -11
package/dist/index.mjs
CHANGED
|
@@ -6,30 +6,41 @@ import {
|
|
|
6
6
|
DeviceJobQueue,
|
|
7
7
|
EConnectorInteraction,
|
|
8
8
|
HardwareErrorCode as HardwareErrorCode3,
|
|
9
|
+
OperationRegistry,
|
|
10
|
+
SDK,
|
|
9
11
|
TypedEventEmitter,
|
|
10
12
|
UI_REQUEST,
|
|
11
13
|
UI_REQUEST_PREEMPTED_TAG,
|
|
12
14
|
UiRequestRegistry,
|
|
15
|
+
canReplayHardwareMethodAfterTransportFailure,
|
|
13
16
|
createHwkError,
|
|
14
17
|
deriveDeviceFingerprint,
|
|
15
18
|
failure as failure2,
|
|
19
|
+
isHardwareOperationId,
|
|
20
|
+
isHwkRecoveryHint,
|
|
21
|
+
operationMayHaveCompletedParams,
|
|
16
22
|
rehydrateConnectorError,
|
|
23
|
+
requestBleDeviceSelection,
|
|
24
|
+
requestSaveDeviceBinding,
|
|
25
|
+
resolveSearchTargetReusePolicy,
|
|
17
26
|
success
|
|
18
27
|
} from "@onekeyfe/hwk-adapter-core";
|
|
19
28
|
|
|
20
29
|
// src/errors.ts
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function ledgerFailure(code, error, appName, tag, params) {
|
|
30
|
+
import {
|
|
31
|
+
HardwareErrorCode,
|
|
32
|
+
defaultOriginForCode,
|
|
33
|
+
defaultRecoveryForCode,
|
|
34
|
+
enrichErrorMessage
|
|
35
|
+
} from "@onekeyfe/hwk-adapter-core";
|
|
36
|
+
function ledgerFailure(code, error, appName, tag, params, origin, recovery) {
|
|
29
37
|
const payload = { error, code };
|
|
30
38
|
if (appName !== void 0) payload.appName = appName;
|
|
31
39
|
if (tag !== void 0) payload._tag = tag;
|
|
32
40
|
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);
|
|
33
44
|
return { success: false, payload };
|
|
34
45
|
}
|
|
35
46
|
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
@@ -170,7 +181,10 @@ var ERROR_TAG = {
|
|
|
170
181
|
UnknownDevice: "UnknownDeviceError",
|
|
171
182
|
DeviceSessionRefresher: "DeviceSessionRefresherError",
|
|
172
183
|
DeviceNotInitialized: "DeviceNotInitializedError",
|
|
173
|
-
|
|
184
|
+
// DMK class OpeningConnectionError carries `_tag` "ConnectionOpeningError"; both
|
|
185
|
+
// spellings are kept so a DMK release that aligns them still classifies.
|
|
186
|
+
OpeningConnection: "ConnectionOpeningError",
|
|
187
|
+
OpeningConnectionLegacy: "OpeningConnectionError",
|
|
174
188
|
DeviceDisconnectedBeforeSendingApdu: "DeviceDisconnectedBeforeSendingApdu",
|
|
175
189
|
DeviceDisconnectedWhileSending: "DeviceDisconnectedWhileSendingError",
|
|
176
190
|
Disconnect: "DisconnectError",
|
|
@@ -183,7 +197,16 @@ var ERROR_TAG = {
|
|
|
183
197
|
// DMK remote-network failures (manager-api HTTP / secure-channel WS).
|
|
184
198
|
WebSocketConnection: "WebSocketConnectionError",
|
|
185
199
|
HttpFetch: "FetchError",
|
|
186
|
-
|
|
200
|
+
NetworkDA: "NetworkDAError",
|
|
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"
|
|
187
210
|
};
|
|
188
211
|
function isDeviceLockedError(err) {
|
|
189
212
|
if (!err || typeof err !== "object") return false;
|
|
@@ -213,7 +236,6 @@ function isBlePairingFailureError(err) {
|
|
|
213
236
|
return false;
|
|
214
237
|
}
|
|
215
238
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
216
|
-
ERROR_TAG.DeviceLocked,
|
|
217
239
|
ERROR_TAG.DeviceNotAdvertising,
|
|
218
240
|
ERROR_TAG.BlePairingTimeout,
|
|
219
241
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -225,6 +247,7 @@ var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
|
225
247
|
ERROR_TAG.DeviceSessionRefresher,
|
|
226
248
|
ERROR_TAG.DeviceNotInitialized,
|
|
227
249
|
ERROR_TAG.OpeningConnection,
|
|
250
|
+
ERROR_TAG.OpeningConnectionLegacy,
|
|
228
251
|
ERROR_TAG.DeviceDisconnectedBeforeSendingApdu,
|
|
229
252
|
ERROR_TAG.DeviceDisconnectedWhileSending,
|
|
230
253
|
ERROR_TAG.Disconnect,
|
|
@@ -239,7 +262,10 @@ var DEVICE_NOT_FOUND_TAGS = /* @__PURE__ */ new Set([
|
|
|
239
262
|
// Map to DeviceNotFound so non-BLE-direct paths get a sensible error code.
|
|
240
263
|
ERROR_TAG.DeviceNotInDiscoveryCache
|
|
241
264
|
]);
|
|
242
|
-
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
265
|
+
var DEVICE_BUSY_TAGS = /* @__PURE__ */ new Set([
|
|
266
|
+
ERROR_TAG.OpeningConnection,
|
|
267
|
+
ERROR_TAG.OpeningConnectionLegacy
|
|
268
|
+
]);
|
|
243
269
|
var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
244
270
|
ERROR_TAG.DeviceNotRecognized,
|
|
245
271
|
ERROR_TAG.DeviceSessionNotFound,
|
|
@@ -251,17 +277,18 @@ var DEVICE_DISCONNECTED_TAGS = /* @__PURE__ */ new Set([
|
|
|
251
277
|
ERROR_TAG.WebHIDDisconnect
|
|
252
278
|
]);
|
|
253
279
|
function isConnectionLevelError(err) {
|
|
254
|
-
|
|
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;
|
|
280
|
+
return hasErrorTag(err, CONNECTION_LEVEL_TAGS);
|
|
261
281
|
}
|
|
262
282
|
function isKnownConnectionTag(tag) {
|
|
263
283
|
return typeof tag === "string" && CONNECTION_LEVEL_TAGS.has(tag);
|
|
264
284
|
}
|
|
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
|
+
}
|
|
265
292
|
function hasStatusCode(err, codeSet) {
|
|
266
293
|
if (!err || typeof err !== "object") return false;
|
|
267
294
|
const e = err;
|
|
@@ -297,28 +324,28 @@ function hasInvalidArgumentCode(err) {
|
|
|
297
324
|
if (e.error != null && e._tag && hasInvalidArgumentCode(e.error)) return true;
|
|
298
325
|
return false;
|
|
299
326
|
}
|
|
300
|
-
function
|
|
327
|
+
function hasErrorTag(err, tags) {
|
|
301
328
|
if (!err || typeof err !== "object") return false;
|
|
302
|
-
const tag = err._tag;
|
|
303
|
-
if (tag && DEVICE_NOT_FOUND_TAGS.has(tag)) return true;
|
|
304
329
|
const e = err;
|
|
305
|
-
if (e.
|
|
306
|
-
if (e.
|
|
330
|
+
if (typeof e._tag === "string" && tags.has(e._tag)) return true;
|
|
331
|
+
if (e.originalError != null && hasErrorTag(e.originalError, tags)) return true;
|
|
332
|
+
if (e.error != null && e._tag && hasErrorTag(e.error, tags)) return true;
|
|
307
333
|
return false;
|
|
308
334
|
}
|
|
335
|
+
function isDeviceNotFoundError(err) {
|
|
336
|
+
return hasErrorTag(err, DEVICE_NOT_FOUND_TAGS);
|
|
337
|
+
}
|
|
309
338
|
function isDeviceBusyError(err) {
|
|
310
|
-
|
|
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;
|
|
339
|
+
return hasErrorTag(err, DEVICE_BUSY_TAGS);
|
|
317
340
|
}
|
|
341
|
+
var USER_REJECTED_TAGS = /* @__PURE__ */ new Set([
|
|
342
|
+
ERROR_TAG.UserRefusedOnDevice,
|
|
343
|
+
ERROR_TAG.RefusedByUserDA
|
|
344
|
+
]);
|
|
318
345
|
function isUserRejectedError(err) {
|
|
319
346
|
if (!err || typeof err !== "object") return false;
|
|
320
347
|
const e = err;
|
|
321
|
-
if (
|
|
348
|
+
if (hasErrorTag(err, USER_REJECTED_TAGS)) return true;
|
|
322
349
|
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
323
350
|
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
324
351
|
return false;
|
|
@@ -349,15 +376,33 @@ function isAppNotInstalledError(err) {
|
|
|
349
376
|
if (hasStatusCode(err, APP_NOT_INSTALLED_CODES)) return true;
|
|
350
377
|
return false;
|
|
351
378
|
}
|
|
379
|
+
var OUT_OF_MEMORY_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.OutOfMemoryDA]);
|
|
352
380
|
function isOutOfMemoryError(err) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
381
|
+
return hasErrorTag(err, OUT_OF_MEMORY_TAGS);
|
|
382
|
+
}
|
|
383
|
+
var APP_ALREADY_INSTALLED_TAGS = /* @__PURE__ */ new Set([ERROR_TAG.AppAlreadyInstalledDA]);
|
|
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);
|
|
356
401
|
}
|
|
357
402
|
var NETWORK_ERROR_TAGS = /* @__PURE__ */ new Set([
|
|
358
403
|
ERROR_TAG.WebSocketConnection,
|
|
359
404
|
ERROR_TAG.HttpFetch,
|
|
360
|
-
ERROR_TAG.
|
|
405
|
+
ERROR_TAG.NetworkDA
|
|
361
406
|
]);
|
|
362
407
|
function isNetworkError(err) {
|
|
363
408
|
if (!err || typeof err !== "object") return false;
|
|
@@ -407,23 +452,34 @@ function isTransportStuck(err) {
|
|
|
407
452
|
function isStuckAppStateError(err) {
|
|
408
453
|
return isAppStuckByApdu(err) || isTransportStuck(err);
|
|
409
454
|
}
|
|
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
|
+
}
|
|
410
464
|
function mapLedgerError(err, opts) {
|
|
411
465
|
let originalMessage = "Unknown Ledger error";
|
|
412
466
|
if (err instanceof Error) {
|
|
413
467
|
originalMessage = err.message;
|
|
414
468
|
} else if (err && typeof err === "object") {
|
|
415
469
|
const e = err;
|
|
416
|
-
originalMessage = String(
|
|
470
|
+
originalMessage = String(
|
|
471
|
+
e.message ?? nestedErrorMessage(e.originalError) ?? e._tag ?? e.type ?? JSON.stringify(err)
|
|
472
|
+
);
|
|
417
473
|
}
|
|
418
474
|
let code;
|
|
419
475
|
if (isDeviceLockedError(err)) {
|
|
420
476
|
code = HardwareErrorCode.DeviceLocked;
|
|
421
477
|
} else if (isDeviceNotAdvertisingError(err) || isDeviceNotFoundError(err)) {
|
|
422
478
|
code = HardwareErrorCode.DeviceNotFound;
|
|
423
|
-
} else if (isDeviceBusyError(err)) {
|
|
424
|
-
code = HardwareErrorCode.DeviceBusy;
|
|
425
479
|
} else if (isBlePairingFailureError(err)) {
|
|
426
480
|
code = HardwareErrorCode.BlePairingTimeout;
|
|
481
|
+
} else if (isDeviceBusyError(err)) {
|
|
482
|
+
code = HardwareErrorCode.DeviceBusy;
|
|
427
483
|
} else if (isUserAbortedError(err)) {
|
|
428
484
|
code = HardwareErrorCode.UserAborted;
|
|
429
485
|
} else if (isUserRejectedError(err)) {
|
|
@@ -432,10 +488,18 @@ function mapLedgerError(err, opts) {
|
|
|
432
488
|
code = HardwareErrorCode.WrongApp;
|
|
433
489
|
} else if (isAppNotInstalledError(err)) {
|
|
434
490
|
code = HardwareErrorCode.AppNotInstalled;
|
|
491
|
+
} else if (isAppAlreadyInstalledError(err)) {
|
|
492
|
+
code = HardwareErrorCode.AppAlreadyInstalled;
|
|
435
493
|
} else if (isOutOfMemoryError(err)) {
|
|
436
494
|
code = HardwareErrorCode.DeviceOutOfMemory;
|
|
495
|
+
} else if (isDeviceNotOnboardedError(err)) {
|
|
496
|
+
code = HardwareErrorCode.DeviceNotInitialized;
|
|
497
|
+
} else if (isFirmwareMetadataError(err)) {
|
|
498
|
+
code = HardwareErrorCode.LedgerFirmwareMetadataError;
|
|
437
499
|
} else if (isNetworkError(err)) {
|
|
438
500
|
code = HardwareErrorCode.NetworkError;
|
|
501
|
+
} else if (isSecureChannelError(err)) {
|
|
502
|
+
code = HardwareErrorCode.LedgerSecureChannelError;
|
|
439
503
|
} else if (isDeviceDisconnectedError(err)) {
|
|
440
504
|
code = HardwareErrorCode.DeviceDisconnected;
|
|
441
505
|
} else if (isTimeoutError(err)) {
|
|
@@ -448,11 +512,33 @@ function mapLedgerError(err, opts) {
|
|
|
448
512
|
}
|
|
449
513
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
450
514
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
451
|
-
return {
|
|
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;
|
|
452
530
|
}
|
|
453
531
|
|
|
454
532
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
455
|
-
import {
|
|
533
|
+
import {
|
|
534
|
+
HardwareErrorCode as HardwareErrorCode2,
|
|
535
|
+
failure,
|
|
536
|
+
isConnectionLost,
|
|
537
|
+
isUserRefusal,
|
|
538
|
+
isWalletSafetyFailure,
|
|
539
|
+
resolveHardwareOperationTarget,
|
|
540
|
+
runAllNetworkGetAddress
|
|
541
|
+
} from "@onekeyfe/hwk-adapter-core";
|
|
456
542
|
|
|
457
543
|
// src/utils/sdkEventBus.ts
|
|
458
544
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -509,65 +595,107 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
509
595
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
510
596
|
function createAllNetworkGetAddress({
|
|
511
597
|
callChain,
|
|
512
|
-
getChainFingerprint
|
|
598
|
+
getChainFingerprint,
|
|
599
|
+
retainOperation,
|
|
600
|
+
errorToFailure,
|
|
601
|
+
createCancelScope,
|
|
602
|
+
releaseBundle
|
|
513
603
|
}) {
|
|
514
604
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
515
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
516
|
-
|
|
605
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
606
|
+
method: "allNetworkGetAddress",
|
|
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
|
+
};
|
|
517
625
|
const commonParams = {
|
|
518
|
-
autoInstallApp: params.autoInstallApp
|
|
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
|
|
519
632
|
};
|
|
520
633
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
634
|
+
try {
|
|
635
|
+
const result = await runAllNetworkGetAddress({
|
|
636
|
+
connectId: effectiveTargetId,
|
|
637
|
+
deviceId: _deviceId,
|
|
638
|
+
params,
|
|
639
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
640
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
641
|
+
callItem: async ({ method, chain, item }) => {
|
|
642
|
+
if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
|
|
643
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
644
|
+
return callAllNetworkMethod(
|
|
645
|
+
callChain,
|
|
646
|
+
effectiveTargetId,
|
|
647
|
+
itemDeviceId,
|
|
648
|
+
method,
|
|
649
|
+
item,
|
|
650
|
+
commonParams,
|
|
651
|
+
installContext
|
|
652
|
+
);
|
|
653
|
+
},
|
|
654
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
655
|
+
getChainFingerprint,
|
|
656
|
+
effectiveTargetId,
|
|
534
657
|
item,
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
return result;
|
|
658
|
+
chain,
|
|
659
|
+
payload,
|
|
660
|
+
chainFingerprints,
|
|
661
|
+
installContext,
|
|
662
|
+
cancelScope.signal
|
|
663
|
+
),
|
|
664
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
665
|
+
buildTopLevelFailure: (response) => {
|
|
666
|
+
const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
|
|
667
|
+
return failure(
|
|
668
|
+
code,
|
|
669
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
670
|
+
response.payload?.params
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
675
|
+
method: "allNetworkGetAddress",
|
|
676
|
+
success: result.success,
|
|
677
|
+
payload: result
|
|
678
|
+
});
|
|
679
|
+
return result;
|
|
680
|
+
} finally {
|
|
681
|
+
await releaseBundle(installContext, cancelScope.signal).catch(() => void 0);
|
|
682
|
+
cancelScope.release();
|
|
683
|
+
releaseOperationRetention?.();
|
|
684
|
+
}
|
|
563
685
|
};
|
|
564
686
|
}
|
|
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
|
+
}
|
|
565
693
|
function isTopLevelAllNetworkFailure(response) {
|
|
566
694
|
if (response.success) {
|
|
567
695
|
return false;
|
|
568
696
|
}
|
|
569
697
|
const code = response.payload?.code;
|
|
570
|
-
return code
|
|
698
|
+
return isWalletSafetyFailure(code) || isUserRefusal(code) || isConnectionLost(code);
|
|
571
699
|
}
|
|
572
700
|
function getItemDeviceId(item) {
|
|
573
701
|
const { deviceId } = item;
|
|
@@ -597,8 +725,18 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
597
725
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
598
726
|
return coin ? { ...item, coin } : item;
|
|
599
727
|
}
|
|
600
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
601
|
-
const
|
|
728
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
|
|
729
|
+
const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
|
|
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
|
+
);
|
|
602
740
|
if (!fingerprint) {
|
|
603
741
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
604
742
|
}
|
|
@@ -619,8 +757,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
619
757
|
}
|
|
620
758
|
};
|
|
621
759
|
}
|
|
622
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
623
|
-
const response = await getChainFingerprint(connectId, chain);
|
|
760
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
|
|
761
|
+
const response = await getChainFingerprint(connectId, chain, context);
|
|
624
762
|
return response.success ? response.payload : "";
|
|
625
763
|
}
|
|
626
764
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -713,6 +851,16 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
|
|
|
713
851
|
function formatDeviceMismatchError(expected, actual) {
|
|
714
852
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
715
853
|
}
|
|
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
|
+
}
|
|
716
864
|
var BTC_HIGH_INDEX_THRESHOLD = 100;
|
|
717
865
|
function btcAccountIndexFromPath(path) {
|
|
718
866
|
const segments = path.replace(/^m\//, "").split("/");
|
|
@@ -724,9 +872,35 @@ function btcAccountIndexFromPath(path) {
|
|
|
724
872
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
725
873
|
constructor(connector, options) {
|
|
726
874
|
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";
|
|
727
878
|
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
|
+
});
|
|
728
893
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
729
894
|
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();
|
|
730
904
|
this._uiRegistry = new UiRequestRegistry();
|
|
731
905
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
732
906
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -740,10 +914,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
740
914
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
741
915
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
742
916
|
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();
|
|
743
925
|
this._installProgressLastEmittedValue = -Infinity;
|
|
744
926
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
745
927
|
callChain: this.callChain.bind(this),
|
|
746
|
-
getChainFingerprint: (connectId, chain) =>
|
|
928
|
+
getChainFingerprint: async (connectId, chain, context) => {
|
|
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)
|
|
747
943
|
});
|
|
748
944
|
// ---------------------------------------------------------------------------
|
|
749
945
|
// Private helpers
|
|
@@ -753,7 +949,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
753
949
|
*
|
|
754
950
|
* - If a session already exists for the given connectId, reuse it.
|
|
755
951
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
756
|
-
* - Otherwise: search →
|
|
952
|
+
* - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
|
|
757
953
|
*/
|
|
758
954
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
759
955
|
this._connectingPromise = null;
|
|
@@ -770,6 +966,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
770
966
|
});
|
|
771
967
|
};
|
|
772
968
|
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
|
+
}
|
|
773
975
|
this._discoveredDevices.delete(data.connectId);
|
|
774
976
|
this._sessions.delete(data.connectId);
|
|
775
977
|
this.emitter.emit(DEVICE.DISCONNECT, {
|
|
@@ -778,10 +980,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
778
980
|
});
|
|
779
981
|
};
|
|
780
982
|
// Forward connector `ui-event` to the public hw.emitter so consumers only
|
|
781
|
-
// need to subscribe in one place.
|
|
782
|
-
//
|
|
783
|
-
// exists (race during teardown) we drop. All other variants pass through
|
|
784
|
-
// unchanged.
|
|
983
|
+
// need to subscribe in one place. The AppInstallProgress variant re-keys
|
|
984
|
+
// sessionId to connectId via `_sessions`, and drops if no mapping exists.
|
|
785
985
|
this.uiEventForwarder = (event) => {
|
|
786
986
|
if (event.type === EConnectorInteraction.AppInstallProgress) {
|
|
787
987
|
let connectId;
|
|
@@ -826,11 +1026,34 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
826
1026
|
this._jobQueue = new DeviceJobQueue();
|
|
827
1027
|
this.registerEventListeners();
|
|
828
1028
|
}
|
|
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
|
+
}
|
|
829
1047
|
// Transport
|
|
830
1048
|
get activeTransport() {
|
|
831
|
-
return
|
|
1049
|
+
return this._isBleConnection() ? "ble" : "hid";
|
|
832
1050
|
}
|
|
833
1051
|
getAvailableTransports() {
|
|
1052
|
+
if (this.connector.availableTransports) {
|
|
1053
|
+
return this.connector.availableTransports.map(
|
|
1054
|
+
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
834
1057
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
835
1058
|
}
|
|
836
1059
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -847,21 +1070,42 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
847
1070
|
* The next operation will re-discover and re-connect automatically.
|
|
848
1071
|
*/
|
|
849
1072
|
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();
|
|
850
1082
|
this._discoveredDevices.clear();
|
|
851
1083
|
this._sessions.clear();
|
|
1084
|
+
this._sessionTransports.clear();
|
|
1085
|
+
this._pendingOperationBindings.clear();
|
|
1086
|
+
this._pendingPickBindings.clear();
|
|
1087
|
+
this._selectedBleReconnectTargets.clear();
|
|
852
1088
|
this._connectingPromise = null;
|
|
1089
|
+
this._doConnectAbortController = null;
|
|
853
1090
|
this._uiRegistry.reset();
|
|
854
1091
|
this._jobQueue.clear();
|
|
855
1092
|
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
|
+
});
|
|
856
1104
|
}
|
|
857
1105
|
async dispose() {
|
|
858
|
-
this.
|
|
859
|
-
this._jobQueue.clear();
|
|
1106
|
+
await this._resetStateAndDisconnectSessions();
|
|
860
1107
|
this.unregisterEventListeners();
|
|
861
1108
|
this.connector.reset();
|
|
862
|
-
this._discoveredDevices.clear();
|
|
863
|
-
this._sessions.clear();
|
|
864
|
-
this._btcHighIndexConfirmedThisSession = false;
|
|
865
1109
|
this.emitter.removeAllListeners();
|
|
866
1110
|
}
|
|
867
1111
|
uiResponse(response) {
|
|
@@ -871,17 +1115,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
871
1115
|
// Device management
|
|
872
1116
|
// ---------------------------------------------------------------------------
|
|
873
1117
|
async searchDevices(options) {
|
|
1118
|
+
return this._searchDevices(options);
|
|
1119
|
+
}
|
|
1120
|
+
async _searchDevices(options, signal) {
|
|
874
1121
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
875
1122
|
try {
|
|
876
1123
|
if (options?.resetSession) {
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
this.
|
|
880
|
-
|
|
881
|
-
|
|
1124
|
+
await this._resetStateAndDisconnectSessions();
|
|
1125
|
+
} else {
|
|
1126
|
+
await this._connectorTeardownTail;
|
|
1127
|
+
}
|
|
1128
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
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
|
+
});
|
|
882
1143
|
}
|
|
883
|
-
await this._ensureDevicePermission();
|
|
884
|
-
const devices = await this.connector.searchDevices();
|
|
885
1144
|
this._discoveredDevices.clear();
|
|
886
1145
|
for (const d of devices) {
|
|
887
1146
|
if (d.connectId) {
|
|
@@ -889,7 +1148,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
889
1148
|
}
|
|
890
1149
|
}
|
|
891
1150
|
if (this._discoveredDevices.size === 0) {
|
|
892
|
-
await this._ensureDevicePermission();
|
|
1151
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
893
1152
|
}
|
|
894
1153
|
const result = Array.from(this._discoveredDevices.values());
|
|
895
1154
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -908,41 +1167,280 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
908
1167
|
throw err;
|
|
909
1168
|
}
|
|
910
1169
|
}
|
|
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
|
+
}
|
|
911
1191
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
912
|
-
async _evictAllSessions() {
|
|
1192
|
+
async _evictAllSessions(preserveOperationId) {
|
|
1193
|
+
this._operations.endAll("explicit", preserveOperationId);
|
|
913
1194
|
if (this._sessions.size === 0) return;
|
|
914
1195
|
const stale = [...this._sessions.values()];
|
|
915
1196
|
this._sessions.clear();
|
|
916
|
-
for (const sid of stale)
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1197
|
+
for (const sid of stale) this._sessionTransports.delete(sid);
|
|
1198
|
+
await this._runConnectorTeardown(async () => {
|
|
1199
|
+
for (const sid of stale) {
|
|
1200
|
+
try {
|
|
1201
|
+
await this.connector.disconnect(sid);
|
|
1202
|
+
} catch {
|
|
1203
|
+
}
|
|
920
1204
|
}
|
|
921
|
-
}
|
|
1205
|
+
});
|
|
922
1206
|
}
|
|
923
1207
|
static _createDeviceBusyError(method) {
|
|
924
1208
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
925
1209
|
code: HardwareErrorCode3.DeviceBusy
|
|
926
1210
|
});
|
|
927
1211
|
}
|
|
928
|
-
async connectDevice(
|
|
1212
|
+
async connectDevice(searchTargetId) {
|
|
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) {
|
|
929
1392
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
930
1393
|
try {
|
|
931
|
-
|
|
1394
|
+
this._assertConnectorReady("connectDevice");
|
|
1395
|
+
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1396
|
+
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1397
|
+
this._activeConnectionType = discoveredType;
|
|
1398
|
+
}
|
|
1399
|
+
if (this._isBleConnection() && !connectId) {
|
|
932
1400
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
933
1401
|
code: HardwareErrorCode3.DeviceNotFound
|
|
934
1402
|
});
|
|
935
1403
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1404
|
+
await this._ensureDevicePermission(connectId, void 0, signal);
|
|
1405
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
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();
|
|
938
1436
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
this.
|
|
1437
|
+
const resolvedConnectId = session.deviceInfo?.connectId || connectId;
|
|
1438
|
+
this._sessions.set(resolvedConnectId, session.sessionId);
|
|
1439
|
+
this._sessionTransports.set(session.sessionId, this._isBleConnection() ? "ble" : "usb");
|
|
942
1440
|
if (session.deviceInfo) {
|
|
943
|
-
this._discoveredDevices.set(
|
|
1441
|
+
this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
|
|
944
1442
|
}
|
|
945
|
-
const result = success(
|
|
1443
|
+
const result = success(resolvedConnectId);
|
|
946
1444
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
947
1445
|
return result;
|
|
948
1446
|
} catch (err) {
|
|
@@ -955,30 +1453,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
955
1453
|
return failureResult;
|
|
956
1454
|
}
|
|
957
1455
|
}
|
|
958
|
-
async
|
|
1456
|
+
async releaseOperation(operationId) {
|
|
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;
|
|
959
1465
|
debugLog("[LedgerAdapter][REQ]", {
|
|
960
|
-
method: "
|
|
1466
|
+
method: "releaseOperation",
|
|
961
1467
|
connectId,
|
|
962
1468
|
params: { connectId }
|
|
963
1469
|
});
|
|
964
1470
|
try {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
await this.connector.disconnect(sessionId);
|
|
968
|
-
this._sessions.delete(connectId);
|
|
969
|
-
}
|
|
970
|
-
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1471
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1472
|
+
debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
|
|
971
1473
|
} catch (err) {
|
|
972
1474
|
const e = err;
|
|
973
1475
|
debugLog("[LedgerAdapter][RES]", {
|
|
974
|
-
method: "
|
|
1476
|
+
method: "releaseOperation",
|
|
975
1477
|
success: false,
|
|
976
1478
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
977
1479
|
});
|
|
978
1480
|
throw err;
|
|
979
1481
|
}
|
|
980
1482
|
}
|
|
981
|
-
async
|
|
1483
|
+
async _releaseOperationConnection(operation) {
|
|
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
|
+
}
|
|
982
1510
|
debugLog("[LedgerAdapter][REQ]", {
|
|
983
1511
|
method: "getDeviceInfo",
|
|
984
1512
|
connectId,
|
|
@@ -1013,11 +1541,19 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1013
1541
|
success: false,
|
|
1014
1542
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1015
1543
|
});
|
|
1016
|
-
|
|
1544
|
+
return this.errorToFailure(err);
|
|
1017
1545
|
}
|
|
1018
1546
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1547
|
+
/** A bundle's BLE pick that no item got past the wallet check is not kept. */
|
|
1548
|
+
async _dropUncheckedBundlePick(context, signal) {
|
|
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
|
+
}
|
|
1021
1557
|
}
|
|
1022
1558
|
// ---------------------------------------------------------------------------
|
|
1023
1559
|
// Chain call helper
|
|
@@ -1053,13 +1589,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1053
1589
|
if (params && typeof params === "object") {
|
|
1054
1590
|
const {
|
|
1055
1591
|
autoInstallApp,
|
|
1592
|
+
operationId,
|
|
1056
1593
|
passphraseState: _passphraseState,
|
|
1057
1594
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1595
|
+
knownConnections,
|
|
1596
|
+
extra,
|
|
1597
|
+
allowDeviceSelection,
|
|
1598
|
+
supportedTransports,
|
|
1058
1599
|
...rest
|
|
1059
1600
|
} = params;
|
|
1060
1601
|
return {
|
|
1061
1602
|
commonParams: {
|
|
1062
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
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
|
|
1063
1609
|
},
|
|
1064
1610
|
rest
|
|
1065
1611
|
};
|
|
@@ -1235,9 +1781,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1235
1781
|
);
|
|
1236
1782
|
}
|
|
1237
1783
|
// ---------------------------------------------------------------------------
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
|
|
1784
|
+
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1785
|
+
// ---------------------------------------------------------------------------
|
|
1786
|
+
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
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.
|
|
1241
1806
|
// ---------------------------------------------------------------------------
|
|
1242
1807
|
async installApp(connectId, appName) {
|
|
1243
1808
|
try {
|
|
@@ -1323,7 +1888,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1323
1888
|
);
|
|
1324
1889
|
}
|
|
1325
1890
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1326
|
-
this.
|
|
1891
|
+
await this._resetStateAndDisconnectSessions();
|
|
1327
1892
|
}
|
|
1328
1893
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1329
1894
|
if (!result.isGenuine) {
|
|
@@ -1349,10 +1914,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1349
1914
|
if (relayUrl) {
|
|
1350
1915
|
try {
|
|
1351
1916
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1352
|
-
this.
|
|
1917
|
+
await this._resetStateAndDisconnectSessions();
|
|
1353
1918
|
} catch {
|
|
1354
1919
|
this.connector.reset();
|
|
1355
|
-
this.
|
|
1920
|
+
await this._resetStateAndDisconnectSessions();
|
|
1356
1921
|
}
|
|
1357
1922
|
}
|
|
1358
1923
|
}
|
|
@@ -1364,6 +1929,20 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1364
1929
|
this.emitter.off(event, listener);
|
|
1365
1930
|
}
|
|
1366
1931
|
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
|
+
}
|
|
1367
1946
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1368
1947
|
code: HardwareErrorCode3.UserAborted,
|
|
1369
1948
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1374,14 +1953,48 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1374
1953
|
this._lastCancelReason = void 0;
|
|
1375
1954
|
}
|
|
1376
1955
|
}, 2e3);
|
|
1377
|
-
this.
|
|
1378
|
-
|
|
1379
|
-
|
|
1956
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1957
|
+
let operationId;
|
|
1958
|
+
if (isHardwareOperationId(connectId)) {
|
|
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
|
+
});
|
|
1380
1993
|
} else {
|
|
1381
1994
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1382
1995
|
}
|
|
1383
|
-
if (
|
|
1384
|
-
const sessionId = this._sessions.get(
|
|
1996
|
+
if (resolvedConnectId) {
|
|
1997
|
+
const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
|
|
1385
1998
|
void this.connector.cancel(sessionId);
|
|
1386
1999
|
} else {
|
|
1387
2000
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1389,45 +2002,86 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1389
2002
|
if (this._connectingPromise) {
|
|
1390
2003
|
this._doConnectAbortController?.abort(userAbortReason);
|
|
1391
2004
|
}
|
|
2005
|
+
const connectingConnectId = this._connectingConnectId;
|
|
2006
|
+
if (connectingConnectId) void this.connector.cancel(connectingConnectId);
|
|
1392
2007
|
}
|
|
1393
2008
|
// ---------------------------------------------------------------------------
|
|
1394
2009
|
// Chain fingerprint
|
|
1395
2010
|
// ---------------------------------------------------------------------------
|
|
2011
|
+
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1396
2012
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1397
2013
|
try {
|
|
1398
2014
|
const fingerprint = await this._computeChainFingerprint(
|
|
1399
2015
|
chain,
|
|
1400
2016
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1401
2017
|
);
|
|
2018
|
+
if (deviceId) {
|
|
2019
|
+
if (fingerprint !== deviceId) {
|
|
2020
|
+
return failure2(
|
|
2021
|
+
HardwareErrorCode3.DeviceMismatch,
|
|
2022
|
+
formatDeviceMismatchError(deviceId, fingerprint)
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
1402
2026
|
return success(fingerprint);
|
|
1403
2027
|
} catch (err) {
|
|
1404
2028
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
1405
2029
|
return this.errorToFailure(err);
|
|
1406
2030
|
}
|
|
1407
2031
|
}
|
|
1408
|
-
/**
|
|
1409
|
-
|
|
1410
|
-
* Safe to call inside connectorCall without causing queue deadlock.
|
|
1411
|
-
*/
|
|
1412
|
-
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
1413
|
-
if (!deviceId) return { success: true };
|
|
2032
|
+
/** Saves a picked BLE endpoint; `identity` is set when its wallet was just checked. */
|
|
2033
|
+
async _saveSelectedBleBinding(connectId, selectionRequestId, context, operationId, signal, identity) {
|
|
1414
2034
|
try {
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
|
|
2035
|
+
const outcome = await requestSaveDeviceBinding(
|
|
2036
|
+
this.emitter,
|
|
2037
|
+
this._uiRegistry,
|
|
2038
|
+
{
|
|
2039
|
+
selectionRequestId,
|
|
2040
|
+
connection: { transport: "ble", connectId },
|
|
2041
|
+
...identity ? { identity } : {},
|
|
2042
|
+
extra: context?.extra,
|
|
2043
|
+
operationId
|
|
2044
|
+
},
|
|
2045
|
+
signal
|
|
1418
2046
|
);
|
|
1419
|
-
if (
|
|
1420
|
-
|
|
2047
|
+
if (context) context.bindingSaved = outcome.saved;
|
|
2048
|
+
if (!outcome.saved) {
|
|
2049
|
+
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
2050
|
+
connectId,
|
|
2051
|
+
reason: outcome.reason
|
|
2052
|
+
});
|
|
1421
2053
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
2054
|
+
} catch (error) {
|
|
2055
|
+
await this._dropSession(connectId, signal);
|
|
2056
|
+
throw error;
|
|
2057
|
+
} finally {
|
|
2058
|
+
if (this._bindingSelectionRequestId === selectionRequestId) {
|
|
2059
|
+
this._bindingSelectionRequestId = void 0;
|
|
1427
2060
|
}
|
|
1428
|
-
throw err;
|
|
1429
2061
|
}
|
|
1430
2062
|
}
|
|
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
|
+
}
|
|
1431
2085
|
/**
|
|
1432
2086
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
1433
2087
|
*
|
|
@@ -1455,11 +2109,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1455
2109
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
1456
2110
|
} else if (chain === "tron") {
|
|
1457
2111
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2112
|
+
} else if (chain === "zcash") {
|
|
2113
|
+
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
1458
2114
|
} else {
|
|
1459
2115
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
1460
2116
|
}
|
|
1461
2117
|
return deriveDeviceFingerprint(address);
|
|
1462
2118
|
}
|
|
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
|
+
}
|
|
1463
2129
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
1464
2130
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
1465
2131
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
@@ -1469,15 +2135,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1469
2135
|
if (signal?.aborted) {
|
|
1470
2136
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1471
2137
|
}
|
|
2138
|
+
const operationId = this._activeOperationId();
|
|
1472
2139
|
const waitPromise = this._uiRegistry.wait(
|
|
1473
|
-
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2140
|
+
UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2141
|
+
{ operationId }
|
|
1474
2142
|
);
|
|
1475
2143
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
1476
2144
|
type: UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
1477
2145
|
payload: {
|
|
1478
2146
|
vendor: "ledger",
|
|
1479
2147
|
reason: "device-not-found",
|
|
1480
|
-
message: "Please connect and unlock your Ledger device"
|
|
2148
|
+
message: "Please connect and unlock your Ledger device",
|
|
2149
|
+
operationId
|
|
1481
2150
|
}
|
|
1482
2151
|
});
|
|
1483
2152
|
let payload;
|
|
@@ -1538,15 +2207,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1538
2207
|
return { ...params, showOnDevice: true };
|
|
1539
2208
|
}
|
|
1540
2209
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2210
|
+
const operationId = this._activeOperationId();
|
|
1541
2211
|
const waitPromise = this._uiRegistry.wait(
|
|
1542
|
-
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2212
|
+
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2213
|
+
{ operationId }
|
|
1543
2214
|
);
|
|
1544
2215
|
this.emitter.emit(UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
1545
2216
|
type: UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
1546
2217
|
payload: {
|
|
1547
2218
|
vendor: "ledger",
|
|
1548
2219
|
path,
|
|
1549
|
-
accountIndex
|
|
2220
|
+
accountIndex,
|
|
2221
|
+
operationId
|
|
1550
2222
|
}
|
|
1551
2223
|
});
|
|
1552
2224
|
try {
|
|
@@ -1563,12 +2235,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1563
2235
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
1564
2236
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
1565
2237
|
async _waitForInstallAppConfirm(appName) {
|
|
2238
|
+
const operationId = this._activeOperationId();
|
|
1566
2239
|
const waitPromise = this._uiRegistry.wait(
|
|
1567
|
-
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2240
|
+
UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2241
|
+
{ operationId }
|
|
1568
2242
|
);
|
|
1569
2243
|
this.emitter.emit(UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
1570
2244
|
type: UI_REQUEST.REQUEST_INSTALL_APP,
|
|
1571
|
-
payload: { vendor: "ledger", appName }
|
|
2245
|
+
payload: { vendor: "ledger", appName, operationId }
|
|
1572
2246
|
});
|
|
1573
2247
|
try {
|
|
1574
2248
|
const payload = await waitPromise;
|
|
@@ -1584,16 +2258,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1584
2258
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
1585
2259
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
1586
2260
|
// doesn't kill caller B's await.
|
|
1587
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2261
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
1588
2262
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
1589
|
-
|
|
2263
|
+
this._assertConnectorReady("connectDevice");
|
|
2264
|
+
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1590
2265
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1591
2266
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1592
2267
|
});
|
|
1593
2268
|
}
|
|
1594
2269
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
1595
2270
|
if (!connectId && this._sessions.size > 0) {
|
|
1596
|
-
if (!
|
|
2271
|
+
if (!this._isBleConnection() && this._sessions.size > 1) {
|
|
1597
2272
|
throw Object.assign(
|
|
1598
2273
|
new Error(
|
|
1599
2274
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -1603,15 +2278,24 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1603
2278
|
}
|
|
1604
2279
|
return this._sessions.keys().next().value;
|
|
1605
2280
|
}
|
|
1606
|
-
if (!this._connectingPromise) {
|
|
1607
|
-
|
|
1608
|
-
|
|
2281
|
+
if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
|
|
2282
|
+
const controller = new AbortController();
|
|
2283
|
+
this._doConnectAbortController = controller;
|
|
2284
|
+
const innerSignal = controller.signal;
|
|
1609
2285
|
this._connectingPromise = (async () => {
|
|
1610
2286
|
try {
|
|
1611
|
-
return await this._doConnect(
|
|
2287
|
+
return await this._doConnect(
|
|
2288
|
+
innerSignal,
|
|
2289
|
+
connectId,
|
|
2290
|
+
allowUsbEphemeralFallback,
|
|
2291
|
+
preserveOperationId,
|
|
2292
|
+
context
|
|
2293
|
+
);
|
|
1612
2294
|
} finally {
|
|
1613
|
-
this.
|
|
1614
|
-
|
|
2295
|
+
if (this._doConnectAbortController === controller) {
|
|
2296
|
+
this._connectingPromise = null;
|
|
2297
|
+
this._doConnectAbortController = null;
|
|
2298
|
+
}
|
|
1615
2299
|
}
|
|
1616
2300
|
})();
|
|
1617
2301
|
}
|
|
@@ -1620,22 +2304,57 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1620
2304
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1621
2305
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1622
2306
|
// throw DeviceNotFound so the user is kicked out of the loop.
|
|
1623
|
-
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
2307
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
2308
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2309
|
+
if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
|
|
2310
|
+
this._activeConnectionType = "usb";
|
|
2311
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
|
|
2312
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2313
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2314
|
+
(connection) => connection.transport === "usb"
|
|
2315
|
+
);
|
|
2316
|
+
const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
|
|
2317
|
+
if (usbDevices.length > 0 && (usbTarget || !targetConnectId || allowUsbEphemeralFallback)) {
|
|
2318
|
+
return this._connectFirstOrSelect(
|
|
2319
|
+
usbDevices,
|
|
2320
|
+
usbTarget,
|
|
2321
|
+
allowUsbEphemeralFallback,
|
|
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
|
+
});
|
|
1638
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
|
+
});
|
|
2345
|
+
}
|
|
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);
|
|
1639
2358
|
}
|
|
1640
2359
|
let confirms = 0;
|
|
1641
2360
|
while (!internalSignal.aborted) {
|
|
@@ -1643,22 +2362,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1643
2362
|
type: EConnectorInteraction.Searching,
|
|
1644
2363
|
payload: { sessionId: "" }
|
|
1645
2364
|
});
|
|
1646
|
-
let devices = await this.
|
|
2365
|
+
let devices = await this._searchDevices(void 0, internalSignal);
|
|
2366
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1647
2367
|
if (devices.length === 0) {
|
|
1648
2368
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1649
2369
|
await new Promise((resolve) => {
|
|
1650
2370
|
setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
1651
2371
|
});
|
|
1652
|
-
|
|
2372
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2373
|
+
devices = await this._searchDevices(void 0, internalSignal);
|
|
2374
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1653
2375
|
if (devices.length > 0) break;
|
|
1654
2376
|
}
|
|
1655
2377
|
}
|
|
1656
|
-
if (devices.length > 0) {
|
|
2378
|
+
if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
|
|
1657
2379
|
try {
|
|
1658
2380
|
return await this._connectFirstOrSelect(
|
|
1659
2381
|
devices,
|
|
1660
2382
|
targetConnectId,
|
|
1661
|
-
allowUsbEphemeralFallback
|
|
2383
|
+
allowUsbEphemeralFallback,
|
|
2384
|
+
preserveOperationId,
|
|
2385
|
+
context,
|
|
2386
|
+
internalSignal
|
|
1662
2387
|
);
|
|
1663
2388
|
} catch (err) {
|
|
1664
2389
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -1687,45 +2412,147 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1687
2412
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1688
2413
|
throw new Error("_doConnect aborted");
|
|
1689
2414
|
}
|
|
1690
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2415
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
|
|
2416
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1691
2417
|
if (targetConnectId) {
|
|
1692
2418
|
const target = devices.find(
|
|
1693
2419
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1694
2420
|
);
|
|
1695
2421
|
if (target) {
|
|
1696
|
-
return this._connectDeviceOrThrow(target.connectId);
|
|
2422
|
+
return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
|
|
1697
2423
|
}
|
|
1698
|
-
if (!
|
|
2424
|
+
if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
1699
2425
|
debugLog(
|
|
1700
2426
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
1701
2427
|
);
|
|
1702
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2428
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1703
2429
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
2430
|
+
if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
|
|
2431
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
2432
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
2433
|
+
});
|
|
2434
|
+
if (this._isBleConnection()) {
|
|
2435
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2436
|
+
}
|
|
2437
|
+
throw err;
|
|
1709
2438
|
}
|
|
1710
|
-
throw err;
|
|
1711
2439
|
}
|
|
1712
|
-
|
|
2440
|
+
const requiresBleSelection = this._isBleConnection();
|
|
2441
|
+
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1713
2442
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1714
2443
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1715
2444
|
});
|
|
1716
2445
|
}
|
|
1717
|
-
if (
|
|
1718
|
-
|
|
2446
|
+
if (requiresBleSelection && context?.allowDeviceSelection !== false) {
|
|
2447
|
+
const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
|
|
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);
|
|
1719
2546
|
}
|
|
1720
2547
|
if (devices.length !== 1) {
|
|
1721
2548
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
1722
2549
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1723
2550
|
});
|
|
1724
2551
|
}
|
|
1725
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2552
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1726
2553
|
}
|
|
1727
|
-
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1728
|
-
const result = await this.
|
|
2554
|
+
async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
|
|
2555
|
+
const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
|
|
1729
2556
|
if (!result.success) {
|
|
1730
2557
|
const payload = result.payload;
|
|
1731
2558
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -1736,7 +2563,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1736
2563
|
}
|
|
1737
2564
|
throw rethrow;
|
|
1738
2565
|
}
|
|
1739
|
-
return
|
|
2566
|
+
return result.payload;
|
|
1740
2567
|
}
|
|
1741
2568
|
/**
|
|
1742
2569
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -1766,33 +2593,145 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1766
2593
|
* lives in one place.
|
|
1767
2594
|
*/
|
|
1768
2595
|
async _callConnector(sessionId, method, params, signal) {
|
|
1769
|
-
|
|
2596
|
+
this._assertConnectorReady(method);
|
|
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
|
+
}
|
|
1770
2606
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
1771
2607
|
return this._unwrapConnectorResult(result);
|
|
1772
2608
|
}
|
|
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
|
+
}
|
|
1773
2661
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
1774
|
-
|
|
1775
|
-
|
|
2662
|
+
const positionalOperationId = isHardwareOperationId(connectId) ? connectId : void 0;
|
|
2663
|
+
if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
|
|
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 });
|
|
1776
2683
|
try {
|
|
1777
2684
|
const result = await this._jobQueue.enqueue(
|
|
1778
2685
|
queueKey,
|
|
1779
|
-
async (signal) =>
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2686
|
+
async (signal) => {
|
|
2687
|
+
if (operationId) this._activeOperationJobs.add(operationId);
|
|
2688
|
+
try {
|
|
2689
|
+
return await this._runConnectorCall(
|
|
2690
|
+
effectiveConnectId,
|
|
2691
|
+
method,
|
|
2692
|
+
params,
|
|
2693
|
+
signal,
|
|
2694
|
+
fingerprint,
|
|
2695
|
+
permissionDeviceId,
|
|
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
|
+
},
|
|
1789
2724
|
{
|
|
1790
2725
|
label: method,
|
|
1791
2726
|
rejectIfBusy: true,
|
|
1792
2727
|
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
1793
2728
|
}
|
|
1794
2729
|
);
|
|
1795
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
2730
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
2731
|
+
method,
|
|
2732
|
+
success: true,
|
|
2733
|
+
payload: redactResultForLog(result)
|
|
2734
|
+
});
|
|
1796
2735
|
return result;
|
|
1797
2736
|
} catch (err) {
|
|
1798
2737
|
const e = err;
|
|
@@ -1806,15 +2745,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1806
2745
|
}
|
|
1807
2746
|
});
|
|
1808
2747
|
throw err;
|
|
2748
|
+
} finally {
|
|
2749
|
+
releaseOperationRetention?.();
|
|
1809
2750
|
}
|
|
1810
2751
|
}
|
|
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
|
+
}
|
|
1811
2756
|
/**
|
|
1812
2757
|
* Race a promise against an abort signal. On abort, rejects with
|
|
1813
2758
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
1814
2759
|
*/
|
|
1815
2760
|
_abortable(signal, promise) {
|
|
1816
|
-
const getAbortReason = () =>
|
|
2761
|
+
const getAbortReason = () => this._abortReason(signal);
|
|
1817
2762
|
if (signal.aborted) {
|
|
2763
|
+
void promise.catch(() => void 0);
|
|
1818
2764
|
return Promise.reject(getAbortReason());
|
|
1819
2765
|
}
|
|
1820
2766
|
return new Promise((resolve, reject) => {
|
|
@@ -1841,13 +2787,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1841
2787
|
}
|
|
1842
2788
|
}
|
|
1843
2789
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
1844
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2790
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
|
|
1845
2791
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2792
|
+
if (!operationId) {
|
|
2793
|
+
await this._ensureDevicePermission(
|
|
2794
|
+
connectId,
|
|
2795
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
2796
|
+
signal
|
|
2797
|
+
);
|
|
2798
|
+
}
|
|
1851
2799
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1852
2800
|
let effectiveParams = params;
|
|
1853
2801
|
if (method === "btcGetPublicKey") {
|
|
@@ -1861,19 +2809,67 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1861
2809
|
effectiveParams = gatedParams;
|
|
1862
2810
|
}
|
|
1863
2811
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
1864
|
-
|
|
1865
|
-
|
|
2812
|
+
let businessCallStarted = false;
|
|
2813
|
+
const selectedBleTarget = connectId ? this._selectedBleReconnectTargets.get(connectId) : void 0;
|
|
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,
|
|
1866
2836
|
signal,
|
|
1867
|
-
allowUsbEphemeralFallback
|
|
2837
|
+
allowUsbEphemeralFallback,
|
|
2838
|
+
void 0,
|
|
2839
|
+
connectionAttempt
|
|
1868
2840
|
);
|
|
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
|
+
}
|
|
1869
2848
|
const sessionId = this._sessions.get(resolvedConnectId);
|
|
2849
|
+
if (sessionId && installContext && !installContext.connection) {
|
|
2850
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2851
|
+
}
|
|
1870
2852
|
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
|
+
}
|
|
1871
2861
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
1872
2862
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
1873
2863
|
});
|
|
1874
2864
|
}
|
|
1875
2865
|
try {
|
|
1876
|
-
|
|
2866
|
+
const isBleSession = this._sessionTransports.get(sessionId) === "ble";
|
|
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) {
|
|
1877
2873
|
const fp = await this._abortable(
|
|
1878
2874
|
signal,
|
|
1879
2875
|
this._verifyDeviceFingerprintWithSession(
|
|
@@ -1883,14 +2879,68 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1883
2879
|
)
|
|
1884
2880
|
);
|
|
1885
2881
|
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
|
+
}
|
|
1886
2891
|
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
1887
2892
|
code: HardwareErrorCode3.DeviceMismatch
|
|
1888
2893
|
});
|
|
1889
2894
|
}
|
|
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
|
+
);
|
|
1890
2914
|
}
|
|
1891
|
-
|
|
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
|
+
}
|
|
2922
|
+
businessCallStarted = true;
|
|
2923
|
+
const result = await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2924
|
+
if (walletCheck) walletCheck.derived = true;
|
|
2925
|
+
return result;
|
|
1892
2926
|
} catch (err) {
|
|
1893
2927
|
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
|
+
}
|
|
1894
2944
|
const errObj = err;
|
|
1895
2945
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1896
2946
|
message: errObj?.message,
|
|
@@ -1902,6 +2952,64 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1902
2952
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1903
2953
|
isStuckApp: isStuckAppStateError(err)
|
|
1904
2954
|
});
|
|
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
|
+
}
|
|
1905
3013
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
1906
3014
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
|
|
1907
3015
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -1957,157 +3065,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1957
3065
|
fingerprint,
|
|
1958
3066
|
permissionDeviceId,
|
|
1959
3067
|
commonParams,
|
|
1960
|
-
installContext
|
|
3068
|
+
installContext,
|
|
3069
|
+
operationId
|
|
1961
3070
|
);
|
|
1962
3071
|
}
|
|
1963
3072
|
}
|
|
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
|
|
2001
|
-
);
|
|
2002
|
-
throw retryErr;
|
|
2003
|
-
}
|
|
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
|
-
}
|
|
2074
3073
|
throw err;
|
|
2075
3074
|
}
|
|
2076
3075
|
}
|
|
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
|
-
}
|
|
2111
3076
|
_sleepAbortable(ms, signal) {
|
|
2112
3077
|
return new Promise((resolve, reject) => {
|
|
2113
3078
|
if (signal.aborted) {
|
|
@@ -2126,111 +3091,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2126
3091
|
});
|
|
2127
3092
|
}
|
|
2128
3093
|
/**
|
|
2129
|
-
*
|
|
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
|
|
3094
|
+
* Ensure OS-level Bluetooth/USB permission; times out after 60s if the host never
|
|
3095
|
+
* replies. Without connectId the request is environment-level, else device-level.
|
|
2221
3096
|
*/
|
|
2222
3097
|
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
2223
3098
|
if (signal?.aborted) {
|
|
2224
3099
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2225
3100
|
}
|
|
2226
3101
|
const transportType = this.activeTransport ?? "hid";
|
|
3102
|
+
const operationId = this._activeOperationId();
|
|
2227
3103
|
const waitPromise = this._uiRegistry.wait(
|
|
2228
3104
|
UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2229
|
-
{ timeoutMs: 6e4 }
|
|
3105
|
+
{ timeoutMs: 6e4, operationId }
|
|
2230
3106
|
);
|
|
2231
3107
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
2232
3108
|
type: UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2233
|
-
payload: { transportType, connectId, deviceId }
|
|
3109
|
+
payload: { transportType, connectId, deviceId, operationId }
|
|
2234
3110
|
});
|
|
2235
3111
|
let response;
|
|
2236
3112
|
const onAbort = () => {
|
|
@@ -2276,7 +3152,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2276
3152
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
2277
3153
|
const e = err;
|
|
2278
3154
|
const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
2279
|
-
return ledgerFailure(
|
|
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
|
+
);
|
|
2280
3164
|
}
|
|
2281
3165
|
const mapped = mapLedgerError(err);
|
|
2282
3166
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -2303,7 +3187,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2303
3187
|
deviceId: device.deviceId,
|
|
2304
3188
|
connectId: device.connectId,
|
|
2305
3189
|
label: device.name,
|
|
2306
|
-
connectionType: this.connector.connectionType,
|
|
3190
|
+
connectionType: device.connectionType ?? this.connector.connectionType,
|
|
2307
3191
|
rssi: device.rssi,
|
|
2308
3192
|
isConnectable: device.isConnectable,
|
|
2309
3193
|
serialNumber: device.serialNumber,
|
|
@@ -2329,8 +3213,8 @@ var LedgerAdapter = _LedgerAdapter;
|
|
|
2329
3213
|
|
|
2330
3214
|
// src/connector/LedgerConnectorBase.ts
|
|
2331
3215
|
import {
|
|
2332
|
-
EConnectorInteraction as
|
|
2333
|
-
HardwareErrorCode as
|
|
3216
|
+
EConnectorInteraction as EConnectorInteraction3,
|
|
3217
|
+
HardwareErrorCode as HardwareErrorCode7,
|
|
2334
3218
|
isKnownNonTargetHardwareVendor,
|
|
2335
3219
|
serializeConnectorError
|
|
2336
3220
|
} from "@onekeyfe/hwk-adapter-core";
|
|
@@ -2603,7 +3487,7 @@ var LedgerDeviceManager = class {
|
|
|
2603
3487
|
|
|
2604
3488
|
// src/signer/SignerManager.ts
|
|
2605
3489
|
import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
|
|
2606
|
-
import { ContextModuleBuilder } from "@ledgerhq/context-module";
|
|
3490
|
+
import { ContextModuleBuilder, ContextModuleChainID } from "@ledgerhq/context-module";
|
|
2607
3491
|
|
|
2608
3492
|
// src/signer/SignerEth.ts
|
|
2609
3493
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
@@ -2869,7 +3753,7 @@ var SignerManager = class _SignerManager {
|
|
|
2869
3753
|
return (args) => new SignerEthBuilder(args);
|
|
2870
3754
|
}
|
|
2871
3755
|
static _createContextModule() {
|
|
2872
|
-
const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3756
|
+
const contextModule = new ContextModuleBuilder({}).setChain(ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
|
|
2873
3757
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2874
3758
|
}
|
|
2875
3759
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -2907,6 +3791,27 @@ function collapseSignerInteraction(interaction) {
|
|
|
2907
3791
|
return EConnectorInteraction2.ConfirmOnDevice;
|
|
2908
3792
|
}
|
|
2909
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);
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
2910
3815
|
|
|
2911
3816
|
// src/connector/chains/evm.ts
|
|
2912
3817
|
async function evmGetAddress(ctx, sessionId, params) {
|
|
@@ -3131,7 +4036,21 @@ function _purposeToTemplate(purpose, DDT) {
|
|
|
3131
4036
|
});
|
|
3132
4037
|
}
|
|
3133
4038
|
}
|
|
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
|
+
}
|
|
3134
4046
|
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
|
+
}
|
|
3135
4054
|
const btcSigner = await _createBtcSigner(ctx, sessionId);
|
|
3136
4055
|
const path = normalizePath(params.path);
|
|
3137
4056
|
try {
|
|
@@ -3162,6 +4081,7 @@ async function btcGetAddress(ctx, sessionId, params) {
|
|
|
3162
4081
|
}
|
|
3163
4082
|
}
|
|
3164
4083
|
async function btcGetPublicKey(ctx, sessionId, params) {
|
|
4084
|
+
assertBitcoinMainnet(params.coin);
|
|
3165
4085
|
const btcSigner = await _createBtcSigner(ctx, sessionId);
|
|
3166
4086
|
const path = normalizePath(params.path);
|
|
3167
4087
|
debugLog("[LedgerConnector] btcGetPublicKey called, path:", path, "sessionId:", sessionId);
|
|
@@ -3339,7 +4259,8 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
3339
4259
|
}
|
|
3340
4260
|
|
|
3341
4261
|
// src/connector/chains/sol.ts
|
|
3342
|
-
import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
|
|
4262
|
+
import { bytesToHex, hexToBytes as hexToBytes3, prepareSolanaOffchainMessageV1 } from "@onekeyfe/hwk-adapter-core";
|
|
4263
|
+
import bs58 from "bs58";
|
|
3343
4264
|
|
|
3344
4265
|
// src/signer/SignerSol.ts
|
|
3345
4266
|
var SignerSol = class {
|
|
@@ -3423,8 +4344,21 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3423
4344
|
const path = normalizePath(params.path);
|
|
3424
4345
|
const messageBytes = hexToBytes3(params.message);
|
|
3425
4346
|
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
|
+
}
|
|
3426
4360
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
3427
|
-
return { signature: result.signature };
|
|
4361
|
+
return { signature: decodeBase58EnvelopeSignature(result.signature) };
|
|
3428
4362
|
} catch (err) {
|
|
3429
4363
|
ctx.invalidateSession(sessionId);
|
|
3430
4364
|
throw ctx.wrapError(err);
|
|
@@ -3432,11 +4366,27 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3432
4366
|
ctx.clearCanceller(sessionId);
|
|
3433
4367
|
}
|
|
3434
4368
|
}
|
|
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
|
+
}
|
|
3435
4383
|
async function _createSolSigner(ctx, sessionId) {
|
|
3436
4384
|
const dmk = await ctx.getOrCreateDmk();
|
|
3437
|
-
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit(
|
|
4385
|
+
const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
|
|
4386
|
+
"@ledgerhq/context-module"
|
|
4387
|
+
);
|
|
3438
4388
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
3439
|
-
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4389
|
+
const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
|
|
3440
4390
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
3441
4391
|
const signer = new SignerSol(sdkSigner);
|
|
3442
4392
|
signer.onInteraction = (interaction) => {
|
|
@@ -3452,361 +4402,147 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
3452
4402
|
return signer;
|
|
3453
4403
|
}
|
|
3454
4404
|
|
|
3455
|
-
// src/connector/chains/
|
|
3456
|
-
import {
|
|
3457
|
-
import Trx from "@ledgerhq/hw-app-trx";
|
|
3458
|
-
|
|
3459
|
-
// src/connector/chains/legacyChainCall.ts
|
|
3460
|
-
import { EConnectorInteraction as EConnectorInteraction3 } from "@onekeyfe/hwk-adapter-core";
|
|
4405
|
+
// src/connector/chains/zcash.ts
|
|
4406
|
+
import { bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
|
|
3461
4407
|
|
|
3462
|
-
// src/
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
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;
|
|
3486
|
-
}
|
|
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);
|
|
4408
|
+
// src/signer/SignerZcash.ts
|
|
4409
|
+
var SignerZcash = class {
|
|
4410
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4411
|
+
constructor(_sdk) {
|
|
4412
|
+
this._sdk = _sdk;
|
|
3528
4413
|
}
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
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
|
-
}
|
|
4414
|
+
/** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
|
|
4415
|
+
async getFullViewingKey(derivationPath, options) {
|
|
4416
|
+
const action = this._sdk.getFullViewingKey(derivationPath, options);
|
|
4417
|
+
return deviceActionToPromise(
|
|
4418
|
+
action,
|
|
4419
|
+
this.onInteraction,
|
|
4420
|
+
void 0,
|
|
4421
|
+
this.onRegisterCanceller
|
|
3565
4422
|
);
|
|
3566
4423
|
}
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
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})`
|
|
4424
|
+
/** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
|
|
4425
|
+
async getShieldedAddress(derivationPath, options) {
|
|
4426
|
+
const action = this._sdk.getShieldedAddress(derivationPath, options);
|
|
4427
|
+
return deviceActionToPromise(
|
|
4428
|
+
action,
|
|
4429
|
+
this.onInteraction,
|
|
4430
|
+
void 0,
|
|
4431
|
+
this.onRegisterCanceller
|
|
3629
4432
|
);
|
|
3630
4433
|
}
|
|
3631
|
-
_isDashboard(appName) {
|
|
3632
|
-
return appName === DASHBOARD_APP_NAME;
|
|
3633
|
-
}
|
|
3634
|
-
_wait() {
|
|
3635
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3636
|
-
}
|
|
3637
4434
|
};
|
|
3638
4435
|
|
|
3639
|
-
// src/connector/chains/
|
|
3640
|
-
function
|
|
3641
|
-
|
|
4436
|
+
// src/connector/chains/zcash.ts
|
|
4437
|
+
async function zcashGetFullViewingKey(ctx, sessionId, params) {
|
|
4438
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
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
|
+
});
|
|
3642
4448
|
}
|
|
3643
|
-
async function
|
|
3644
|
-
const
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
type: EConnectorInteraction3.ConfirmOpenApp,
|
|
3650
|
-
payload: { sessionId }
|
|
4449
|
+
async function zcashGetShieldedAddress(ctx, sessionId, params) {
|
|
4450
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4451
|
+
const path = normalizePath(params.path);
|
|
4452
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4453
|
+
const result = await signer.getShieldedAddress(path, {
|
|
4454
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
3651
4455
|
});
|
|
3652
|
-
|
|
3653
|
-
|
|
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
|
-
}
|
|
4456
|
+
return { address: result.address, path: params.path };
|
|
4457
|
+
});
|
|
3719
4458
|
}
|
|
3720
|
-
async function
|
|
4459
|
+
async function _createZcashSigner(ctx, sessionId) {
|
|
3721
4460
|
const dmk = await ctx.getOrCreateDmk();
|
|
3722
|
-
const
|
|
3723
|
-
|
|
4461
|
+
const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
|
|
4462
|
+
const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
|
|
4463
|
+
return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
|
|
3724
4464
|
}
|
|
3725
4465
|
|
|
3726
|
-
// src/
|
|
3727
|
-
import
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
4466
|
+
// src/connector/chains/tron.ts
|
|
4467
|
+
import { HardwareErrorCode as HardwareErrorCode6, bytesToHex as bytesToHex3, hexToBytes as hexToBytes4 } from "@onekeyfe/hwk-adapter-core";
|
|
4468
|
+
|
|
4469
|
+
// src/signer/SignerTron.ts
|
|
4470
|
+
var SignerTron = class {
|
|
4471
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4472
|
+
constructor(_sdk) {
|
|
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 };
|
|
3733
4485
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
}
|
|
3744
|
-
result.set(statusCode, data.length);
|
|
3745
|
-
return result;
|
|
4486
|
+
/** Sign a protobuf-encoded raw transaction. */
|
|
4487
|
+
async signTransaction(derivationPath, transaction, options) {
|
|
4488
|
+
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
4489
|
+
return deviceActionToPromise(
|
|
4490
|
+
action,
|
|
4491
|
+
this.onInteraction,
|
|
4492
|
+
void 0,
|
|
4493
|
+
this.onRegisterCanceller
|
|
4494
|
+
);
|
|
3746
4495
|
}
|
|
3747
|
-
|
|
4496
|
+
/** Sign a personal message (TIP-191). */
|
|
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
|
+
);
|
|
3748
4505
|
}
|
|
3749
4506
|
};
|
|
3750
4507
|
|
|
3751
4508
|
// src/connector/chains/tron.ts
|
|
3752
4509
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4510
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3753
4511
|
const path = normalizePath(params.path);
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
{
|
|
3759
|
-
|
|
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
|
-
);
|
|
4512
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4513
|
+
const result = await tronSigner.getAddress(path, {
|
|
4514
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4515
|
+
});
|
|
4516
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
4517
|
+
});
|
|
3770
4518
|
}
|
|
3771
4519
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
3772
4520
|
if (!params.rawTxHex) {
|
|
3773
4521
|
throw Object.assign(
|
|
3774
4522
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
3775
|
-
{ code:
|
|
4523
|
+
{ code: HardwareErrorCode6.InvalidParams }
|
|
3776
4524
|
);
|
|
3777
4525
|
}
|
|
4526
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3778
4527
|
const path = normalizePath(params.path);
|
|
3779
|
-
return
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
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
|
-
);
|
|
4528
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4529
|
+
const signature = await tronSigner.signTransaction(path, hexToBytes4(params.rawTxHex));
|
|
4530
|
+
return { signature: bytesToHex3(signature) };
|
|
4531
|
+
});
|
|
3793
4532
|
}
|
|
3794
4533
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4534
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3795
4535
|
const path = normalizePath(params.path);
|
|
3796
|
-
return
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
async (sid) => {
|
|
3801
|
-
const trx = await _createTrx(ctx, sid);
|
|
3802
|
-
const signature = await trx.signPersonalMessage(path, params.messageHex);
|
|
3803
|
-
return { signature };
|
|
3804
|
-
}
|
|
3805
|
-
);
|
|
4536
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4537
|
+
const signature = await tronSigner.signPersonalMessage(path, hexToBytes4(params.messageHex));
|
|
4538
|
+
return { signature: bytesToHex3(signature) };
|
|
4539
|
+
});
|
|
3806
4540
|
}
|
|
3807
|
-
async function
|
|
4541
|
+
async function _createTronSigner(ctx, sessionId) {
|
|
3808
4542
|
const dmk = await ctx.getOrCreateDmk();
|
|
3809
|
-
|
|
4543
|
+
const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
|
|
4544
|
+
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4545
|
+
return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
|
|
3810
4546
|
}
|
|
3811
4547
|
|
|
3812
4548
|
// src/device-apps/customActions.ts
|
|
@@ -3985,7 +4721,7 @@ var DeviceApps = class {
|
|
|
3985
4721
|
seTargetId: v.seTargetId,
|
|
3986
4722
|
mcuTargetId: v.mcuTargetId,
|
|
3987
4723
|
seVersion: v.seVersion,
|
|
3988
|
-
seFlagsHex:
|
|
4724
|
+
seFlagsHex: bytesToHex4(v.seFlags),
|
|
3989
4725
|
mcuSephVersion: v.mcuSephVersion,
|
|
3990
4726
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
3991
4727
|
hwVersion: v.hwVersion
|
|
@@ -4059,7 +4795,7 @@ var DeviceApps = class {
|
|
|
4059
4795
|
}
|
|
4060
4796
|
}
|
|
4061
4797
|
};
|
|
4062
|
-
function
|
|
4798
|
+
function bytesToHex4(bytes) {
|
|
4063
4799
|
if (!bytes) return "";
|
|
4064
4800
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4065
4801
|
}
|
|
@@ -4102,10 +4838,11 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4102
4838
|
evm: "Ethereum",
|
|
4103
4839
|
btc: "Bitcoin",
|
|
4104
4840
|
sol: "Solana",
|
|
4105
|
-
tron: "Tron"
|
|
4841
|
+
tron: "Tron",
|
|
4842
|
+
zcash: "Zcash"
|
|
4106
4843
|
};
|
|
4107
4844
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4108
|
-
Object.values(
|
|
4845
|
+
Object.values(HardwareErrorCode7).filter((value) => typeof value === "number")
|
|
4109
4846
|
);
|
|
4110
4847
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4111
4848
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4142,6 +4879,10 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4142
4879
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4143
4880
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4144
4881
|
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");
|
|
4145
4886
|
case "@ledgerhq/context-module":
|
|
4146
4887
|
return import("@ledgerhq/context-module");
|
|
4147
4888
|
default:
|
|
@@ -4201,6 +4942,7 @@ var LedgerConnectorBase = class {
|
|
|
4201
4942
|
this._ctx = {
|
|
4202
4943
|
emit: (event, data) => this._emit(event, data),
|
|
4203
4944
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4945
|
+
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4204
4946
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4205
4947
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4206
4948
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -4300,7 +5042,7 @@ var LedgerConnectorBase = class {
|
|
|
4300
5042
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
4301
5043
|
);
|
|
4302
5044
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
4303
|
-
err.code =
|
|
5045
|
+
err.code = HardwareErrorCode7.BlePairingTimeout;
|
|
4304
5046
|
reject(err);
|
|
4305
5047
|
}, HANG_CEILING_MS);
|
|
4306
5048
|
});
|
|
@@ -4333,7 +5075,7 @@ var LedgerConnectorBase = class {
|
|
|
4333
5075
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
4334
5076
|
);
|
|
4335
5077
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
4336
|
-
err.code =
|
|
5078
|
+
err.code = HardwareErrorCode7.DeviceNotFound;
|
|
4337
5079
|
throw err;
|
|
4338
5080
|
};
|
|
4339
5081
|
const doConnect = async (path) => {
|
|
@@ -4390,14 +5132,14 @@ var LedgerConnectorBase = class {
|
|
|
4390
5132
|
this._resetSignersAndSessions();
|
|
4391
5133
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
4392
5134
|
const tag = err?._tag;
|
|
4393
|
-
if (isKnownConnectionTag(tag)) {
|
|
5135
|
+
if (isKnownConnectionTag(tag) && !isConnectionOpeningTag(tag)) {
|
|
4394
5136
|
throw err;
|
|
4395
5137
|
}
|
|
4396
5138
|
const wrapped = new Error(
|
|
4397
5139
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
4398
5140
|
);
|
|
4399
5141
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
4400
|
-
wrapped.code =
|
|
5142
|
+
wrapped.code = HardwareErrorCode7.BlePairingTimeout;
|
|
4401
5143
|
wrapped.originalError = err;
|
|
4402
5144
|
throw wrapped;
|
|
4403
5145
|
}
|
|
@@ -4475,7 +5217,7 @@ var LedgerConnectorBase = class {
|
|
|
4475
5217
|
this._unwatchSessionState(sessionId);
|
|
4476
5218
|
this._signerManager?.invalidate(sessionId);
|
|
4477
5219
|
this._cancellers.get(sessionId)?.({
|
|
4478
|
-
code:
|
|
5220
|
+
code: HardwareErrorCode7.DeviceDisconnected,
|
|
4479
5221
|
tag: "DeviceDisconnected",
|
|
4480
5222
|
message: "Device disconnected"
|
|
4481
5223
|
});
|
|
@@ -4501,7 +5243,7 @@ var LedgerConnectorBase = class {
|
|
|
4501
5243
|
success: false,
|
|
4502
5244
|
error: serializeConnectorError(
|
|
4503
5245
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
4504
|
-
code:
|
|
5246
|
+
code: HardwareErrorCode7.DeviceAppStuck,
|
|
4505
5247
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
4506
5248
|
originalError: err
|
|
4507
5249
|
})
|
|
@@ -4513,7 +5255,7 @@ var LedgerConnectorBase = class {
|
|
|
4513
5255
|
success: false,
|
|
4514
5256
|
error: serializeConnectorError(
|
|
4515
5257
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
4516
|
-
code:
|
|
5258
|
+
code: HardwareErrorCode7.TransportError,
|
|
4517
5259
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
4518
5260
|
originalError: err
|
|
4519
5261
|
})
|
|
@@ -4559,6 +5301,11 @@ var LedgerConnectorBase = class {
|
|
|
4559
5301
|
return solSignTransaction(ctx, sessionId, params);
|
|
4560
5302
|
case "solSignMessage":
|
|
4561
5303
|
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);
|
|
4562
5309
|
// TRON
|
|
4563
5310
|
case "tronGetAddress":
|
|
4564
5311
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -4583,7 +5330,7 @@ var LedgerConnectorBase = class {
|
|
|
4583
5330
|
try {
|
|
4584
5331
|
return await apps.install(p.appName, ({ progress }) => {
|
|
4585
5332
|
ctx.emit("ui-event", {
|
|
4586
|
-
type:
|
|
5333
|
+
type: EConnectorInteraction3.AppInstallProgress,
|
|
4587
5334
|
payload: {
|
|
4588
5335
|
sessionId,
|
|
4589
5336
|
appName: p.appName,
|
|
@@ -4592,7 +5339,7 @@ var LedgerConnectorBase = class {
|
|
|
4592
5339
|
});
|
|
4593
5340
|
});
|
|
4594
5341
|
} catch (err) {
|
|
4595
|
-
ctx.
|
|
5342
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4596
5343
|
throw ctx.wrapError(err);
|
|
4597
5344
|
} finally {
|
|
4598
5345
|
ctx.clearCanceller(sessionId);
|
|
@@ -4682,7 +5429,7 @@ var LedgerConnectorBase = class {
|
|
|
4682
5429
|
);
|
|
4683
5430
|
return { isGenuine: output.isGenuine, deviceId };
|
|
4684
5431
|
} catch (err) {
|
|
4685
|
-
ctx.
|
|
5432
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4686
5433
|
throw ctx.wrapError(err);
|
|
4687
5434
|
} finally {
|
|
4688
5435
|
ctx.clearCanceller(sessionId);
|
|
@@ -4801,9 +5548,26 @@ var LedgerConnectorBase = class {
|
|
|
4801
5548
|
}
|
|
4802
5549
|
return this._deviceAppsManager;
|
|
4803
5550
|
}
|
|
5551
|
+
// DeviceAppsManager is a per-call factory with no cached session state, so
|
|
5552
|
+
// there is nothing to invalidate for it here.
|
|
4804
5553
|
_invalidateSession(sessionId) {
|
|
4805
5554
|
this._signerManager?.invalidate(sessionId);
|
|
4806
5555
|
}
|
|
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
|
+
}
|
|
4807
5571
|
/**
|
|
4808
5572
|
* Replace an old session with a new one after app switch.
|
|
4809
5573
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -4888,7 +5652,7 @@ var LedgerConnectorBase = class {
|
|
|
4888
5652
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
4889
5653
|
*/
|
|
4890
5654
|
_ctxForMethod(method) {
|
|
4891
|
-
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5655
|
+
const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
|
|
4892
5656
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
4893
5657
|
if (!defaultAppName) return this._ctx;
|
|
4894
5658
|
return {
|
|
@@ -4922,6 +5686,209 @@ var LedgerConnectorBase = class {
|
|
|
4922
5686
|
return error;
|
|
4923
5687
|
}
|
|
4924
5688
|
};
|
|
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
|
+
};
|
|
4925
5892
|
export {
|
|
4926
5893
|
AppManager,
|
|
4927
5894
|
DmkTransport,
|
|
@@ -4932,6 +5899,7 @@ export {
|
|
|
4932
5899
|
SignerEth,
|
|
4933
5900
|
SignerManager,
|
|
4934
5901
|
SignerSol,
|
|
5902
|
+
SignerZcash,
|
|
4935
5903
|
debugLog,
|
|
4936
5904
|
deviceActionToPromise,
|
|
4937
5905
|
isDeviceLockedError,
|