@onekeyfe/hwk-ledger-adapter 1.2.4 → 1.2.5-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +79 -39
- package/dist/index.d.ts +79 -39
- package/dist/index.js +1742 -843
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1758 -839
- 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,32 @@ 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
|
+
resolveHardwareOperationTarget,
|
|
539
|
+
runAllNetworkGetAddress
|
|
540
|
+
} from "@onekeyfe/hwk-adapter-core";
|
|
456
541
|
|
|
457
542
|
// src/utils/sdkEventBus.ts
|
|
458
543
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -509,65 +594,103 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
509
594
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
510
595
|
function createAllNetworkGetAddress({
|
|
511
596
|
callChain,
|
|
512
|
-
getChainFingerprint
|
|
597
|
+
getChainFingerprint,
|
|
598
|
+
retainOperation,
|
|
599
|
+
errorToFailure,
|
|
600
|
+
createCancelScope
|
|
513
601
|
}) {
|
|
514
602
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
515
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
603
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
604
|
+
method: "allNetworkGetAddress",
|
|
605
|
+
connectId,
|
|
606
|
+
itemCount: params.bundle.length
|
|
607
|
+
});
|
|
608
|
+
const target = resolveHardwareOperationTarget(connectId, params.operationId, "ledger");
|
|
609
|
+
if (!target.success) return target;
|
|
610
|
+
const effectiveTargetId = target.payload.targetId ?? "";
|
|
611
|
+
let releaseOperationRetention;
|
|
612
|
+
try {
|
|
613
|
+
releaseOperationRetention = target.payload.operationId ? retainOperation(target.payload.operationId) : void 0;
|
|
614
|
+
} catch (error) {
|
|
615
|
+
return errorToFailure(error);
|
|
616
|
+
}
|
|
617
|
+
const cancelScope = createCancelScope(
|
|
618
|
+
ledgerQueueKey({ operationId: target.payload.operationId, connectId: effectiveTargetId })
|
|
619
|
+
);
|
|
516
620
|
const installContext = {};
|
|
517
621
|
const commonParams = {
|
|
518
|
-
autoInstallApp: params.autoInstallApp
|
|
622
|
+
autoInstallApp: params.autoInstallApp,
|
|
623
|
+
operationId: target.payload.operationId,
|
|
624
|
+
knownConnections: params.knownConnections,
|
|
625
|
+
extra: params.extra,
|
|
626
|
+
allowDeviceSelection: params.allowDeviceSelection,
|
|
627
|
+
supportedTransports: params.supportedTransports
|
|
519
628
|
};
|
|
520
629
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
630
|
+
try {
|
|
631
|
+
const result = await runAllNetworkGetAddress({
|
|
632
|
+
connectId: effectiveTargetId,
|
|
633
|
+
deviceId: _deviceId,
|
|
634
|
+
params,
|
|
635
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
636
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
637
|
+
callItem: async ({ method, chain, item }) => {
|
|
638
|
+
if (cancelScope.signal.aborted) return buildCancelledFailure(cancelScope.signal);
|
|
639
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
640
|
+
return callAllNetworkMethod(
|
|
641
|
+
callChain,
|
|
642
|
+
effectiveTargetId,
|
|
643
|
+
itemDeviceId,
|
|
644
|
+
method,
|
|
645
|
+
item,
|
|
646
|
+
commonParams,
|
|
647
|
+
installContext
|
|
648
|
+
);
|
|
649
|
+
},
|
|
650
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
651
|
+
getChainFingerprint,
|
|
652
|
+
effectiveTargetId,
|
|
534
653
|
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;
|
|
654
|
+
chain,
|
|
655
|
+
payload,
|
|
656
|
+
chainFingerprints,
|
|
657
|
+
installContext,
|
|
658
|
+
cancelScope.signal
|
|
659
|
+
),
|
|
660
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
661
|
+
buildTopLevelFailure: (response) => {
|
|
662
|
+
const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
|
|
663
|
+
return failure(
|
|
664
|
+
code,
|
|
665
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
666
|
+
response.payload?.params
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
671
|
+
method: "allNetworkGetAddress",
|
|
672
|
+
success: result.success,
|
|
673
|
+
payload: result
|
|
674
|
+
});
|
|
675
|
+
return result;
|
|
676
|
+
} finally {
|
|
677
|
+
cancelScope.release();
|
|
678
|
+
releaseOperationRetention?.();
|
|
679
|
+
}
|
|
563
680
|
};
|
|
564
681
|
}
|
|
682
|
+
function buildCancelledFailure(signal) {
|
|
683
|
+
const reason = signal.reason;
|
|
684
|
+
const code = typeof reason?.code === "number" ? reason.code : HardwareErrorCode2.UserAborted;
|
|
685
|
+
const message = typeof reason?.message === "string" ? reason.message : "";
|
|
686
|
+
return failure(code, message || "All-network get-address cancelled");
|
|
687
|
+
}
|
|
565
688
|
function isTopLevelAllNetworkFailure(response) {
|
|
566
689
|
if (response.success) {
|
|
567
690
|
return false;
|
|
568
691
|
}
|
|
569
692
|
const code = response.payload?.code;
|
|
570
|
-
return code === HardwareErrorCode2.DeviceMismatch || code
|
|
693
|
+
return code === HardwareErrorCode2.DeviceMismatch || isUserRefusal(code) || isConnectionLost(code);
|
|
571
694
|
}
|
|
572
695
|
function getItemDeviceId(item) {
|
|
573
696
|
const { deviceId } = item;
|
|
@@ -597,8 +720,18 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
597
720
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
598
721
|
return coin ? { ...item, coin } : item;
|
|
599
722
|
}
|
|
600
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
601
|
-
const
|
|
723
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context, cancelSignal) {
|
|
724
|
+
const knownFingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || "";
|
|
725
|
+
if (!knownFingerprint && cancelSignal.aborted) {
|
|
726
|
+
const cancelled = buildCancelledFailure(cancelSignal);
|
|
727
|
+
return { ...item, success: false, payload: cancelled.payload };
|
|
728
|
+
}
|
|
729
|
+
const fingerprint = knownFingerprint || await bootstrapChainFingerprint(
|
|
730
|
+
getChainFingerprint,
|
|
731
|
+
context.connection?.connectId ?? connectId,
|
|
732
|
+
chain,
|
|
733
|
+
context
|
|
734
|
+
);
|
|
602
735
|
if (!fingerprint) {
|
|
603
736
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
604
737
|
}
|
|
@@ -619,8 +752,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
619
752
|
}
|
|
620
753
|
};
|
|
621
754
|
}
|
|
622
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
623
|
-
const response = await getChainFingerprint(connectId, chain);
|
|
755
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
|
|
756
|
+
const response = await getChainFingerprint(connectId, chain, context);
|
|
624
757
|
return response.success ? response.payload : "";
|
|
625
758
|
}
|
|
626
759
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -713,6 +846,16 @@ function isLedgerBleDescriptor(connectionType, descriptor) {
|
|
|
713
846
|
function formatDeviceMismatchError(expected, actual) {
|
|
714
847
|
return `Wrong device: expected ${expected}, got ${actual}`;
|
|
715
848
|
}
|
|
849
|
+
var LOG_REDACTED_RESULT_KEYS = ["ufvk", "orchardFvk"];
|
|
850
|
+
function redactResultForLog(result) {
|
|
851
|
+
if (!result || typeof result !== "object") return result;
|
|
852
|
+
const keys = LOG_REDACTED_RESULT_KEYS.filter((key) => key in result);
|
|
853
|
+
if (!keys.length) return result;
|
|
854
|
+
return { ...result, ...Object.fromEntries(keys.map((key) => [key, "[redacted]"])) };
|
|
855
|
+
}
|
|
856
|
+
function isLostConnectionError(err) {
|
|
857
|
+
return isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err);
|
|
858
|
+
}
|
|
716
859
|
var BTC_HIGH_INDEX_THRESHOLD = 100;
|
|
717
860
|
function btcAccountIndexFromPath(path) {
|
|
718
861
|
const segments = path.replace(/^m\//, "").split("/");
|
|
@@ -724,9 +867,31 @@ function btcAccountIndexFromPath(path) {
|
|
|
724
867
|
var _LedgerAdapter = class _LedgerAdapter {
|
|
725
868
|
constructor(connector, options) {
|
|
726
869
|
this.vendor = "ledger";
|
|
870
|
+
// Cancel frees the DMK action and its intent-queue slot, but cannot retract a
|
|
871
|
+
// confirmation screen the device is already showing.
|
|
872
|
+
this.cancelCapability = "stops-waiting";
|
|
727
873
|
this.emitter = new TypedEventEmitter();
|
|
874
|
+
this._operations = new OperationRegistry({
|
|
875
|
+
vendor: "ledger",
|
|
876
|
+
onEnded: (operation, reason) => {
|
|
877
|
+
const binding = this._pendingOperationBindings.get(operation.operationId);
|
|
878
|
+
if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
|
|
879
|
+
this._finishBleBinding("cancelled");
|
|
880
|
+
}
|
|
881
|
+
this._pendingOperationBindings.delete(operation.operationId);
|
|
882
|
+
this.emitter.emit(SDK.OPERATION_ENDED, {
|
|
883
|
+
type: SDK.OPERATION_ENDED,
|
|
884
|
+
payload: { operationId: operation.operationId, reason }
|
|
885
|
+
});
|
|
886
|
+
if (reason === "timeout") {
|
|
887
|
+
void this._releaseOperationConnection(operation);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
});
|
|
728
891
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
729
892
|
this._sessions = /* @__PURE__ */ new Map();
|
|
893
|
+
this._pendingOperationBindings = /* @__PURE__ */ new Map();
|
|
894
|
+
this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
730
895
|
this._uiRegistry = new UiRequestRegistry();
|
|
731
896
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
732
897
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -740,10 +905,31 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
740
905
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
741
906
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
742
907
|
this._doConnectAbortController = null;
|
|
908
|
+
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
909
|
+
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
910
|
+
this._resetPromise = null;
|
|
911
|
+
this._stateGeneration = 0;
|
|
912
|
+
this._connectorTeardownTail = Promise.resolve();
|
|
913
|
+
this._pendingConnectorTeardowns = 0;
|
|
914
|
+
this._activeOperationJobs = /* @__PURE__ */ new Set();
|
|
915
|
+
this._pendingOperationDisconnects = /* @__PURE__ */ new Set();
|
|
743
916
|
this._installProgressLastEmittedValue = -Infinity;
|
|
744
917
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
745
918
|
callChain: this.callChain.bind(this),
|
|
746
|
-
getChainFingerprint: (connectId, chain) =>
|
|
919
|
+
getChainFingerprint: async (connectId, chain, context) => {
|
|
920
|
+
try {
|
|
921
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
922
|
+
chain,
|
|
923
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
924
|
+
);
|
|
925
|
+
return success(fingerprint);
|
|
926
|
+
} catch (error) {
|
|
927
|
+
return this.errorToFailure(error);
|
|
928
|
+
}
|
|
929
|
+
},
|
|
930
|
+
retainOperation: (operationId) => this._operations.retain(operationId),
|
|
931
|
+
errorToFailure: (error) => this.errorToFailure(error),
|
|
932
|
+
createCancelScope: (queueKey) => this._jobQueue.createCancelScope(queueKey)
|
|
747
933
|
});
|
|
748
934
|
// ---------------------------------------------------------------------------
|
|
749
935
|
// Private helpers
|
|
@@ -753,7 +939,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
753
939
|
*
|
|
754
940
|
* - If a session already exists for the given connectId, reuse it.
|
|
755
941
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
756
|
-
* - Otherwise: search →
|
|
942
|
+
* - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
|
|
757
943
|
*/
|
|
758
944
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
759
945
|
this._connectingPromise = null;
|
|
@@ -770,6 +956,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
770
956
|
});
|
|
771
957
|
};
|
|
772
958
|
this.deviceDisconnectHandler = (data) => {
|
|
959
|
+
const activeOperation = this._operations.findActiveByConnectionKey(data.connectId);
|
|
960
|
+
if (activeOperation && this._activeOperationJobs.has(activeOperation.operationId)) {
|
|
961
|
+
this._pendingOperationDisconnects.add(activeOperation.operationId);
|
|
962
|
+
} else {
|
|
963
|
+
this._operations.endByConnectionKey(data.connectId, "disconnect");
|
|
964
|
+
}
|
|
773
965
|
this._discoveredDevices.delete(data.connectId);
|
|
774
966
|
this._sessions.delete(data.connectId);
|
|
775
967
|
this.emitter.emit(DEVICE.DISCONNECT, {
|
|
@@ -778,10 +970,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
778
970
|
});
|
|
779
971
|
};
|
|
780
972
|
// 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.
|
|
973
|
+
// need to subscribe in one place. The AppInstallProgress variant re-keys
|
|
974
|
+
// sessionId to connectId via `_sessions`, and drops if no mapping exists.
|
|
785
975
|
this.uiEventForwarder = (event) => {
|
|
786
976
|
if (event.type === EConnectorInteraction.AppInstallProgress) {
|
|
787
977
|
let connectId;
|
|
@@ -826,11 +1016,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
826
1016
|
this._jobQueue = new DeviceJobQueue();
|
|
827
1017
|
this.registerEventListeners();
|
|
828
1018
|
}
|
|
1019
|
+
_finishBleBinding(status) {
|
|
1020
|
+
const selectionRequestId = this._bindingSelectionRequestId;
|
|
1021
|
+
this._bindingSelectionRequestId = void 0;
|
|
1022
|
+
if (selectionRequestId)
|
|
1023
|
+
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
1024
|
+
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
1025
|
+
payload: { selectionRequestId, status }
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
_isBleConnection() {
|
|
1029
|
+
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
1030
|
+
}
|
|
829
1031
|
// Transport
|
|
830
1032
|
get activeTransport() {
|
|
831
|
-
return
|
|
1033
|
+
return this._isBleConnection() ? "ble" : "hid";
|
|
832
1034
|
}
|
|
833
1035
|
getAvailableTransports() {
|
|
1036
|
+
if (this.connector.availableTransports) {
|
|
1037
|
+
return this.connector.availableTransports.map(
|
|
1038
|
+
(transport) => transport === "ble" ? "ble" : "hid"
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
834
1041
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
835
1042
|
}
|
|
836
1043
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -847,21 +1054,39 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
847
1054
|
* The next operation will re-discover and re-connect automatically.
|
|
848
1055
|
*/
|
|
849
1056
|
resetState() {
|
|
1057
|
+
void this._resetStateAndDisconnectSessions();
|
|
1058
|
+
}
|
|
1059
|
+
_resetStateAndDisconnectSessions() {
|
|
1060
|
+
if (this._resetPromise) return this._resetPromise;
|
|
1061
|
+
const sessionIds = new Set(this._sessions.values());
|
|
1062
|
+
this._stateGeneration += 1;
|
|
1063
|
+
this._finishBleBinding("cancelled");
|
|
1064
|
+
this._operations.endAll("runtime-reset");
|
|
1065
|
+
this._doConnectAbortController?.abort();
|
|
850
1066
|
this._discoveredDevices.clear();
|
|
851
1067
|
this._sessions.clear();
|
|
1068
|
+
this._verifiedBleReconnectTargets.clear();
|
|
852
1069
|
this._connectingPromise = null;
|
|
1070
|
+
this._doConnectAbortController = null;
|
|
853
1071
|
this._uiRegistry.reset();
|
|
854
1072
|
this._jobQueue.clear();
|
|
855
1073
|
this._btcHighIndexConfirmedThisSession = false;
|
|
1074
|
+
const resetPromise = this._runConnectorTeardown(async () => {
|
|
1075
|
+
for (const sessionId of sessionIds) {
|
|
1076
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
this._resetPromise = resetPromise;
|
|
1080
|
+
return resetPromise.finally(() => {
|
|
1081
|
+
if (this._resetPromise === resetPromise) {
|
|
1082
|
+
this._resetPromise = null;
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
856
1085
|
}
|
|
857
1086
|
async dispose() {
|
|
858
|
-
this.
|
|
859
|
-
this._jobQueue.clear();
|
|
1087
|
+
await this._resetStateAndDisconnectSessions();
|
|
860
1088
|
this.unregisterEventListeners();
|
|
861
1089
|
this.connector.reset();
|
|
862
|
-
this._discoveredDevices.clear();
|
|
863
|
-
this._sessions.clear();
|
|
864
|
-
this._btcHighIndexConfirmedThisSession = false;
|
|
865
1090
|
this.emitter.removeAllListeners();
|
|
866
1091
|
}
|
|
867
1092
|
uiResponse(response) {
|
|
@@ -871,17 +1096,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
871
1096
|
// Device management
|
|
872
1097
|
// ---------------------------------------------------------------------------
|
|
873
1098
|
async searchDevices(options) {
|
|
1099
|
+
return this._searchDevices(options);
|
|
1100
|
+
}
|
|
1101
|
+
async _searchDevices(options, signal) {
|
|
874
1102
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
875
1103
|
try {
|
|
876
1104
|
if (options?.resetSession) {
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
this.
|
|
880
|
-
|
|
881
|
-
|
|
1105
|
+
await this._resetStateAndDisconnectSessions();
|
|
1106
|
+
} else {
|
|
1107
|
+
await this._connectorTeardownTail;
|
|
1108
|
+
}
|
|
1109
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1110
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1111
|
+
const stateGeneration = this._stateGeneration;
|
|
1112
|
+
const devices = await this.connector.searchDevices(
|
|
1113
|
+
options?.transportType ? {
|
|
1114
|
+
transportType: options.transportType,
|
|
1115
|
+
waitForAll: options.waitForAllTransports
|
|
1116
|
+
} : void 0
|
|
1117
|
+
);
|
|
1118
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1119
|
+
if (stateGeneration !== this._stateGeneration) {
|
|
1120
|
+
throw createHwkError({
|
|
1121
|
+
code: HardwareErrorCode3.UserAborted,
|
|
1122
|
+
message: "Ledger discovery was reset"
|
|
1123
|
+
});
|
|
882
1124
|
}
|
|
883
|
-
await this._ensureDevicePermission();
|
|
884
|
-
const devices = await this.connector.searchDevices();
|
|
885
1125
|
this._discoveredDevices.clear();
|
|
886
1126
|
for (const d of devices) {
|
|
887
1127
|
if (d.connectId) {
|
|
@@ -889,7 +1129,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
889
1129
|
}
|
|
890
1130
|
}
|
|
891
1131
|
if (this._discoveredDevices.size === 0) {
|
|
892
|
-
await this._ensureDevicePermission();
|
|
1132
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
893
1133
|
}
|
|
894
1134
|
const result = Array.from(this._discoveredDevices.values());
|
|
895
1135
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -908,41 +1148,288 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
908
1148
|
throw err;
|
|
909
1149
|
}
|
|
910
1150
|
}
|
|
1151
|
+
async searchDeviceTargets(options) {
|
|
1152
|
+
const devices = await this.searchDevices(options);
|
|
1153
|
+
return devices.map((device) => ({
|
|
1154
|
+
searchTargetId: device.connectId,
|
|
1155
|
+
searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
|
|
1156
|
+
vendor: "ledger",
|
|
1157
|
+
connectionType: device.connectionType,
|
|
1158
|
+
kind: "physical",
|
|
1159
|
+
label: device.label,
|
|
1160
|
+
model: device.model,
|
|
1161
|
+
modelName: device.modelName,
|
|
1162
|
+
serialNumber: device.serialNumber
|
|
1163
|
+
}));
|
|
1164
|
+
}
|
|
1165
|
+
async listConnectionTargets(options) {
|
|
1166
|
+
const targets = await this.searchDeviceTargets(options);
|
|
1167
|
+
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1168
|
+
...target,
|
|
1169
|
+
targetId: searchTargetId
|
|
1170
|
+
}));
|
|
1171
|
+
}
|
|
911
1172
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
912
|
-
async _evictAllSessions() {
|
|
1173
|
+
async _evictAllSessions(preserveOperationId) {
|
|
1174
|
+
this._operations.endAll("explicit", preserveOperationId);
|
|
913
1175
|
if (this._sessions.size === 0) return;
|
|
914
1176
|
const stale = [...this._sessions.values()];
|
|
915
1177
|
this._sessions.clear();
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1178
|
+
await this._runConnectorTeardown(async () => {
|
|
1179
|
+
for (const sid of stale) {
|
|
1180
|
+
try {
|
|
1181
|
+
await this.connector.disconnect(sid);
|
|
1182
|
+
} catch {
|
|
1183
|
+
}
|
|
920
1184
|
}
|
|
921
|
-
}
|
|
1185
|
+
});
|
|
922
1186
|
}
|
|
923
1187
|
static _createDeviceBusyError(method) {
|
|
924
1188
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
925
1189
|
code: HardwareErrorCode3.DeviceBusy
|
|
926
1190
|
});
|
|
927
1191
|
}
|
|
928
|
-
async connectDevice(
|
|
1192
|
+
async connectDevice(searchTargetId) {
|
|
1193
|
+
try {
|
|
1194
|
+
return await this._jobQueue.enqueue(
|
|
1195
|
+
searchTargetId || "__ledger_connect__",
|
|
1196
|
+
async (signal) => {
|
|
1197
|
+
const connected = await this._connectTarget(searchTargetId, void 0, signal);
|
|
1198
|
+
if (!connected.success) return connected;
|
|
1199
|
+
return this._createOperation(searchTargetId, connected.payload);
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
label: "connectDevice",
|
|
1203
|
+
rejectIfBusy: true,
|
|
1204
|
+
busyError: _LedgerAdapter._createDeviceBusyError("connectDevice")
|
|
1205
|
+
}
|
|
1206
|
+
);
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
return this.errorToFailure(error);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
async bindBleDevice(params) {
|
|
1212
|
+
if (params.identity.vendor !== "ledger" || !params.identity.value) {
|
|
1213
|
+
return failure2(HardwareErrorCode3.InvalidParams, "Ledger wallet identity is required");
|
|
1214
|
+
}
|
|
1215
|
+
const { chain, value: expectedFingerprint } = params.identity;
|
|
1216
|
+
try {
|
|
1217
|
+
return await this._jobQueue.enqueue(
|
|
1218
|
+
expectedFingerprint,
|
|
1219
|
+
async (signal) => {
|
|
1220
|
+
if (!this.getAvailableTransports().includes("ble")) {
|
|
1221
|
+
throw createHwkError({
|
|
1222
|
+
code: HardwareErrorCode3.TransportNotAvailable,
|
|
1223
|
+
message: "Ledger Bluetooth transport is not available"
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
this._activeConnectionType = "ble";
|
|
1227
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1228
|
+
const attempt = {
|
|
1229
|
+
extra: params.extra,
|
|
1230
|
+
bindingReason: "manual-rebind"
|
|
1231
|
+
};
|
|
1232
|
+
try {
|
|
1233
|
+
for (; ; ) {
|
|
1234
|
+
const connectId = await this._connectFirstOrSelect(
|
|
1235
|
+
[],
|
|
1236
|
+
void 0,
|
|
1237
|
+
true,
|
|
1238
|
+
void 0,
|
|
1239
|
+
attempt,
|
|
1240
|
+
signal
|
|
1241
|
+
);
|
|
1242
|
+
const sessionId = this._sessions.get(connectId);
|
|
1243
|
+
if (!sessionId) {
|
|
1244
|
+
throw createHwkError({
|
|
1245
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
1246
|
+
message: "Selected Ledger connection ended"
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
let saved = false;
|
|
1250
|
+
try {
|
|
1251
|
+
const installContext = {
|
|
1252
|
+
connection: { connectId, sessionId }
|
|
1253
|
+
};
|
|
1254
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1255
|
+
chain,
|
|
1256
|
+
(method, callParams) => this._runConnectorCall(
|
|
1257
|
+
connectId,
|
|
1258
|
+
method,
|
|
1259
|
+
callParams,
|
|
1260
|
+
signal,
|
|
1261
|
+
void 0,
|
|
1262
|
+
void 0,
|
|
1263
|
+
{ autoInstallApp: true },
|
|
1264
|
+
installContext
|
|
1265
|
+
)
|
|
1266
|
+
);
|
|
1267
|
+
if (fingerprint !== expectedFingerprint) {
|
|
1268
|
+
attempt.rejectedConnectIds ?? (attempt.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
1269
|
+
attempt.rejectedConnectIds.add(connectId);
|
|
1270
|
+
attempt.rejectedConnectId = connectId;
|
|
1271
|
+
} else {
|
|
1272
|
+
const persisted = await this._publishVerifiedBleBinding(
|
|
1273
|
+
connectId,
|
|
1274
|
+
chain,
|
|
1275
|
+
fingerprint,
|
|
1276
|
+
attempt,
|
|
1277
|
+
void 0,
|
|
1278
|
+
signal
|
|
1279
|
+
);
|
|
1280
|
+
if (!persisted) {
|
|
1281
|
+
throw createHwkError({
|
|
1282
|
+
code: HardwareErrorCode3.UnknownError,
|
|
1283
|
+
message: "Bluetooth binding could not be saved",
|
|
1284
|
+
origin: "host"
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
saved = true;
|
|
1288
|
+
return success(connectId);
|
|
1289
|
+
}
|
|
1290
|
+
} finally {
|
|
1291
|
+
if (!saved && this._sessions.get(connectId) === sessionId) {
|
|
1292
|
+
this._sessions.delete(connectId);
|
|
1293
|
+
const teardown = this._runConnectorTeardown(
|
|
1294
|
+
() => this.connector.disconnect(sessionId)
|
|
1295
|
+
).catch(() => void 0);
|
|
1296
|
+
if (!signal.aborted) await teardown;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1302
|
+
throw error;
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
label: "bindBleDevice",
|
|
1307
|
+
rejectIfBusy: true,
|
|
1308
|
+
busyError: _LedgerAdapter._createDeviceBusyError("bindBleDevice")
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
return this.errorToFailure(error);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
async acquireOperation(connectId, context) {
|
|
1316
|
+
try {
|
|
1317
|
+
return await this._jobQueue.enqueue(
|
|
1318
|
+
connectId || "__ledger_acquire__",
|
|
1319
|
+
async (signal) => {
|
|
1320
|
+
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1321
|
+
const hint = context.knownConnections?.find(
|
|
1322
|
+
(connection) => connection.transport === transport
|
|
1323
|
+
);
|
|
1324
|
+
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1325
|
+
await this._ensureDevicePermission(target, void 0, signal);
|
|
1326
|
+
const attempt = {
|
|
1327
|
+
...context,
|
|
1328
|
+
extra: context.extra ? { ...context.extra } : void 0
|
|
1329
|
+
};
|
|
1330
|
+
try {
|
|
1331
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1332
|
+
target,
|
|
1333
|
+
signal,
|
|
1334
|
+
true,
|
|
1335
|
+
void 0,
|
|
1336
|
+
attempt
|
|
1337
|
+
);
|
|
1338
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1339
|
+
const result = this._createOperation(connectId, resolvedConnectId);
|
|
1340
|
+
if (result.success && attempt.selectedConnection) {
|
|
1341
|
+
this._pendingOperationBindings.set(result.payload, attempt);
|
|
1342
|
+
}
|
|
1343
|
+
return result;
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1346
|
+
throw error;
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
label: "acquireOperation",
|
|
1351
|
+
rejectIfBusy: true,
|
|
1352
|
+
busyError: _LedgerAdapter._createDeviceBusyError("acquireOperation")
|
|
1353
|
+
}
|
|
1354
|
+
);
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
return this.errorToFailure(error);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
_createOperation(searchTargetId, resolvedConnectId) {
|
|
1360
|
+
this._operations.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1361
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1362
|
+
if (sessionId) this._operations.endByConnectionKey(sessionId, "explicit");
|
|
1363
|
+
const connectionType = this._isBleConnection() ? "ble" : "usb";
|
|
1364
|
+
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1365
|
+
vendor: "ledger",
|
|
1366
|
+
model: "unknown",
|
|
1367
|
+
firmwareVersion: "",
|
|
1368
|
+
deviceId: "",
|
|
1369
|
+
connectId: resolvedConnectId,
|
|
1370
|
+
connectionType
|
|
1371
|
+
};
|
|
1372
|
+
const operation = this._operations.create({
|
|
1373
|
+
searchTargetId,
|
|
1374
|
+
connectId: resolvedConnectId,
|
|
1375
|
+
device,
|
|
1376
|
+
connectionType,
|
|
1377
|
+
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1378
|
+
});
|
|
1379
|
+
return success(operation.operationId);
|
|
1380
|
+
}
|
|
1381
|
+
async _connectTarget(connectId, preserveOperationId, signal) {
|
|
929
1382
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
930
1383
|
try {
|
|
931
|
-
|
|
1384
|
+
this._assertConnectorReady("connectDevice");
|
|
1385
|
+
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1386
|
+
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1387
|
+
this._activeConnectionType = discoveredType;
|
|
1388
|
+
}
|
|
1389
|
+
if (this._isBleConnection() && !connectId) {
|
|
932
1390
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
933
1391
|
code: HardwareErrorCode3.DeviceNotFound
|
|
934
1392
|
});
|
|
935
1393
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1394
|
+
await this._ensureDevicePermission(connectId, void 0, signal);
|
|
1395
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1396
|
+
const isMultiTransport = (this.connector.availableTransports?.length ?? 0) > 1;
|
|
1397
|
+
if (!isMultiTransport && this._isBleConnection()) {
|
|
1398
|
+
const previousSessionId = this._sessions.get(connectId);
|
|
1399
|
+
this._operations.endByConnectionKey(connectId, "explicit", preserveOperationId);
|
|
1400
|
+
if (previousSessionId) {
|
|
1401
|
+
this._operations.endByConnectionKey(previousSessionId, "explicit", preserveOperationId);
|
|
1402
|
+
this._sessions.delete(connectId);
|
|
1403
|
+
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1404
|
+
}
|
|
1405
|
+
} else {
|
|
1406
|
+
await this._evictAllSessions(preserveOperationId);
|
|
1407
|
+
}
|
|
1408
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1409
|
+
const stateGeneration = this._stateGeneration;
|
|
1410
|
+
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1411
|
+
let session;
|
|
1412
|
+
this._connectingConnectId = connectId;
|
|
1413
|
+
try {
|
|
1414
|
+
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1415
|
+
transportType: this._activeConnectionType ?? "usb"
|
|
1416
|
+
}) : await this.connector.connect(connectId);
|
|
1417
|
+
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1418
|
+
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1419
|
+
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1420
|
+
code: HardwareErrorCode3.UserAborted
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
} finally {
|
|
1424
|
+
if (this._connectingConnectId === connectId) this._connectingConnectId = void 0;
|
|
1425
|
+
releaseOperation();
|
|
938
1426
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
this._sessions.set(connectId, session.sessionId);
|
|
1427
|
+
const resolvedConnectId = session.deviceInfo?.connectId || connectId;
|
|
1428
|
+
this._sessions.set(resolvedConnectId, session.sessionId);
|
|
942
1429
|
if (session.deviceInfo) {
|
|
943
|
-
this._discoveredDevices.set(
|
|
1430
|
+
this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
|
|
944
1431
|
}
|
|
945
|
-
const result = success(
|
|
1432
|
+
const result = success(resolvedConnectId);
|
|
946
1433
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
947
1434
|
return result;
|
|
948
1435
|
} catch (err) {
|
|
@@ -955,30 +1442,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
955
1442
|
return failureResult;
|
|
956
1443
|
}
|
|
957
1444
|
}
|
|
958
|
-
async
|
|
1445
|
+
async releaseOperation(operationId) {
|
|
1446
|
+
const operation = this._operations.find(operationId);
|
|
1447
|
+
if (!operation) {
|
|
1448
|
+
this._operations.resolve(operationId);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
const endedOperation = this._operations.end(operationId, "explicit");
|
|
1452
|
+
if (!endedOperation) return;
|
|
1453
|
+
const { connectId } = operation;
|
|
959
1454
|
debugLog("[LedgerAdapter][REQ]", {
|
|
960
|
-
method: "
|
|
1455
|
+
method: "releaseOperation",
|
|
961
1456
|
connectId,
|
|
962
1457
|
params: { connectId }
|
|
963
1458
|
});
|
|
964
1459
|
try {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
await this.connector.disconnect(sessionId);
|
|
968
|
-
this._sessions.delete(connectId);
|
|
969
|
-
}
|
|
970
|
-
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1460
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1461
|
+
debugLog("[LedgerAdapter][RES]", { method: "releaseOperation", success: true });
|
|
971
1462
|
} catch (err) {
|
|
972
1463
|
const e = err;
|
|
973
1464
|
debugLog("[LedgerAdapter][RES]", {
|
|
974
|
-
method: "
|
|
1465
|
+
method: "releaseOperation",
|
|
975
1466
|
success: false,
|
|
976
1467
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
977
1468
|
});
|
|
978
1469
|
throw err;
|
|
979
1470
|
}
|
|
980
1471
|
}
|
|
981
|
-
async
|
|
1472
|
+
async _releaseOperationConnection(operation) {
|
|
1473
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
1474
|
+
for (const [connectId, sessionId] of this._sessions) {
|
|
1475
|
+
if (connectId === operation.connectId || operation.connectionKeys.includes(connectId) || operation.connectionKeys.includes(sessionId)) {
|
|
1476
|
+
this._sessions.delete(connectId);
|
|
1477
|
+
sessionIds.add(sessionId);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
for (const sessionId of sessionIds) {
|
|
1481
|
+
await this._runConnectorTeardown(
|
|
1482
|
+
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
async _releaseLostOperationConnection(operationId) {
|
|
1487
|
+
const endedOperation = this._operations.end(operationId, "disconnect");
|
|
1488
|
+
if (!endedOperation) return;
|
|
1489
|
+
this._discoveredDevices.delete(endedOperation.connectId);
|
|
1490
|
+
await this._releaseOperationConnection(endedOperation);
|
|
1491
|
+
}
|
|
1492
|
+
async getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1493
|
+
let connectId;
|
|
1494
|
+
try {
|
|
1495
|
+
connectId = isHardwareOperationId(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
return this.errorToFailure(error);
|
|
1498
|
+
}
|
|
982
1499
|
debugLog("[LedgerAdapter][REQ]", {
|
|
983
1500
|
method: "getDeviceInfo",
|
|
984
1501
|
connectId,
|
|
@@ -1013,12 +1530,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1013
1530
|
success: false,
|
|
1014
1531
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
1015
1532
|
});
|
|
1016
|
-
|
|
1533
|
+
return this.errorToFailure(err);
|
|
1017
1534
|
}
|
|
1018
1535
|
}
|
|
1019
|
-
getSupportedChains() {
|
|
1020
|
-
return ["evm", "btc", "sol", "tron"];
|
|
1021
|
-
}
|
|
1022
1536
|
// ---------------------------------------------------------------------------
|
|
1023
1537
|
// Chain call helper
|
|
1024
1538
|
// ---------------------------------------------------------------------------
|
|
@@ -1053,13 +1567,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1053
1567
|
if (params && typeof params === "object") {
|
|
1054
1568
|
const {
|
|
1055
1569
|
autoInstallApp,
|
|
1570
|
+
operationId,
|
|
1056
1571
|
passphraseState: _passphraseState,
|
|
1057
1572
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1573
|
+
knownConnections,
|
|
1574
|
+
extra,
|
|
1575
|
+
allowDeviceSelection,
|
|
1576
|
+
supportedTransports,
|
|
1058
1577
|
...rest
|
|
1059
1578
|
} = params;
|
|
1060
1579
|
return {
|
|
1061
1580
|
commonParams: {
|
|
1062
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1581
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
|
|
1582
|
+
operationId: typeof operationId === "string" ? operationId : void 0,
|
|
1583
|
+
knownConnections,
|
|
1584
|
+
extra,
|
|
1585
|
+
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0,
|
|
1586
|
+
supportedTransports
|
|
1063
1587
|
},
|
|
1064
1588
|
rest
|
|
1065
1589
|
};
|
|
@@ -1235,9 +1759,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1235
1759
|
);
|
|
1236
1760
|
}
|
|
1237
1761
|
// ---------------------------------------------------------------------------
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
|
|
1762
|
+
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1763
|
+
// ---------------------------------------------------------------------------
|
|
1764
|
+
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
1765
|
+
return this.callChainWithMergedParams(
|
|
1766
|
+
connectId,
|
|
1767
|
+
deviceId,
|
|
1768
|
+
"zcash",
|
|
1769
|
+
"zcashGetFullViewingKey",
|
|
1770
|
+
params
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1774
|
+
return this.callChainWithMergedParams(
|
|
1775
|
+
connectId,
|
|
1776
|
+
deviceId,
|
|
1777
|
+
"zcash",
|
|
1778
|
+
"zcashGetShieldedAddress",
|
|
1779
|
+
params
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
// ---------------------------------------------------------------------------
|
|
1783
|
+
// App management: OS-level app install/list, no fingerprint or chain dispatch.
|
|
1241
1784
|
// ---------------------------------------------------------------------------
|
|
1242
1785
|
async installApp(connectId, appName) {
|
|
1243
1786
|
try {
|
|
@@ -1323,7 +1866,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1323
1866
|
);
|
|
1324
1867
|
}
|
|
1325
1868
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1326
|
-
this.
|
|
1869
|
+
await this._resetStateAndDisconnectSessions();
|
|
1327
1870
|
}
|
|
1328
1871
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1329
1872
|
if (!result.isGenuine) {
|
|
@@ -1349,10 +1892,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1349
1892
|
if (relayUrl) {
|
|
1350
1893
|
try {
|
|
1351
1894
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1352
|
-
this.
|
|
1895
|
+
await this._resetStateAndDisconnectSessions();
|
|
1353
1896
|
} catch {
|
|
1354
1897
|
this.connector.reset();
|
|
1355
|
-
this.
|
|
1898
|
+
await this._resetStateAndDisconnectSessions();
|
|
1356
1899
|
}
|
|
1357
1900
|
}
|
|
1358
1901
|
}
|
|
@@ -1364,6 +1907,20 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1364
1907
|
this.emitter.off(event, listener);
|
|
1365
1908
|
}
|
|
1366
1909
|
cancel(connectId) {
|
|
1910
|
+
const namedOperationIsLive = (id) => {
|
|
1911
|
+
try {
|
|
1912
|
+
this._operations.resolve(id);
|
|
1913
|
+
return true;
|
|
1914
|
+
} catch {
|
|
1915
|
+
return false;
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
if (isHardwareOperationId(connectId) && !namedOperationIsLive(connectId)) {
|
|
1919
|
+
debugLog("[LedgerAdapter] cancel target already ended; nothing to cancel", {
|
|
1920
|
+
connectId
|
|
1921
|
+
});
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1367
1924
|
const userAbortReason = Object.assign(new Error("User aborted operation"), {
|
|
1368
1925
|
code: HardwareErrorCode3.UserAborted,
|
|
1369
1926
|
_tag: ERROR_TAG.UserAborted
|
|
@@ -1374,14 +1931,50 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1374
1931
|
this._lastCancelReason = void 0;
|
|
1375
1932
|
}
|
|
1376
1933
|
}, 2e3);
|
|
1377
|
-
this.
|
|
1378
|
-
|
|
1379
|
-
|
|
1934
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1935
|
+
let operationId;
|
|
1936
|
+
if (isHardwareOperationId(connectId)) {
|
|
1937
|
+
operationId = connectId;
|
|
1938
|
+
} else if (!connectId && isHardwareOperationId(activeJobId)) {
|
|
1939
|
+
operationId = activeJobId;
|
|
1940
|
+
}
|
|
1941
|
+
let resolvedConnectId = connectId;
|
|
1942
|
+
if (operationId) {
|
|
1943
|
+
try {
|
|
1944
|
+
resolvedConnectId = this._operations.resolve(operationId).connectId;
|
|
1945
|
+
} catch {
|
|
1946
|
+
resolvedConnectId = void 0;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
const interactionForPhysicalId = !operationId && connectId ? this._operations.findActiveByConnectionKey(connectId) : void 0;
|
|
1950
|
+
const pendingOperationId = operationId ?? interactionForPhysicalId?.operationId;
|
|
1951
|
+
if (!connectId) {
|
|
1952
|
+
this._uiRegistry.cancel();
|
|
1953
|
+
} else if (pendingOperationId) {
|
|
1954
|
+
this._uiRegistry.cancel(void 0, void 0, pendingOperationId);
|
|
1955
|
+
}
|
|
1956
|
+
this._finishBleBinding("cancelled");
|
|
1957
|
+
if (!connectId) this._pendingOperationBindings.clear();
|
|
1958
|
+
else if (pendingOperationId) {
|
|
1959
|
+
this._pendingOperationBindings.delete(pendingOperationId);
|
|
1960
|
+
}
|
|
1961
|
+
const queueKeys = /* @__PURE__ */ new Set();
|
|
1962
|
+
if (connectId) queueKeys.add(ledgerQueueKey({ connectId }));
|
|
1963
|
+
if (pendingOperationId) queueKeys.add(ledgerQueueKey({ operationId: pendingOperationId }));
|
|
1964
|
+
if (queueKeys.size) {
|
|
1965
|
+
let cancelledAnyJob = false;
|
|
1966
|
+
for (const key of queueKeys) {
|
|
1967
|
+
cancelledAnyJob = this._jobQueue.cancelActiveAndPending(key, userAbortReason) || cancelledAnyJob;
|
|
1968
|
+
}
|
|
1969
|
+
debugLog("[LedgerAdapter] cancel routed to queue keys", {
|
|
1970
|
+
queueKeys: [...queueKeys],
|
|
1971
|
+
cancelledAnyJob
|
|
1972
|
+
});
|
|
1380
1973
|
} else {
|
|
1381
1974
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1382
1975
|
}
|
|
1383
|
-
if (
|
|
1384
|
-
const sessionId = this._sessions.get(
|
|
1976
|
+
if (resolvedConnectId) {
|
|
1977
|
+
const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
|
|
1385
1978
|
void this.connector.cancel(sessionId);
|
|
1386
1979
|
} else {
|
|
1387
1980
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1389,45 +1982,105 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1389
1982
|
if (this._connectingPromise) {
|
|
1390
1983
|
this._doConnectAbortController?.abort(userAbortReason);
|
|
1391
1984
|
}
|
|
1985
|
+
const connectingConnectId = this._connectingConnectId;
|
|
1986
|
+
if (connectingConnectId) void this.connector.cancel(connectingConnectId);
|
|
1392
1987
|
}
|
|
1393
1988
|
// ---------------------------------------------------------------------------
|
|
1394
1989
|
// Chain fingerprint
|
|
1395
1990
|
// ---------------------------------------------------------------------------
|
|
1991
|
+
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1396
1992
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1397
1993
|
try {
|
|
1398
1994
|
const fingerprint = await this._computeChainFingerprint(
|
|
1399
1995
|
chain,
|
|
1400
1996
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1401
1997
|
);
|
|
1998
|
+
if (deviceId) {
|
|
1999
|
+
if (fingerprint !== deviceId) {
|
|
2000
|
+
return failure2(
|
|
2001
|
+
HardwareErrorCode3.DeviceMismatch,
|
|
2002
|
+
formatDeviceMismatchError(deviceId, fingerprint)
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
if (isHardwareOperationId(connectId)) {
|
|
2006
|
+
const operation = this._operations.resolve(connectId);
|
|
2007
|
+
await this._publishVerifiedBleBinding(
|
|
2008
|
+
operation.connectId,
|
|
2009
|
+
chain,
|
|
2010
|
+
fingerprint,
|
|
2011
|
+
void 0,
|
|
2012
|
+
connectId
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
1402
2016
|
return success(fingerprint);
|
|
1403
2017
|
} catch (err) {
|
|
1404
2018
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
1405
2019
|
return this.errorToFailure(err);
|
|
1406
2020
|
}
|
|
1407
2021
|
}
|
|
1408
|
-
/**
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
2022
|
+
/** Discovery may select a BLE target before a later call verifies its wallet. */
|
|
2023
|
+
async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, operationId, signal) {
|
|
2024
|
+
const binding = operationId ? this._pendingOperationBindings.get(operationId) : attempt;
|
|
2025
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) {
|
|
2026
|
+
return false;
|
|
2027
|
+
}
|
|
1414
2028
|
try {
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
2029
|
+
const outcome = await requestSaveDeviceBinding(
|
|
2030
|
+
this.emitter,
|
|
2031
|
+
this._uiRegistry,
|
|
2032
|
+
{
|
|
2033
|
+
selectionRequestId: binding.selectedConnection.requestId,
|
|
2034
|
+
connection: { transport: "ble", connectId },
|
|
2035
|
+
identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
|
|
2036
|
+
extra: binding.extra,
|
|
2037
|
+
operationId
|
|
2038
|
+
},
|
|
2039
|
+
signal
|
|
2040
|
+
);
|
|
2041
|
+
if (operationId) this._operations.resolve(operationId);
|
|
2042
|
+
if (!outcome.saved) {
|
|
2043
|
+
debugLog("[LedgerAdapter] BLE binding not persisted by host", {
|
|
2044
|
+
connectId,
|
|
2045
|
+
reason: outcome.reason
|
|
2046
|
+
});
|
|
1421
2047
|
}
|
|
1422
|
-
return
|
|
1423
|
-
} catch (
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
2048
|
+
return outcome.saved;
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
if (operationId) {
|
|
2051
|
+
this._pendingOperationBindings.delete(operationId);
|
|
2052
|
+
this._operations.end(operationId, "explicit");
|
|
2053
|
+
}
|
|
2054
|
+
const sessionId = this._sessions.get(connectId);
|
|
2055
|
+
this._sessions.delete(connectId);
|
|
2056
|
+
if (sessionId) {
|
|
2057
|
+
const teardown = this._runConnectorTeardown(
|
|
2058
|
+
() => this.connector.disconnect(sessionId)
|
|
2059
|
+
).catch(() => void 0);
|
|
2060
|
+
if (!signal?.aborted) await teardown;
|
|
2061
|
+
}
|
|
2062
|
+
throw error;
|
|
2063
|
+
} finally {
|
|
2064
|
+
if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
|
|
2065
|
+
this._bindingSelectionRequestId = void 0;
|
|
2066
|
+
}
|
|
2067
|
+
if (operationId) {
|
|
2068
|
+
this._pendingOperationBindings.delete(operationId);
|
|
1427
2069
|
}
|
|
1428
|
-
throw err;
|
|
1429
2070
|
}
|
|
1430
2071
|
}
|
|
2072
|
+
/** Verify on the acquired session without re-entering the job queue. */
|
|
2073
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
2074
|
+
if (!deviceId) return { success: true };
|
|
2075
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
2076
|
+
chain,
|
|
2077
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
2078
|
+
);
|
|
2079
|
+
if (fingerprint === deviceId) {
|
|
2080
|
+
return { success: true };
|
|
2081
|
+
}
|
|
2082
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
2083
|
+
}
|
|
1431
2084
|
/**
|
|
1432
2085
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
1433
2086
|
*
|
|
@@ -1455,11 +2108,23 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1455
2108
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
1456
2109
|
} else if (chain === "tron") {
|
|
1457
2110
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
2111
|
+
} else if (chain === "zcash") {
|
|
2112
|
+
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
1458
2113
|
} else {
|
|
1459
2114
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
1460
2115
|
}
|
|
1461
2116
|
return deriveDeviceFingerprint(address);
|
|
1462
2117
|
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Operation owning the running job, so a mid-call UI request can name it.
|
|
2120
|
+
* Undefined at cold start when no operation owns the job.
|
|
2121
|
+
*/
|
|
2122
|
+
_activeOperationId() {
|
|
2123
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
2124
|
+
if (!activeJobId) return void 0;
|
|
2125
|
+
if (isHardwareOperationId(activeJobId)) return activeJobId;
|
|
2126
|
+
return this._operations.findActiveByConnectionKey(activeJobId)?.operationId;
|
|
2127
|
+
}
|
|
1463
2128
|
// Ledger WebUSB won't expose a locked device, so we can't auto-detect unlock.
|
|
1464
2129
|
// The user must press Confirm after unlocking, which triggers a search retry.
|
|
1465
2130
|
// If `signal` is provided, an abort cancels the pending UI request so the
|
|
@@ -1469,15 +2134,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1469
2134
|
if (signal?.aborted) {
|
|
1470
2135
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1471
2136
|
}
|
|
2137
|
+
const operationId = this._activeOperationId();
|
|
1472
2138
|
const waitPromise = this._uiRegistry.wait(
|
|
1473
|
-
UI_REQUEST.REQUEST_DEVICE_CONNECT
|
|
2139
|
+
UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
2140
|
+
{ operationId }
|
|
1474
2141
|
);
|
|
1475
2142
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_CONNECT, {
|
|
1476
2143
|
type: UI_REQUEST.REQUEST_DEVICE_CONNECT,
|
|
1477
2144
|
payload: {
|
|
1478
2145
|
vendor: "ledger",
|
|
1479
2146
|
reason: "device-not-found",
|
|
1480
|
-
message: "Please connect and unlock your Ledger device"
|
|
2147
|
+
message: "Please connect and unlock your Ledger device",
|
|
2148
|
+
operationId
|
|
1481
2149
|
}
|
|
1482
2150
|
});
|
|
1483
2151
|
let payload;
|
|
@@ -1538,15 +2206,18 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1538
2206
|
return { ...params, showOnDevice: true };
|
|
1539
2207
|
}
|
|
1540
2208
|
async _waitForBtcHighIndexConfirm(path, accountIndex) {
|
|
2209
|
+
const operationId = this._activeOperationId();
|
|
1541
2210
|
const waitPromise = this._uiRegistry.wait(
|
|
1542
|
-
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM
|
|
2211
|
+
UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
2212
|
+
{ operationId }
|
|
1543
2213
|
);
|
|
1544
2214
|
this.emitter.emit(UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM, {
|
|
1545
2215
|
type: UI_REQUEST.REQUEST_BTC_HIGH_INDEX_CONFIRM,
|
|
1546
2216
|
payload: {
|
|
1547
2217
|
vendor: "ledger",
|
|
1548
2218
|
path,
|
|
1549
|
-
accountIndex
|
|
2219
|
+
accountIndex,
|
|
2220
|
+
operationId
|
|
1550
2221
|
}
|
|
1551
2222
|
});
|
|
1552
2223
|
try {
|
|
@@ -1563,12 +2234,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1563
2234
|
// Ask the user whether to install a missing app (autoInstallApp flow).
|
|
1564
2235
|
// Same register-then-emit ordering as the BTC high-index gate.
|
|
1565
2236
|
async _waitForInstallAppConfirm(appName) {
|
|
2237
|
+
const operationId = this._activeOperationId();
|
|
1566
2238
|
const waitPromise = this._uiRegistry.wait(
|
|
1567
|
-
UI_REQUEST.REQUEST_INSTALL_APP
|
|
2239
|
+
UI_REQUEST.REQUEST_INSTALL_APP,
|
|
2240
|
+
{ operationId }
|
|
1568
2241
|
);
|
|
1569
2242
|
this.emitter.emit(UI_REQUEST.REQUEST_INSTALL_APP, {
|
|
1570
2243
|
type: UI_REQUEST.REQUEST_INSTALL_APP,
|
|
1571
|
-
payload: { vendor: "ledger", appName }
|
|
2244
|
+
payload: { vendor: "ledger", appName, operationId }
|
|
1572
2245
|
});
|
|
1573
2246
|
try {
|
|
1574
2247
|
const payload = await waitPromise;
|
|
@@ -1584,16 +2257,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1584
2257
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
1585
2258
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
1586
2259
|
// doesn't kill caller B's await.
|
|
1587
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
2260
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
1588
2261
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
1589
|
-
|
|
2262
|
+
this._assertConnectorReady("connectDevice");
|
|
2263
|
+
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1590
2264
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1591
2265
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1592
2266
|
});
|
|
1593
2267
|
}
|
|
1594
2268
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
1595
2269
|
if (!connectId && this._sessions.size > 0) {
|
|
1596
|
-
if (!
|
|
2270
|
+
if (!this._isBleConnection() && this._sessions.size > 1) {
|
|
1597
2271
|
throw Object.assign(
|
|
1598
2272
|
new Error(
|
|
1599
2273
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -1603,15 +2277,24 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1603
2277
|
}
|
|
1604
2278
|
return this._sessions.keys().next().value;
|
|
1605
2279
|
}
|
|
1606
|
-
if (!this._connectingPromise) {
|
|
1607
|
-
|
|
1608
|
-
|
|
2280
|
+
if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
|
|
2281
|
+
const controller = new AbortController();
|
|
2282
|
+
this._doConnectAbortController = controller;
|
|
2283
|
+
const innerSignal = controller.signal;
|
|
1609
2284
|
this._connectingPromise = (async () => {
|
|
1610
2285
|
try {
|
|
1611
|
-
return await this._doConnect(
|
|
2286
|
+
return await this._doConnect(
|
|
2287
|
+
innerSignal,
|
|
2288
|
+
connectId,
|
|
2289
|
+
allowUsbEphemeralFallback,
|
|
2290
|
+
preserveOperationId,
|
|
2291
|
+
context
|
|
2292
|
+
);
|
|
1612
2293
|
} finally {
|
|
1613
|
-
this.
|
|
1614
|
-
|
|
2294
|
+
if (this._doConnectAbortController === controller) {
|
|
2295
|
+
this._connectingPromise = null;
|
|
2296
|
+
this._doConnectAbortController = null;
|
|
2297
|
+
}
|
|
1615
2298
|
}
|
|
1616
2299
|
})();
|
|
1617
2300
|
}
|
|
@@ -1620,22 +2303,57 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1620
2303
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1621
2304
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1622
2305
|
// 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
|
-
|
|
2306
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveOperationId, context) {
|
|
2307
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2308
|
+
if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
|
|
2309
|
+
this._activeConnectionType = "usb";
|
|
2310
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
|
|
2311
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2312
|
+
if (usbDevices.length > 0) {
|
|
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
|
+
return this._connectFirstOrSelect(
|
|
2318
|
+
usbDevices,
|
|
2319
|
+
usbTarget,
|
|
2320
|
+
allowUsbEphemeralFallback,
|
|
2321
|
+
preserveOperationId,
|
|
2322
|
+
context,
|
|
2323
|
+
internalSignal
|
|
2324
|
+
);
|
|
1638
2325
|
}
|
|
2326
|
+
if (context?.supportedTransports && !context.supportedTransports.includes("ble")) {
|
|
2327
|
+
throw createHwkError({
|
|
2328
|
+
code: HardwareErrorCode3.DeviceNotFound,
|
|
2329
|
+
message: "This Ledger model has no Bluetooth transport; connect it over USB"
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
this._activeConnectionType = "ble";
|
|
2333
|
+
const knownBle = context?.knownConnections?.find(
|
|
2334
|
+
(connection) => connection.transport === "ble"
|
|
2335
|
+
);
|
|
2336
|
+
if (knownBle?.transport === "ble") {
|
|
2337
|
+
return this._connectDeviceOrThrow(knownBle.connectId, preserveOperationId, internalSignal);
|
|
2338
|
+
}
|
|
2339
|
+
if (targetConnectId && context?.knownConnections === void 0) {
|
|
2340
|
+
throw createHwkError({
|
|
2341
|
+
code: HardwareErrorCode3.DeviceNotFound,
|
|
2342
|
+
message: "Ledger connection metadata is required before starting Bluetooth binding"
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2346
|
+
return this._connectFirstOrSelect(
|
|
2347
|
+
bleDevices,
|
|
2348
|
+
void 0,
|
|
2349
|
+
allowUsbEphemeralFallback,
|
|
2350
|
+
preserveOperationId,
|
|
2351
|
+
context,
|
|
2352
|
+
internalSignal
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
2355
|
+
if (this._isBleConnection() && targetConnectId) {
|
|
2356
|
+
return this._connectDeviceOrThrow(targetConnectId, preserveOperationId, internalSignal);
|
|
1639
2357
|
}
|
|
1640
2358
|
let confirms = 0;
|
|
1641
2359
|
while (!internalSignal.aborted) {
|
|
@@ -1643,22 +2361,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1643
2361
|
type: EConnectorInteraction.Searching,
|
|
1644
2362
|
payload: { sessionId: "" }
|
|
1645
2363
|
});
|
|
1646
|
-
let devices = await this.
|
|
2364
|
+
let devices = await this._searchDevices(void 0, internalSignal);
|
|
2365
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1647
2366
|
if (devices.length === 0) {
|
|
1648
2367
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1649
2368
|
await new Promise((resolve) => {
|
|
1650
2369
|
setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
1651
2370
|
});
|
|
1652
|
-
|
|
2371
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2372
|
+
devices = await this._searchDevices(void 0, internalSignal);
|
|
2373
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1653
2374
|
if (devices.length > 0) break;
|
|
1654
2375
|
}
|
|
1655
2376
|
}
|
|
1656
|
-
if (devices.length > 0) {
|
|
2377
|
+
if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
|
|
1657
2378
|
try {
|
|
1658
2379
|
return await this._connectFirstOrSelect(
|
|
1659
2380
|
devices,
|
|
1660
2381
|
targetConnectId,
|
|
1661
|
-
allowUsbEphemeralFallback
|
|
2382
|
+
allowUsbEphemeralFallback,
|
|
2383
|
+
preserveOperationId,
|
|
2384
|
+
context,
|
|
2385
|
+
internalSignal
|
|
1662
2386
|
);
|
|
1663
2387
|
} catch (err) {
|
|
1664
2388
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -1687,45 +2411,133 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1687
2411
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1688
2412
|
throw new Error("_doConnect aborted");
|
|
1689
2413
|
}
|
|
1690
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback
|
|
2414
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback, preserveOperationId, context, signal) {
|
|
2415
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1691
2416
|
if (targetConnectId) {
|
|
1692
2417
|
const target = devices.find(
|
|
1693
2418
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1694
2419
|
);
|
|
1695
2420
|
if (target) {
|
|
1696
|
-
return this._connectDeviceOrThrow(target.connectId);
|
|
2421
|
+
return this._connectDeviceOrThrow(target.connectId, preserveOperationId, signal);
|
|
1697
2422
|
}
|
|
1698
|
-
if (!
|
|
2423
|
+
if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
1699
2424
|
debugLog(
|
|
1700
2425
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
1701
2426
|
);
|
|
1702
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2427
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1703
2428
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
2429
|
+
if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
|
|
2430
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
2431
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
2432
|
+
});
|
|
2433
|
+
if (this._isBleConnection()) {
|
|
2434
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2435
|
+
}
|
|
2436
|
+
throw err;
|
|
1709
2437
|
}
|
|
1710
|
-
throw err;
|
|
1711
2438
|
}
|
|
1712
|
-
|
|
2439
|
+
const requiresBleSelection = this._isBleConnection();
|
|
2440
|
+
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1713
2441
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1714
2442
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1715
2443
|
});
|
|
1716
2444
|
}
|
|
1717
|
-
if (
|
|
1718
|
-
|
|
2445
|
+
if (requiresBleSelection && context?.allowDeviceSelection !== false) {
|
|
2446
|
+
const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
|
|
2447
|
+
if (context) context.bindingSessionId = bindingSessionId;
|
|
2448
|
+
const allowUsbFallback = context?.bindingReason !== "manual-rebind" && Boolean(this.connector.availableTransports?.includes("usb"));
|
|
2449
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2450
|
+
(connection) => connection.transport === "usb"
|
|
2451
|
+
);
|
|
2452
|
+
const usbConnectId = knownUsb?.transport === "usb" ? knownUsb.connectId : targetConnectId;
|
|
2453
|
+
const { device, requestId } = await requestBleDeviceSelection({
|
|
2454
|
+
emitter: this.emitter,
|
|
2455
|
+
registry: this._uiRegistry,
|
|
2456
|
+
signal,
|
|
2457
|
+
allowUsbFallback,
|
|
2458
|
+
scan: async () => {
|
|
2459
|
+
if (allowUsbFallback) {
|
|
2460
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, signal);
|
|
2461
|
+
const candidate = usbDevices.find(
|
|
2462
|
+
(device2) => device2.connectionType === "usb" && device2.connectId === usbConnectId
|
|
2463
|
+
) ?? (usbDevices.length === 1 && usbDevices[0].connectionType === "usb" ? usbDevices[0] : void 0);
|
|
2464
|
+
if (candidate) return [candidate];
|
|
2465
|
+
}
|
|
2466
|
+
return (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId));
|
|
2467
|
+
},
|
|
2468
|
+
request: {
|
|
2469
|
+
devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
|
|
2470
|
+
bindingSessionId,
|
|
2471
|
+
rejectedConnectId: context?.rejectedConnectId,
|
|
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
|
+
if (preserveOperationId) this._pendingOperationBindings.delete(preserveOperationId);
|
|
2485
|
+
this._bindingSelectionRequestId = requestId;
|
|
2486
|
+
this._finishBleBinding("cancelled");
|
|
2487
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2488
|
+
}
|
|
2489
|
+
if (context) context.selectedConnection = { connectId: device.connectId, requestId };
|
|
2490
|
+
this._bindingSelectionRequestId = requestId;
|
|
2491
|
+
return this._connectDeviceOrThrow(device.connectId, preserveOperationId, signal);
|
|
2492
|
+
}
|
|
2493
|
+
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2494
|
+
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2495
|
+
throw createHwkError({
|
|
2496
|
+
code: HardwareErrorCode3.DeviceNotFound,
|
|
2497
|
+
message: "Select a Ledger device before continuing"
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
const requestId = this._uiRegistry.createRequestId();
|
|
2501
|
+
const operationId = this._activeOperationId();
|
|
2502
|
+
const waitPromise = this._uiRegistry.wait(
|
|
2503
|
+
UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2504
|
+
{ requestId, operationId }
|
|
2505
|
+
);
|
|
2506
|
+
this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2507
|
+
type: UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2508
|
+
payload: {
|
|
2509
|
+
devices,
|
|
2510
|
+
requestId,
|
|
2511
|
+
operationId,
|
|
2512
|
+
context: requiresBleSelection ? {
|
|
2513
|
+
kind: "bind-connection",
|
|
2514
|
+
transport: "ble",
|
|
2515
|
+
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2516
|
+
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2517
|
+
extra: context?.extra
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
const { sdkConnectId } = await this._abortable(signal, waitPromise);
|
|
2521
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2522
|
+
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2523
|
+
if (!selected) {
|
|
2524
|
+
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2525
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
if (context && requiresBleSelection)
|
|
2529
|
+
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2530
|
+
return this._connectDeviceOrThrow(selected.connectId, preserveOperationId, signal);
|
|
1719
2531
|
}
|
|
1720
2532
|
if (devices.length !== 1) {
|
|
1721
2533
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
1722
2534
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1723
2535
|
});
|
|
1724
2536
|
}
|
|
1725
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2537
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveOperationId, signal);
|
|
1726
2538
|
}
|
|
1727
|
-
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1728
|
-
const result = await this.
|
|
2539
|
+
async _connectDeviceOrThrow(chosenConnectId, preserveOperationId, signal) {
|
|
2540
|
+
const result = await this._connectTarget(chosenConnectId, preserveOperationId, signal);
|
|
1729
2541
|
if (!result.success) {
|
|
1730
2542
|
const payload = result.payload;
|
|
1731
2543
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -1736,7 +2548,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1736
2548
|
}
|
|
1737
2549
|
throw rethrow;
|
|
1738
2550
|
}
|
|
1739
|
-
return
|
|
2551
|
+
return result.payload;
|
|
1740
2552
|
}
|
|
1741
2553
|
/**
|
|
1742
2554
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -1766,33 +2578,133 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1766
2578
|
* lives in one place.
|
|
1767
2579
|
*/
|
|
1768
2580
|
async _callConnector(sessionId, method, params, signal) {
|
|
1769
|
-
|
|
2581
|
+
this._assertConnectorReady(method);
|
|
2582
|
+
if (signal?.aborted) throw this._abortReason(signal);
|
|
2583
|
+
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2584
|
+
let promise;
|
|
2585
|
+
try {
|
|
2586
|
+
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2587
|
+
} catch (error) {
|
|
2588
|
+
releaseOperation();
|
|
2589
|
+
throw error;
|
|
2590
|
+
}
|
|
1770
2591
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
1771
2592
|
return this._unwrapConnectorResult(result);
|
|
1772
2593
|
}
|
|
2594
|
+
_assertConnectorReady(method) {
|
|
2595
|
+
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2596
|
+
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
_runConnectorTeardown(task) {
|
|
2600
|
+
const previous = this._connectorTeardownTail;
|
|
2601
|
+
let releaseTail = () => void 0;
|
|
2602
|
+
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2603
|
+
releaseTail = resolve;
|
|
2604
|
+
});
|
|
2605
|
+
this._pendingConnectorTeardowns += 1;
|
|
2606
|
+
return (async () => {
|
|
2607
|
+
try {
|
|
2608
|
+
await previous;
|
|
2609
|
+
await this._waitForConnectorOperationsToDrain();
|
|
2610
|
+
await task();
|
|
2611
|
+
} finally {
|
|
2612
|
+
this._pendingConnectorTeardowns -= 1;
|
|
2613
|
+
releaseTail();
|
|
2614
|
+
}
|
|
2615
|
+
})();
|
|
2616
|
+
}
|
|
2617
|
+
_waitForConnectorOperationsToDrain() {
|
|
2618
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2619
|
+
return Promise.resolve();
|
|
2620
|
+
}
|
|
2621
|
+
return new Promise((resolve) => {
|
|
2622
|
+
this._connectorIdleWaiters.add(resolve);
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
_retainConnectorOperation(key) {
|
|
2626
|
+
this._unsettledConnectorOperations.set(
|
|
2627
|
+
key,
|
|
2628
|
+
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2629
|
+
);
|
|
2630
|
+
let released = false;
|
|
2631
|
+
return () => {
|
|
2632
|
+
if (released) return;
|
|
2633
|
+
released = true;
|
|
2634
|
+
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2635
|
+
if (remaining > 0) {
|
|
2636
|
+
this._unsettledConnectorOperations.set(key, remaining);
|
|
2637
|
+
} else {
|
|
2638
|
+
this._unsettledConnectorOperations.delete(key);
|
|
2639
|
+
}
|
|
2640
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2641
|
+
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2642
|
+
this._connectorIdleWaiters.clear();
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
1773
2646
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
1774
|
-
|
|
1775
|
-
|
|
2647
|
+
const positionalOperationId = isHardwareOperationId(connectId) ? connectId : void 0;
|
|
2648
|
+
if (positionalOperationId && commonParams?.operationId && positionalOperationId !== commonParams.operationId) {
|
|
2649
|
+
throw createHwkError({
|
|
2650
|
+
code: HardwareErrorCode3.InvalidParams,
|
|
2651
|
+
message: "Conflicting Ledger operation ids",
|
|
2652
|
+
params: {
|
|
2653
|
+
positionalOperationId,
|
|
2654
|
+
commonOperationId: commonParams.operationId
|
|
2655
|
+
}
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
const operationId = commonParams?.operationId ?? positionalOperationId;
|
|
2659
|
+
const operation = operationId ? this._operations.resolve(operationId) : void 0;
|
|
2660
|
+
const releaseOperationRetention = operationId ? this._operations.retain(operationId) : void 0;
|
|
2661
|
+
const effectiveConnectId = operation?.connectId ?? connectId;
|
|
2662
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
2663
|
+
method,
|
|
2664
|
+
connectId: effectiveConnectId || "(empty)",
|
|
2665
|
+
params
|
|
2666
|
+
});
|
|
2667
|
+
const queueKey = ledgerQueueKey({ operationId, connectId: effectiveConnectId });
|
|
1776
2668
|
try {
|
|
1777
2669
|
const result = await this._jobQueue.enqueue(
|
|
1778
2670
|
queueKey,
|
|
1779
|
-
async (signal) =>
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2671
|
+
async (signal) => {
|
|
2672
|
+
if (operationId) this._activeOperationJobs.add(operationId);
|
|
2673
|
+
try {
|
|
2674
|
+
return await this._runConnectorCall(
|
|
2675
|
+
effectiveConnectId,
|
|
2676
|
+
method,
|
|
2677
|
+
params,
|
|
2678
|
+
signal,
|
|
2679
|
+
fingerprint,
|
|
2680
|
+
permissionDeviceId,
|
|
2681
|
+
commonParams,
|
|
2682
|
+
installContext ?? {},
|
|
2683
|
+
operationId
|
|
2684
|
+
);
|
|
2685
|
+
} catch (error) {
|
|
2686
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
2687
|
+
throw error;
|
|
2688
|
+
} finally {
|
|
2689
|
+
if (operationId) {
|
|
2690
|
+
this._activeOperationJobs.delete(operationId);
|
|
2691
|
+
if (this._pendingOperationDisconnects.delete(operationId)) {
|
|
2692
|
+
await this._releaseLostOperationConnection(operationId);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
},
|
|
1789
2697
|
{
|
|
1790
2698
|
label: method,
|
|
1791
2699
|
rejectIfBusy: true,
|
|
1792
2700
|
busyError: _LedgerAdapter._createDeviceBusyError(method)
|
|
1793
2701
|
}
|
|
1794
2702
|
);
|
|
1795
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
2703
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
2704
|
+
method,
|
|
2705
|
+
success: true,
|
|
2706
|
+
payload: redactResultForLog(result)
|
|
2707
|
+
});
|
|
1796
2708
|
return result;
|
|
1797
2709
|
} catch (err) {
|
|
1798
2710
|
const e = err;
|
|
@@ -1806,15 +2718,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1806
2718
|
}
|
|
1807
2719
|
});
|
|
1808
2720
|
throw err;
|
|
2721
|
+
} finally {
|
|
2722
|
+
releaseOperationRetention?.();
|
|
1809
2723
|
}
|
|
1810
2724
|
}
|
|
2725
|
+
/** Hermes/RN polyfills don't always populate signal.reason; fall back. */
|
|
2726
|
+
_abortReason(signal) {
|
|
2727
|
+
return signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
2728
|
+
}
|
|
1811
2729
|
/**
|
|
1812
2730
|
* Race a promise against an abort signal. On abort, rejects with
|
|
1813
2731
|
* signal.reason → instance _lastCancelReason → generic Error('Aborted').
|
|
1814
2732
|
*/
|
|
1815
2733
|
_abortable(signal, promise) {
|
|
1816
|
-
const getAbortReason = () =>
|
|
2734
|
+
const getAbortReason = () => this._abortReason(signal);
|
|
1817
2735
|
if (signal.aborted) {
|
|
2736
|
+
void promise.catch(() => void 0);
|
|
1818
2737
|
return Promise.reject(getAbortReason());
|
|
1819
2738
|
}
|
|
1820
2739
|
return new Promise((resolve, reject) => {
|
|
@@ -1841,13 +2760,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1841
2760
|
}
|
|
1842
2761
|
}
|
|
1843
2762
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
1844
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2763
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, operationId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
|
|
1845
2764
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2765
|
+
if (!operationId) {
|
|
2766
|
+
await this._ensureDevicePermission(
|
|
2767
|
+
connectId,
|
|
2768
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
2769
|
+
signal
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
1851
2772
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1852
2773
|
let effectiveParams = params;
|
|
1853
2774
|
if (method === "btcGetPublicKey") {
|
|
@@ -1861,36 +2782,135 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1861
2782
|
effectiveParams = gatedParams;
|
|
1862
2783
|
}
|
|
1863
2784
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
1864
|
-
|
|
1865
|
-
|
|
2785
|
+
let businessCallStarted = false;
|
|
2786
|
+
const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
|
|
2787
|
+
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2788
|
+
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2789
|
+
(connection) => connection.transport === knownTransport
|
|
2790
|
+
);
|
|
2791
|
+
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2792
|
+
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
|
|
2793
|
+
const connectionAttempt = { ...commonParams };
|
|
2794
|
+
const bundleConnection = installContext?.connection;
|
|
2795
|
+
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2796
|
+
throw createHwkError({
|
|
2797
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2798
|
+
message: "Ledger all-network connection ended"
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
let resolvedConnectId = operationId ? this._operations.resolve(operationId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2802
|
+
preferredConnectId,
|
|
1866
2803
|
signal,
|
|
1867
|
-
allowUsbEphemeralFallback
|
|
2804
|
+
allowUsbEphemeralFallback,
|
|
2805
|
+
void 0,
|
|
2806
|
+
connectionAttempt
|
|
1868
2807
|
);
|
|
1869
|
-
|
|
2808
|
+
let sessionId = this._sessions.get(resolvedConnectId);
|
|
2809
|
+
if (sessionId && installContext && !installContext.connection) {
|
|
2810
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2811
|
+
}
|
|
1870
2812
|
if (!sessionId) {
|
|
2813
|
+
if (operationId) {
|
|
2814
|
+
this._operations.end(operationId, "disconnect");
|
|
2815
|
+
throw createHwkError({
|
|
2816
|
+
code: HardwareErrorCode3.OperationEnded,
|
|
2817
|
+
message: "Ledger operation connection is no longer active",
|
|
2818
|
+
params: { operationId, reason: "disconnect" }
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
1871
2821
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
1872
2822
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
1873
2823
|
});
|
|
1874
2824
|
}
|
|
1875
2825
|
try {
|
|
1876
2826
|
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2827
|
+
for (; ; ) {
|
|
2828
|
+
const fp = await this._abortable(
|
|
2829
|
+
signal,
|
|
2830
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2831
|
+
sessionId,
|
|
2832
|
+
fingerprint.deviceId,
|
|
2833
|
+
fingerprint.chain
|
|
2834
|
+
)
|
|
2835
|
+
);
|
|
2836
|
+
if (fp.success) break;
|
|
2837
|
+
const binding = operationId ? this._pendingOperationBindings.get(operationId) : connectionAttempt;
|
|
2838
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
|
|
2839
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2840
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
2844
|
+
binding.rejectedConnectIds.add(resolvedConnectId);
|
|
2845
|
+
binding.rejectedConnectId = resolvedConnectId;
|
|
2846
|
+
this._sessions.delete(resolvedConnectId);
|
|
2847
|
+
await this.connector.disconnect(sessionId);
|
|
2848
|
+
if (operationId) this._pendingOperationDisconnects.delete(operationId);
|
|
2849
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2850
|
+
resolvedConnectId = await this._connectFirstOrSelect(
|
|
2851
|
+
[],
|
|
2852
|
+
void 0,
|
|
2853
|
+
true,
|
|
2854
|
+
operationId,
|
|
2855
|
+
binding,
|
|
2856
|
+
signal
|
|
2857
|
+
);
|
|
2858
|
+
const selectedSession = this._sessions.get(resolvedConnectId);
|
|
2859
|
+
const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
|
|
2860
|
+
if (!selectedSession || !selectedDevice)
|
|
2861
|
+
throw createHwkError({
|
|
2862
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2863
|
+
message: "Selected Ledger connection ended"
|
|
2864
|
+
});
|
|
2865
|
+
sessionId = selectedSession;
|
|
2866
|
+
if (operationId)
|
|
2867
|
+
this._operations.rebind(operationId, {
|
|
2868
|
+
connectId: resolvedConnectId,
|
|
2869
|
+
device: selectedDevice,
|
|
2870
|
+
// Same source as `_createOperation`: the selected transport, not
|
|
2871
|
+
// the device snapshot a session connect overwrote.
|
|
2872
|
+
connectionType: this._isBleConnection() ? "ble" : "usb",
|
|
2873
|
+
connectionKeys: [sessionId]
|
|
2874
|
+
});
|
|
2875
|
+
if (installContext)
|
|
2876
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2877
|
+
}
|
|
2878
|
+
await this._publishVerifiedBleBinding(
|
|
2879
|
+
resolvedConnectId,
|
|
2880
|
+
fingerprint.chain,
|
|
2881
|
+
fingerprint.deviceId,
|
|
2882
|
+
connectionAttempt,
|
|
2883
|
+
operationId,
|
|
2884
|
+
signal
|
|
1884
2885
|
);
|
|
1885
|
-
if (!
|
|
1886
|
-
|
|
1887
|
-
|
|
2886
|
+
if (!operationId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
|
|
2887
|
+
this._verifiedBleReconnectTargets.set(connectId, {
|
|
2888
|
+
connectId: resolvedConnectId,
|
|
2889
|
+
chain: fingerprint.chain,
|
|
2890
|
+
fingerprint: fingerprint.deviceId
|
|
1888
2891
|
});
|
|
1889
2892
|
}
|
|
1890
2893
|
}
|
|
2894
|
+
businessCallStarted = true;
|
|
1891
2895
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
1892
2896
|
} catch (err) {
|
|
1893
2897
|
if (signal.aborted) throw err;
|
|
2898
|
+
if (isLostConnectionError(err)) {
|
|
2899
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2900
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2901
|
+
else {
|
|
2902
|
+
this._sessions.delete(resolvedConnectId);
|
|
2903
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2904
|
+
}
|
|
2905
|
+
const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2906
|
+
const interactionParams = operationId ? { operationId, reason: "disconnect" } : void 0;
|
|
2907
|
+
throw createHwkError({
|
|
2908
|
+
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(err).code,
|
|
2909
|
+
message: "Ledger operation connection was lost; start a new operation",
|
|
2910
|
+
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId }) : interactionParams,
|
|
2911
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
1894
2914
|
const errObj = err;
|
|
1895
2915
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1896
2916
|
message: errObj?.message,
|
|
@@ -1902,6 +2922,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1902
2922
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1903
2923
|
isStuckApp: isStuckAppStateError(err)
|
|
1904
2924
|
});
|
|
2925
|
+
const assertSessionCurrent = (message) => {
|
|
2926
|
+
if (this._sessions.get(resolvedConnectId) === sessionId) return;
|
|
2927
|
+
throw createHwkError({
|
|
2928
|
+
code: operationId ? HardwareErrorCode3.OperationEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2929
|
+
message
|
|
2930
|
+
});
|
|
2931
|
+
};
|
|
2932
|
+
if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2933
|
+
await this._waitForDeviceConnect(signal);
|
|
2934
|
+
assertSessionCurrent("Ledger connection ended while waiting for unlock");
|
|
2935
|
+
return this._runConnectorCall(
|
|
2936
|
+
resolvedConnectId,
|
|
2937
|
+
method,
|
|
2938
|
+
effectiveParams,
|
|
2939
|
+
signal,
|
|
2940
|
+
fingerprint,
|
|
2941
|
+
permissionDeviceId,
|
|
2942
|
+
commonParams,
|
|
2943
|
+
installContext,
|
|
2944
|
+
operationId,
|
|
2945
|
+
lockedRetryBudget - 1
|
|
2946
|
+
);
|
|
2947
|
+
}
|
|
2948
|
+
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2949
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2950
|
+
assertSessionCurrent("Ledger connection ended during the app transition");
|
|
2951
|
+
try {
|
|
2952
|
+
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2953
|
+
} catch (retryErr) {
|
|
2954
|
+
if (isStuckAppStateError(retryErr)) throw err;
|
|
2955
|
+
if (isLostConnectionError(retryErr)) {
|
|
2956
|
+
if (operationId) await this._releaseLostOperationConnection(operationId);
|
|
2957
|
+
else {
|
|
2958
|
+
this._sessions.delete(resolvedConnectId);
|
|
2959
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2960
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2961
|
+
}
|
|
2962
|
+
const ambiguous = !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2963
|
+
throw createHwkError({
|
|
2964
|
+
code: operationId ? HardwareErrorCode3.OperationEnded : mapLedgerError(retryErr).code,
|
|
2965
|
+
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2966
|
+
params: ambiguous ? operationMayHaveCompletedParams(method, { operationId, reason: "disconnect" }) : { operationId, reason: "disconnect" },
|
|
2967
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
throw retryErr;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
if (!operationId && errObj?.code === HardwareErrorCode3.DeviceMismatch) {
|
|
2974
|
+
this._sessions.delete(resolvedConnectId);
|
|
2975
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2976
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2977
|
+
throw err;
|
|
2978
|
+
}
|
|
1905
2979
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
1906
2980
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
|
|
1907
2981
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -1957,157 +3031,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1957
3031
|
fingerprint,
|
|
1958
3032
|
permissionDeviceId,
|
|
1959
3033
|
commonParams,
|
|
1960
|
-
installContext
|
|
3034
|
+
installContext,
|
|
3035
|
+
operationId
|
|
1961
3036
|
);
|
|
1962
3037
|
}
|
|
1963
3038
|
}
|
|
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
3039
|
throw err;
|
|
2075
3040
|
}
|
|
2076
3041
|
}
|
|
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
3042
|
_sleepAbortable(ms, signal) {
|
|
2112
3043
|
return new Promise((resolve, reject) => {
|
|
2113
3044
|
if (signal.aborted) {
|
|
@@ -2126,111 +3057,22 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2126
3057
|
});
|
|
2127
3058
|
}
|
|
2128
3059
|
/**
|
|
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
|
|
3060
|
+
* Ensure OS-level Bluetooth/USB permission; times out after 60s if the host never
|
|
3061
|
+
* replies. Without connectId the request is environment-level, else device-level.
|
|
2221
3062
|
*/
|
|
2222
3063
|
async _ensureDevicePermission(connectId, deviceId, signal) {
|
|
2223
3064
|
if (signal?.aborted) {
|
|
2224
3065
|
_LedgerAdapter._throwIfAborted(signal);
|
|
2225
3066
|
}
|
|
2226
3067
|
const transportType = this.activeTransport ?? "hid";
|
|
3068
|
+
const operationId = this._activeOperationId();
|
|
2227
3069
|
const waitPromise = this._uiRegistry.wait(
|
|
2228
3070
|
UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2229
|
-
{ timeoutMs: 6e4 }
|
|
3071
|
+
{ timeoutMs: 6e4, operationId }
|
|
2230
3072
|
);
|
|
2231
3073
|
this.emitter.emit(UI_REQUEST.REQUEST_DEVICE_PERMISSION, {
|
|
2232
3074
|
type: UI_REQUEST.REQUEST_DEVICE_PERMISSION,
|
|
2233
|
-
payload: { transportType, connectId, deviceId }
|
|
3075
|
+
payload: { transportType, connectId, deviceId, operationId }
|
|
2234
3076
|
});
|
|
2235
3077
|
let response;
|
|
2236
3078
|
const onAbort = () => {
|
|
@@ -2276,7 +3118,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2276
3118
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
2277
3119
|
const e = err;
|
|
2278
3120
|
const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
2279
|
-
return ledgerFailure(
|
|
3121
|
+
return ledgerFailure(
|
|
3122
|
+
e.code,
|
|
3123
|
+
e.message ?? "Unknown error",
|
|
3124
|
+
e.appName,
|
|
3125
|
+
tag,
|
|
3126
|
+
params,
|
|
3127
|
+
void 0,
|
|
3128
|
+
isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
|
|
3129
|
+
);
|
|
2280
3130
|
}
|
|
2281
3131
|
const mapped = mapLedgerError(err);
|
|
2282
3132
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -2303,7 +3153,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2303
3153
|
deviceId: device.deviceId,
|
|
2304
3154
|
connectId: device.connectId,
|
|
2305
3155
|
label: device.name,
|
|
2306
|
-
connectionType: this.connector.connectionType,
|
|
3156
|
+
connectionType: device.connectionType ?? this.connector.connectionType,
|
|
2307
3157
|
rssi: device.rssi,
|
|
2308
3158
|
isConnectable: device.isConnectable,
|
|
2309
3159
|
serialNumber: device.serialNumber,
|
|
@@ -2329,8 +3179,8 @@ var LedgerAdapter = _LedgerAdapter;
|
|
|
2329
3179
|
|
|
2330
3180
|
// src/connector/LedgerConnectorBase.ts
|
|
2331
3181
|
import {
|
|
2332
|
-
EConnectorInteraction as
|
|
2333
|
-
HardwareErrorCode as
|
|
3182
|
+
EConnectorInteraction as EConnectorInteraction3,
|
|
3183
|
+
HardwareErrorCode as HardwareErrorCode7,
|
|
2334
3184
|
isKnownNonTargetHardwareVendor,
|
|
2335
3185
|
serializeConnectorError
|
|
2336
3186
|
} from "@onekeyfe/hwk-adapter-core";
|
|
@@ -2603,7 +3453,7 @@ var LedgerDeviceManager = class {
|
|
|
2603
3453
|
|
|
2604
3454
|
// src/signer/SignerManager.ts
|
|
2605
3455
|
import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
|
|
2606
|
-
import { ContextModuleBuilder } from "@ledgerhq/context-module";
|
|
3456
|
+
import { ContextModuleBuilder, ContextModuleChainID } from "@ledgerhq/context-module";
|
|
2607
3457
|
|
|
2608
3458
|
// src/signer/SignerEth.ts
|
|
2609
3459
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
@@ -2869,7 +3719,7 @@ var SignerManager = class _SignerManager {
|
|
|
2869
3719
|
return (args) => new SignerEthBuilder(args);
|
|
2870
3720
|
}
|
|
2871
3721
|
static _createContextModule() {
|
|
2872
|
-
const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3722
|
+
const contextModule = new ContextModuleBuilder({}).setChain(ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
|
|
2873
3723
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2874
3724
|
}
|
|
2875
3725
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -2907,6 +3757,27 @@ function collapseSignerInteraction(interaction) {
|
|
|
2907
3757
|
return EConnectorInteraction2.ConfirmOnDevice;
|
|
2908
3758
|
}
|
|
2909
3759
|
}
|
|
3760
|
+
function wireSignerToSession(ctx, sessionId, chain, signer) {
|
|
3761
|
+
signer.onInteraction = (interaction) => {
|
|
3762
|
+
debugLog(`[LedgerConnector] ${chain}.onInteraction:`, interaction);
|
|
3763
|
+
ctx.emit("ui-event", {
|
|
3764
|
+
type: collapseSignerInteraction(interaction),
|
|
3765
|
+
payload: { sessionId }
|
|
3766
|
+
});
|
|
3767
|
+
};
|
|
3768
|
+
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
3769
|
+
return signer;
|
|
3770
|
+
}
|
|
3771
|
+
async function runSignerCall(ctx, sessionId, call) {
|
|
3772
|
+
try {
|
|
3773
|
+
return await call();
|
|
3774
|
+
} catch (err) {
|
|
3775
|
+
ctx.invalidateSession(sessionId);
|
|
3776
|
+
throw ctx.wrapError(err);
|
|
3777
|
+
} finally {
|
|
3778
|
+
ctx.clearCanceller(sessionId);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
2910
3781
|
|
|
2911
3782
|
// src/connector/chains/evm.ts
|
|
2912
3783
|
async function evmGetAddress(ctx, sessionId, params) {
|
|
@@ -3339,7 +4210,8 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
3339
4210
|
}
|
|
3340
4211
|
|
|
3341
4212
|
// src/connector/chains/sol.ts
|
|
3342
|
-
import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
|
|
4213
|
+
import { bytesToHex, hexToBytes as hexToBytes3, prepareSolanaOffchainMessageV1 } from "@onekeyfe/hwk-adapter-core";
|
|
4214
|
+
import bs58 from "bs58";
|
|
3343
4215
|
|
|
3344
4216
|
// src/signer/SignerSol.ts
|
|
3345
4217
|
var SignerSol = class {
|
|
@@ -3423,8 +4295,21 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3423
4295
|
const path = normalizePath(params.path);
|
|
3424
4296
|
const messageBytes = hexToBytes3(params.message);
|
|
3425
4297
|
try {
|
|
4298
|
+
if (params.messageVersion === 1) {
|
|
4299
|
+
const preparedMessage = prepareSolanaOffchainMessageV1({
|
|
4300
|
+
message: messageBytes,
|
|
4301
|
+
requiredSigners: params.requiredSigners
|
|
4302
|
+
});
|
|
4303
|
+
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
4304
|
+
"@ledgerhq/device-signer-kit-solana"
|
|
4305
|
+
);
|
|
4306
|
+
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
4307
|
+
version: SignMessageVersion.Raw
|
|
4308
|
+
});
|
|
4309
|
+
return { signature: decodeBase58Signature(result2.signature) };
|
|
4310
|
+
}
|
|
3426
4311
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
3427
|
-
return { signature: result.signature };
|
|
4312
|
+
return { signature: decodeBase58EnvelopeSignature(result.signature) };
|
|
3428
4313
|
} catch (err) {
|
|
3429
4314
|
ctx.invalidateSession(sessionId);
|
|
3430
4315
|
throw ctx.wrapError(err);
|
|
@@ -3432,11 +4317,27 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3432
4317
|
ctx.clearCanceller(sessionId);
|
|
3433
4318
|
}
|
|
3434
4319
|
}
|
|
4320
|
+
function decodeBase58Signature(signature) {
|
|
4321
|
+
const bytes = bs58.decode(signature);
|
|
4322
|
+
if (bytes.length !== 64) {
|
|
4323
|
+
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
4324
|
+
}
|
|
4325
|
+
return bytesToHex(bytes);
|
|
4326
|
+
}
|
|
4327
|
+
function decodeBase58EnvelopeSignature(envelope) {
|
|
4328
|
+
const bytes = bs58.decode(envelope);
|
|
4329
|
+
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4330
|
+
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4331
|
+
}
|
|
4332
|
+
return bytesToHex(bytes.subarray(1, 65));
|
|
4333
|
+
}
|
|
3435
4334
|
async function _createSolSigner(ctx, sessionId) {
|
|
3436
4335
|
const dmk = await ctx.getOrCreateDmk();
|
|
3437
|
-
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit(
|
|
4336
|
+
const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
|
|
4337
|
+
"@ledgerhq/context-module"
|
|
4338
|
+
);
|
|
3438
4339
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
3439
|
-
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4340
|
+
const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
|
|
3440
4341
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
3441
4342
|
const signer = new SignerSol(sdkSigner);
|
|
3442
4343
|
signer.onInteraction = (interaction) => {
|
|
@@ -3452,361 +4353,147 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
3452
4353
|
return signer;
|
|
3453
4354
|
}
|
|
3454
4355
|
|
|
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";
|
|
4356
|
+
// src/connector/chains/zcash.ts
|
|
4357
|
+
import { bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
|
|
3461
4358
|
|
|
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);
|
|
4359
|
+
// src/signer/SignerZcash.ts
|
|
4360
|
+
var SignerZcash = class {
|
|
4361
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4362
|
+
constructor(_sdk) {
|
|
4363
|
+
this._sdk = _sdk;
|
|
3528
4364
|
}
|
|
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
|
-
}
|
|
4365
|
+
/** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
|
|
4366
|
+
async getFullViewingKey(derivationPath, options) {
|
|
4367
|
+
const action = this._sdk.getFullViewingKey(derivationPath, options);
|
|
4368
|
+
return deviceActionToPromise(
|
|
4369
|
+
action,
|
|
4370
|
+
this.onInteraction,
|
|
4371
|
+
void 0,
|
|
4372
|
+
this.onRegisterCanceller
|
|
3565
4373
|
);
|
|
3566
4374
|
}
|
|
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})`
|
|
4375
|
+
/** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
|
|
4376
|
+
async getShieldedAddress(derivationPath, options) {
|
|
4377
|
+
const action = this._sdk.getShieldedAddress(derivationPath, options);
|
|
4378
|
+
return deviceActionToPromise(
|
|
4379
|
+
action,
|
|
4380
|
+
this.onInteraction,
|
|
4381
|
+
void 0,
|
|
4382
|
+
this.onRegisterCanceller
|
|
3629
4383
|
);
|
|
3630
4384
|
}
|
|
3631
|
-
_isDashboard(appName) {
|
|
3632
|
-
return appName === DASHBOARD_APP_NAME;
|
|
3633
|
-
}
|
|
3634
|
-
_wait() {
|
|
3635
|
-
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
3636
|
-
}
|
|
3637
4385
|
};
|
|
3638
4386
|
|
|
3639
|
-
// src/connector/chains/
|
|
3640
|
-
function
|
|
3641
|
-
|
|
4387
|
+
// src/connector/chains/zcash.ts
|
|
4388
|
+
async function zcashGetFullViewingKey(ctx, sessionId, params) {
|
|
4389
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4390
|
+
const path = normalizePath(params.path);
|
|
4391
|
+
const mode = params.mode ?? "ufvk";
|
|
4392
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4393
|
+
const result = await signer.getFullViewingKey(path, { mode });
|
|
4394
|
+
if (result.mode === "ufvk") {
|
|
4395
|
+
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
4396
|
+
}
|
|
4397
|
+
return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
|
|
4398
|
+
});
|
|
3642
4399
|
}
|
|
3643
|
-
async function
|
|
3644
|
-
const
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
type: EConnectorInteraction3.ConfirmOpenApp,
|
|
3650
|
-
payload: { sessionId }
|
|
4400
|
+
async function zcashGetShieldedAddress(ctx, sessionId, params) {
|
|
4401
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4402
|
+
const path = normalizePath(params.path);
|
|
4403
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4404
|
+
const result = await signer.getShieldedAddress(path, {
|
|
4405
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
3651
4406
|
});
|
|
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
|
-
}
|
|
4407
|
+
return { address: result.address, path: params.path };
|
|
4408
|
+
});
|
|
3719
4409
|
}
|
|
3720
|
-
async function
|
|
4410
|
+
async function _createZcashSigner(ctx, sessionId) {
|
|
3721
4411
|
const dmk = await ctx.getOrCreateDmk();
|
|
3722
|
-
const
|
|
3723
|
-
|
|
4412
|
+
const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
|
|
4413
|
+
const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
|
|
4414
|
+
return wireSignerToSession(ctx, sessionId, "zcash", new SignerZcash(sdkSigner));
|
|
3724
4415
|
}
|
|
3725
4416
|
|
|
3726
|
-
// src/
|
|
3727
|
-
import
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
4417
|
+
// src/connector/chains/tron.ts
|
|
4418
|
+
import { HardwareErrorCode as HardwareErrorCode6, bytesToHex as bytesToHex3, hexToBytes as hexToBytes4 } from "@onekeyfe/hwk-adapter-core";
|
|
4419
|
+
|
|
4420
|
+
// src/signer/SignerTron.ts
|
|
4421
|
+
var SignerTron = class {
|
|
4422
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4423
|
+
constructor(_sdk) {
|
|
4424
|
+
this._sdk = _sdk;
|
|
4425
|
+
}
|
|
4426
|
+
/** Base58 Tron address plus the uncompressed public key at `derivationPath`. */
|
|
4427
|
+
async getAddress(derivationPath, options) {
|
|
4428
|
+
const action = this._sdk.getAddress(derivationPath, options);
|
|
4429
|
+
const result = await deviceActionToPromise(
|
|
4430
|
+
action,
|
|
4431
|
+
this.onInteraction,
|
|
4432
|
+
void 0,
|
|
4433
|
+
this.onRegisterCanceller
|
|
4434
|
+
);
|
|
4435
|
+
return { address: result.address, publicKey: result.publicKey };
|
|
3733
4436
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
}
|
|
3744
|
-
result.set(statusCode, data.length);
|
|
3745
|
-
return result;
|
|
4437
|
+
/** Sign a protobuf-encoded raw transaction. */
|
|
4438
|
+
async signTransaction(derivationPath, transaction, options) {
|
|
4439
|
+
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
4440
|
+
return deviceActionToPromise(
|
|
4441
|
+
action,
|
|
4442
|
+
this.onInteraction,
|
|
4443
|
+
void 0,
|
|
4444
|
+
this.onRegisterCanceller
|
|
4445
|
+
);
|
|
3746
4446
|
}
|
|
3747
|
-
|
|
4447
|
+
/** Sign a personal message (TIP-191). */
|
|
4448
|
+
async signPersonalMessage(derivationPath, message, options) {
|
|
4449
|
+
const action = this._sdk.signPersonalMessage(derivationPath, message, options);
|
|
4450
|
+
return deviceActionToPromise(
|
|
4451
|
+
action,
|
|
4452
|
+
this.onInteraction,
|
|
4453
|
+
void 0,
|
|
4454
|
+
this.onRegisterCanceller
|
|
4455
|
+
);
|
|
3748
4456
|
}
|
|
3749
4457
|
};
|
|
3750
4458
|
|
|
3751
4459
|
// src/connector/chains/tron.ts
|
|
3752
4460
|
async function tronGetAddress(ctx, sessionId, params) {
|
|
4461
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3753
4462
|
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
|
-
);
|
|
4463
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4464
|
+
const result = await tronSigner.getAddress(path, {
|
|
4465
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4466
|
+
});
|
|
4467
|
+
return { address: result.address, publicKey: result.publicKey, path: params.path };
|
|
4468
|
+
});
|
|
3770
4469
|
}
|
|
3771
4470
|
async function tronSignTransaction(ctx, sessionId, params) {
|
|
3772
4471
|
if (!params.rawTxHex) {
|
|
3773
4472
|
throw Object.assign(
|
|
3774
4473
|
new Error("TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."),
|
|
3775
|
-
{ code:
|
|
4474
|
+
{ code: HardwareErrorCode6.InvalidParams }
|
|
3776
4475
|
);
|
|
3777
4476
|
}
|
|
4477
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3778
4478
|
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
|
-
);
|
|
4479
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4480
|
+
const signature = await tronSigner.signTransaction(path, hexToBytes4(params.rawTxHex));
|
|
4481
|
+
return { signature: bytesToHex3(signature) };
|
|
4482
|
+
});
|
|
3793
4483
|
}
|
|
3794
4484
|
async function tronSignMessage(ctx, sessionId, params) {
|
|
4485
|
+
const tronSigner = await _createTronSigner(ctx, sessionId);
|
|
3795
4486
|
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
|
-
);
|
|
4487
|
+
return runSignerCall(ctx, sessionId, async () => {
|
|
4488
|
+
const signature = await tronSigner.signPersonalMessage(path, hexToBytes4(params.messageHex));
|
|
4489
|
+
return { signature: bytesToHex3(signature) };
|
|
4490
|
+
});
|
|
3806
4491
|
}
|
|
3807
|
-
async function
|
|
4492
|
+
async function _createTronSigner(ctx, sessionId) {
|
|
3808
4493
|
const dmk = await ctx.getOrCreateDmk();
|
|
3809
|
-
|
|
4494
|
+
const { SignerTrxBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-tron");
|
|
4495
|
+
const sdkSigner = new SignerTrxBuilder({ dmk, sessionId }).build();
|
|
4496
|
+
return wireSignerToSession(ctx, sessionId, "tron", new SignerTron(sdkSigner));
|
|
3810
4497
|
}
|
|
3811
4498
|
|
|
3812
4499
|
// src/device-apps/customActions.ts
|
|
@@ -3985,7 +4672,7 @@ var DeviceApps = class {
|
|
|
3985
4672
|
seTargetId: v.seTargetId,
|
|
3986
4673
|
mcuTargetId: v.mcuTargetId,
|
|
3987
4674
|
seVersion: v.seVersion,
|
|
3988
|
-
seFlagsHex:
|
|
4675
|
+
seFlagsHex: bytesToHex4(v.seFlags),
|
|
3989
4676
|
mcuSephVersion: v.mcuSephVersion,
|
|
3990
4677
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
3991
4678
|
hwVersion: v.hwVersion
|
|
@@ -4059,7 +4746,7 @@ var DeviceApps = class {
|
|
|
4059
4746
|
}
|
|
4060
4747
|
}
|
|
4061
4748
|
};
|
|
4062
|
-
function
|
|
4749
|
+
function bytesToHex4(bytes) {
|
|
4063
4750
|
if (!bytes) return "";
|
|
4064
4751
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4065
4752
|
}
|
|
@@ -4102,10 +4789,11 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4102
4789
|
evm: "Ethereum",
|
|
4103
4790
|
btc: "Bitcoin",
|
|
4104
4791
|
sol: "Solana",
|
|
4105
|
-
tron: "Tron"
|
|
4792
|
+
tron: "Tron",
|
|
4793
|
+
zcash: "Zcash"
|
|
4106
4794
|
};
|
|
4107
4795
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4108
|
-
Object.values(
|
|
4796
|
+
Object.values(HardwareErrorCode7).filter((value) => typeof value === "number")
|
|
4109
4797
|
);
|
|
4110
4798
|
var BLE_CONNECT_SCAN_TIMEOUT_MS = 1500;
|
|
4111
4799
|
var LEDGER_RELAY_ALLOWED_ROOT_DOMAINS = ["onekeytest.com", "onekey.com"];
|
|
@@ -4142,6 +4830,10 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4142
4830
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4143
4831
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4144
4832
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4833
|
+
case "@ledgerhq/device-signer-kit-tron":
|
|
4834
|
+
return import("@ledgerhq/device-signer-kit-tron");
|
|
4835
|
+
case "@ledgerhq/device-signer-kit-zcash":
|
|
4836
|
+
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4145
4837
|
case "@ledgerhq/context-module":
|
|
4146
4838
|
return import("@ledgerhq/context-module");
|
|
4147
4839
|
default:
|
|
@@ -4201,6 +4893,7 @@ var LedgerConnectorBase = class {
|
|
|
4201
4893
|
this._ctx = {
|
|
4202
4894
|
emit: (event, data) => this._emit(event, data),
|
|
4203
4895
|
invalidateSession: (sid) => this._invalidateSession(sid),
|
|
4896
|
+
teardownSecureChannelSession: (sid) => this._teardownSecureChannelSession(sid),
|
|
4204
4897
|
wrapError: (err, opts) => this._wrapError(err, opts),
|
|
4205
4898
|
getOrCreateDmk: () => this._getOrCreateDmk(),
|
|
4206
4899
|
getDeviceManager: () => this._getDeviceManager(),
|
|
@@ -4300,7 +4993,7 @@ var LedgerConnectorBase = class {
|
|
|
4300
4993
|
`Ledger BLE connect did not return within ${HANG_CEILING_MS / 6e4}min \u2014 DMK hang fallback.`
|
|
4301
4994
|
);
|
|
4302
4995
|
err._tag = ERROR_TAG.BlePairingTimeout;
|
|
4303
|
-
err.code =
|
|
4996
|
+
err.code = HardwareErrorCode7.BlePairingTimeout;
|
|
4304
4997
|
reject(err);
|
|
4305
4998
|
}, HANG_CEILING_MS);
|
|
4306
4999
|
});
|
|
@@ -4333,7 +5026,7 @@ var LedgerConnectorBase = class {
|
|
|
4333
5026
|
"Ledger device is not currently advertising. Wake up and unlock the device, keep it nearby, then try again."
|
|
4334
5027
|
);
|
|
4335
5028
|
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
4336
|
-
err.code =
|
|
5029
|
+
err.code = HardwareErrorCode7.DeviceNotFound;
|
|
4337
5030
|
throw err;
|
|
4338
5031
|
};
|
|
4339
5032
|
const doConnect = async (path) => {
|
|
@@ -4390,14 +5083,14 @@ var LedgerConnectorBase = class {
|
|
|
4390
5083
|
this._resetSignersAndSessions();
|
|
4391
5084
|
if (isLedgerBleConnectionType(this.connectionType)) {
|
|
4392
5085
|
const tag = err?._tag;
|
|
4393
|
-
if (isKnownConnectionTag(tag)) {
|
|
5086
|
+
if (isKnownConnectionTag(tag) && !isConnectionOpeningTag(tag)) {
|
|
4394
5087
|
throw err;
|
|
4395
5088
|
}
|
|
4396
5089
|
const wrapped = new Error(
|
|
4397
5090
|
"Ledger Bluetooth pairing failed. Make sure the device is unlocked and nearby, then try again."
|
|
4398
5091
|
);
|
|
4399
5092
|
wrapped._tag = ERROR_TAG.BleGattBondingFailed;
|
|
4400
|
-
wrapped.code =
|
|
5093
|
+
wrapped.code = HardwareErrorCode7.BlePairingTimeout;
|
|
4401
5094
|
wrapped.originalError = err;
|
|
4402
5095
|
throw wrapped;
|
|
4403
5096
|
}
|
|
@@ -4475,7 +5168,7 @@ var LedgerConnectorBase = class {
|
|
|
4475
5168
|
this._unwatchSessionState(sessionId);
|
|
4476
5169
|
this._signerManager?.invalidate(sessionId);
|
|
4477
5170
|
this._cancellers.get(sessionId)?.({
|
|
4478
|
-
code:
|
|
5171
|
+
code: HardwareErrorCode7.DeviceDisconnected,
|
|
4479
5172
|
tag: "DeviceDisconnected",
|
|
4480
5173
|
message: "Device disconnected"
|
|
4481
5174
|
});
|
|
@@ -4501,7 +5194,7 @@ var LedgerConnectorBase = class {
|
|
|
4501
5194
|
success: false,
|
|
4502
5195
|
error: serializeConnectorError(
|
|
4503
5196
|
Object.assign(new Error("Ledger app is unresponsive"), {
|
|
4504
|
-
code:
|
|
5197
|
+
code: HardwareErrorCode7.DeviceAppStuck,
|
|
4505
5198
|
_tag: ERROR_TAG.DeviceAppStuck,
|
|
4506
5199
|
originalError: err
|
|
4507
5200
|
})
|
|
@@ -4513,7 +5206,7 @@ var LedgerConnectorBase = class {
|
|
|
4513
5206
|
success: false,
|
|
4514
5207
|
error: serializeConnectorError(
|
|
4515
5208
|
Object.assign(new Error("Device communication interrupted, please retry"), {
|
|
4516
|
-
code:
|
|
5209
|
+
code: HardwareErrorCode7.TransportError,
|
|
4517
5210
|
_tag: ERROR_TAG.DeviceTransportStuck,
|
|
4518
5211
|
originalError: err
|
|
4519
5212
|
})
|
|
@@ -4559,6 +5252,11 @@ var LedgerConnectorBase = class {
|
|
|
4559
5252
|
return solSignTransaction(ctx, sessionId, params);
|
|
4560
5253
|
case "solSignMessage":
|
|
4561
5254
|
return solSignMessage(ctx, sessionId, params);
|
|
5255
|
+
// ZCASH
|
|
5256
|
+
case "zcashGetFullViewingKey":
|
|
5257
|
+
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5258
|
+
case "zcashGetShieldedAddress":
|
|
5259
|
+
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
4562
5260
|
// TRON
|
|
4563
5261
|
case "tronGetAddress":
|
|
4564
5262
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -4583,7 +5281,7 @@ var LedgerConnectorBase = class {
|
|
|
4583
5281
|
try {
|
|
4584
5282
|
return await apps.install(p.appName, ({ progress }) => {
|
|
4585
5283
|
ctx.emit("ui-event", {
|
|
4586
|
-
type:
|
|
5284
|
+
type: EConnectorInteraction3.AppInstallProgress,
|
|
4587
5285
|
payload: {
|
|
4588
5286
|
sessionId,
|
|
4589
5287
|
appName: p.appName,
|
|
@@ -4592,7 +5290,7 @@ var LedgerConnectorBase = class {
|
|
|
4592
5290
|
});
|
|
4593
5291
|
});
|
|
4594
5292
|
} catch (err) {
|
|
4595
|
-
ctx.
|
|
5293
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4596
5294
|
throw ctx.wrapError(err);
|
|
4597
5295
|
} finally {
|
|
4598
5296
|
ctx.clearCanceller(sessionId);
|
|
@@ -4682,7 +5380,7 @@ var LedgerConnectorBase = class {
|
|
|
4682
5380
|
);
|
|
4683
5381
|
return { isGenuine: output.isGenuine, deviceId };
|
|
4684
5382
|
} catch (err) {
|
|
4685
|
-
ctx.
|
|
5383
|
+
ctx.teardownSecureChannelSession(sessionId);
|
|
4686
5384
|
throw ctx.wrapError(err);
|
|
4687
5385
|
} finally {
|
|
4688
5386
|
ctx.clearCanceller(sessionId);
|
|
@@ -4801,9 +5499,26 @@ var LedgerConnectorBase = class {
|
|
|
4801
5499
|
}
|
|
4802
5500
|
return this._deviceAppsManager;
|
|
4803
5501
|
}
|
|
5502
|
+
// DeviceAppsManager is a per-call factory with no cached session state, so
|
|
5503
|
+
// there is nothing to invalidate for it here.
|
|
4804
5504
|
_invalidateSession(sessionId) {
|
|
4805
5505
|
this._signerManager?.invalidate(sessionId);
|
|
4806
5506
|
}
|
|
5507
|
+
/**
|
|
5508
|
+
* Fire the canceller so DMK closes the secure-channel WebSocket. The DMK device
|
|
5509
|
+
* session is kept: unlock/retry recovery reuses it.
|
|
5510
|
+
*/
|
|
5511
|
+
_teardownSecureChannelSession(sessionId) {
|
|
5512
|
+
const cancel = this._cancellers.get(sessionId);
|
|
5513
|
+
this._cancellers.delete(sessionId);
|
|
5514
|
+
if (cancel) {
|
|
5515
|
+
try {
|
|
5516
|
+
cancel();
|
|
5517
|
+
} catch {
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
this._invalidateSession(sessionId);
|
|
5521
|
+
}
|
|
4807
5522
|
/**
|
|
4808
5523
|
* Replace an old session with a new one after app switch.
|
|
4809
5524
|
* Emits device-connect so the adapter updates its _sessions Map.
|
|
@@ -4888,7 +5603,7 @@ var LedgerConnectorBase = class {
|
|
|
4888
5603
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
4889
5604
|
*/
|
|
4890
5605
|
_ctxForMethod(method) {
|
|
4891
|
-
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5606
|
+
const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
|
|
4892
5607
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
4893
5608
|
if (!defaultAppName) return this._ctx;
|
|
4894
5609
|
return {
|
|
@@ -4922,6 +5637,209 @@ var LedgerConnectorBase = class {
|
|
|
4922
5637
|
return error;
|
|
4923
5638
|
}
|
|
4924
5639
|
};
|
|
5640
|
+
|
|
5641
|
+
// src/transport/DmkTransport.ts
|
|
5642
|
+
import Transport from "@ledgerhq/hw-transport";
|
|
5643
|
+
var DmkTransport = class extends Transport {
|
|
5644
|
+
constructor(dmk, sessionId) {
|
|
5645
|
+
super();
|
|
5646
|
+
this._dmk = dmk;
|
|
5647
|
+
this._sessionId = sessionId;
|
|
5648
|
+
}
|
|
5649
|
+
async exchange(apdu) {
|
|
5650
|
+
const response = await this._dmk.sendApdu({
|
|
5651
|
+
sessionId: this._sessionId,
|
|
5652
|
+
apdu: new Uint8Array(apdu)
|
|
5653
|
+
});
|
|
5654
|
+
const { data, statusCode } = response;
|
|
5655
|
+
const result = Buffer.alloc(data.length + 2);
|
|
5656
|
+
if (data.length > 0) {
|
|
5657
|
+
result.set(data, 0);
|
|
5658
|
+
}
|
|
5659
|
+
result.set(statusCode, data.length);
|
|
5660
|
+
return result;
|
|
5661
|
+
}
|
|
5662
|
+
async close() {
|
|
5663
|
+
}
|
|
5664
|
+
};
|
|
5665
|
+
|
|
5666
|
+
// src/app/AppManager.ts
|
|
5667
|
+
import {
|
|
5668
|
+
CloseAppCommand,
|
|
5669
|
+
GetAppAndVersionCommand,
|
|
5670
|
+
OpenAppCommand,
|
|
5671
|
+
isSuccessCommandResult
|
|
5672
|
+
} from "@ledgerhq/device-management-kit";
|
|
5673
|
+
import { HardwareErrorCode as HardwareErrorCode8 } from "@onekeyfe/hwk-adapter-core";
|
|
5674
|
+
var APP_NAME_MAP = {
|
|
5675
|
+
ETH: "Ethereum",
|
|
5676
|
+
BTC: "Bitcoin",
|
|
5677
|
+
SOL: "Solana",
|
|
5678
|
+
TRX: "Tron",
|
|
5679
|
+
ZEC: "Zcash",
|
|
5680
|
+
XRP: "XRP",
|
|
5681
|
+
ADA: "Cardano",
|
|
5682
|
+
DOT: "Polkadot",
|
|
5683
|
+
ATOM: "Cosmos"
|
|
5684
|
+
};
|
|
5685
|
+
var DASHBOARD_APP_NAME = "BOLOS";
|
|
5686
|
+
var AppManager = class {
|
|
5687
|
+
constructor(dmk, options) {
|
|
5688
|
+
this._dmk = dmk;
|
|
5689
|
+
this._waitMs = options?.waitMs ?? 1e3;
|
|
5690
|
+
this._maxRetries = options?.maxRetries ?? 10;
|
|
5691
|
+
}
|
|
5692
|
+
/**
|
|
5693
|
+
* Return the Ledger app name for a given chain ticker,
|
|
5694
|
+
* or undefined if the chain is not supported.
|
|
5695
|
+
*/
|
|
5696
|
+
static getAppName(chain) {
|
|
5697
|
+
return APP_NAME_MAP[chain];
|
|
5698
|
+
}
|
|
5699
|
+
/**
|
|
5700
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
5701
|
+
*
|
|
5702
|
+
* Flow:
|
|
5703
|
+
* 1. Check the currently running app.
|
|
5704
|
+
* 2. If it is already the target, return immediately.
|
|
5705
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
5706
|
+
* 4. Open the target app.
|
|
5707
|
+
* 5. Poll until the device confirms the target app is running.
|
|
5708
|
+
*/
|
|
5709
|
+
/**
|
|
5710
|
+
* @param onConfirmOnDevice Called BEFORE OpenAppCommand is issued — the
|
|
5711
|
+
* device is about to display "Open <app>" on screen and wait for the
|
|
5712
|
+
* user's button press. UI consumers should show their "open app" prompt
|
|
5713
|
+
* in response. NOT called when the target app is already open (no user
|
|
5714
|
+
* interaction needed in that case).
|
|
5715
|
+
*
|
|
5716
|
+
* Important: OpenAppCommand is blocking. It does not resolve until the user
|
|
5717
|
+
* has physically confirmed on the device, so anything that runs AFTER
|
|
5718
|
+
* `await this._openApp(...)` lands AFTER the prompt is already gone.
|
|
5719
|
+
* Hence the callback must fire BEFORE that await.
|
|
5720
|
+
*/
|
|
5721
|
+
async ensureAppOpen(sessionId, targetAppName, onConfirmOnDevice) {
|
|
5722
|
+
const currentApp = await this._getCurrentApp(sessionId);
|
|
5723
|
+
if (currentApp === targetAppName) {
|
|
5724
|
+
return;
|
|
5725
|
+
}
|
|
5726
|
+
if (!this._isDashboard(currentApp)) {
|
|
5727
|
+
await this._closeCurrentApp(sessionId);
|
|
5728
|
+
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
5729
|
+
}
|
|
5730
|
+
onConfirmOnDevice?.();
|
|
5731
|
+
await this._openApp(sessionId, targetAppName);
|
|
5732
|
+
await this._waitForApp(sessionId, targetAppName);
|
|
5733
|
+
}
|
|
5734
|
+
// ---------------------------------------------------------------------------
|
|
5735
|
+
// Private helpers
|
|
5736
|
+
// ---------------------------------------------------------------------------
|
|
5737
|
+
async _getCurrentApp(sessionId) {
|
|
5738
|
+
const result = await this._dmk.sendCommand({
|
|
5739
|
+
sessionId,
|
|
5740
|
+
command: new GetAppAndVersionCommand()
|
|
5741
|
+
});
|
|
5742
|
+
if (isSuccessCommandResult(result)) {
|
|
5743
|
+
debugLog("[AppManager] currentApp:", result.data.name);
|
|
5744
|
+
return result.data.name;
|
|
5745
|
+
}
|
|
5746
|
+
const errResult = result;
|
|
5747
|
+
const dmkErr = errResult.error ?? {};
|
|
5748
|
+
const original = dmkErr.originalError;
|
|
5749
|
+
debugLog(
|
|
5750
|
+
"[AppManager] _getCurrentApp failed sessionId=",
|
|
5751
|
+
sessionId,
|
|
5752
|
+
"tag=",
|
|
5753
|
+
dmkErr._tag,
|
|
5754
|
+
"errorCode=",
|
|
5755
|
+
dmkErr.errorCode,
|
|
5756
|
+
"message=",
|
|
5757
|
+
dmkErr.message,
|
|
5758
|
+
"originalErrorMessage=",
|
|
5759
|
+
original?.message ?? String(original ?? "")
|
|
5760
|
+
);
|
|
5761
|
+
throw Object.assign(
|
|
5762
|
+
new Error(
|
|
5763
|
+
dmkErr.message ?? "Failed to get current app from device"
|
|
5764
|
+
),
|
|
5765
|
+
{
|
|
5766
|
+
_tag: dmkErr._tag,
|
|
5767
|
+
errorCode: dmkErr.errorCode,
|
|
5768
|
+
originalError: original
|
|
5769
|
+
}
|
|
5770
|
+
);
|
|
5771
|
+
}
|
|
5772
|
+
async _openApp(sessionId, appName) {
|
|
5773
|
+
const result = await this._dmk.sendCommand({
|
|
5774
|
+
sessionId,
|
|
5775
|
+
command: new OpenAppCommand({ appName })
|
|
5776
|
+
});
|
|
5777
|
+
if (!isSuccessCommandResult(result)) {
|
|
5778
|
+
const dmkErr = result.error;
|
|
5779
|
+
const errorCode = "errorCode" in dmkErr && dmkErr.errorCode != null ? String(dmkErr.errorCode) : "";
|
|
5780
|
+
const message = "message" in dmkErr && typeof dmkErr.message === "string" ? dmkErr.message : "";
|
|
5781
|
+
debugLog(
|
|
5782
|
+
"[AppManager] openApp failed:",
|
|
5783
|
+
appName,
|
|
5784
|
+
"errorCode:",
|
|
5785
|
+
errorCode,
|
|
5786
|
+
"tag:",
|
|
5787
|
+
dmkErr._tag
|
|
5788
|
+
);
|
|
5789
|
+
let code;
|
|
5790
|
+
if (errorCode === "6807" || /unknown application/i.test(message)) {
|
|
5791
|
+
code = HardwareErrorCode8.AppNotInstalled;
|
|
5792
|
+
} else if (errorCode === "5501" || dmkErr._tag === "ActionRefusedError") {
|
|
5793
|
+
code = HardwareErrorCode8.UserRejected;
|
|
5794
|
+
}
|
|
5795
|
+
throw Object.assign(new Error(`Failed to open "${appName}"`), {
|
|
5796
|
+
_tag: ERROR_TAG.OpenAppCommand,
|
|
5797
|
+
code,
|
|
5798
|
+
errorCode,
|
|
5799
|
+
statusCode: errorCode,
|
|
5800
|
+
appName,
|
|
5801
|
+
originalError: dmkErr
|
|
5802
|
+
});
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
async _closeCurrentApp(sessionId) {
|
|
5806
|
+
debugLog("[AppManager] closeCurrentApp");
|
|
5807
|
+
await this._dmk.sendCommand({
|
|
5808
|
+
sessionId,
|
|
5809
|
+
command: new CloseAppCommand()
|
|
5810
|
+
});
|
|
5811
|
+
}
|
|
5812
|
+
/**
|
|
5813
|
+
* Poll the device until the expected app is reported as running,
|
|
5814
|
+
* or throw after `_maxRetries` attempts.
|
|
5815
|
+
*/
|
|
5816
|
+
async _waitForApp(sessionId, expectedAppName) {
|
|
5817
|
+
let lastSeen = "";
|
|
5818
|
+
for (let i = 0; i < this._maxRetries; i++) {
|
|
5819
|
+
await this._wait();
|
|
5820
|
+
const current = await this._getCurrentApp(sessionId);
|
|
5821
|
+
lastSeen = current;
|
|
5822
|
+
if (current === expectedAppName) {
|
|
5823
|
+
return;
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5826
|
+
debugLog(
|
|
5827
|
+
"[AppManager] waitForApp exhausted: expected=",
|
|
5828
|
+
expectedAppName,
|
|
5829
|
+
"lastSeen=",
|
|
5830
|
+
lastSeen
|
|
5831
|
+
);
|
|
5832
|
+
throw new Error(
|
|
5833
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries (last seen: ${lastSeen})`
|
|
5834
|
+
);
|
|
5835
|
+
}
|
|
5836
|
+
_isDashboard(appName) {
|
|
5837
|
+
return appName === DASHBOARD_APP_NAME;
|
|
5838
|
+
}
|
|
5839
|
+
_wait() {
|
|
5840
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
5841
|
+
}
|
|
5842
|
+
};
|
|
4925
5843
|
export {
|
|
4926
5844
|
AppManager,
|
|
4927
5845
|
DmkTransport,
|
|
@@ -4932,6 +5850,7 @@ export {
|
|
|
4932
5850
|
SignerEth,
|
|
4933
5851
|
SignerManager,
|
|
4934
5852
|
SignerSol,
|
|
5853
|
+
SignerZcash,
|
|
4935
5854
|
debugLog,
|
|
4936
5855
|
deviceActionToPromise,
|
|
4937
5856
|
isDeviceLockedError,
|