@onekeyfe/hwk-ledger-adapter 1.2.2-alpha.9 → 1.2.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +62 -28
- package/dist/index.d.ts +62 -28
- package/dist/index.js +1113 -465
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1112 -446
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -9
package/dist/index.mjs
CHANGED
|
@@ -6,30 +6,41 @@ import {
|
|
|
6
6
|
DeviceJobQueue,
|
|
7
7
|
EConnectorInteraction,
|
|
8
8
|
HardwareErrorCode as HardwareErrorCode3,
|
|
9
|
+
InteractionRegistry,
|
|
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
|
+
isHardwareInteractionId,
|
|
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"]);
|
|
@@ -213,7 +224,6 @@ function isBlePairingFailureError(err) {
|
|
|
213
224
|
return false;
|
|
214
225
|
}
|
|
215
226
|
var CONNECTION_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
216
|
-
ERROR_TAG.DeviceLocked,
|
|
217
227
|
ERROR_TAG.DeviceNotAdvertising,
|
|
218
228
|
ERROR_TAG.BlePairingTimeout,
|
|
219
229
|
ERROR_TAG.BleGattBondingFailed,
|
|
@@ -448,11 +458,21 @@ function mapLedgerError(err, opts) {
|
|
|
448
458
|
}
|
|
449
459
|
const errAppName = err && typeof err === "object" ? err.appName : void 0;
|
|
450
460
|
const appName = errAppName ?? opts?.defaultAppName;
|
|
451
|
-
return {
|
|
461
|
+
return {
|
|
462
|
+
code,
|
|
463
|
+
message: enrichErrorMessage(code, originalMessage),
|
|
464
|
+
origin: defaultOriginForCode(code),
|
|
465
|
+
appName
|
|
466
|
+
};
|
|
452
467
|
}
|
|
453
468
|
|
|
454
469
|
// src/adapter/methods/allNetworkGetAddress.ts
|
|
455
|
-
import {
|
|
470
|
+
import {
|
|
471
|
+
HardwareErrorCode as HardwareErrorCode2,
|
|
472
|
+
failure,
|
|
473
|
+
resolveHardwareOperationTarget,
|
|
474
|
+
runAllNetworkGetAddress
|
|
475
|
+
} from "@onekeyfe/hwk-adapter-core";
|
|
456
476
|
|
|
457
477
|
// src/utils/sdkEventBus.ts
|
|
458
478
|
var listeners = /* @__PURE__ */ new Set();
|
|
@@ -509,57 +529,81 @@ var LEDGER_BTC_NETWORK_COIN_MAP = {
|
|
|
509
529
|
var LEDGER_UNSUPPORTED_ALLNETWORK_NETWORKS = /* @__PURE__ */ new Set(["doge", "dogecoin"]);
|
|
510
530
|
function createAllNetworkGetAddress({
|
|
511
531
|
callChain,
|
|
512
|
-
getChainFingerprint
|
|
532
|
+
getChainFingerprint,
|
|
533
|
+
retainInteraction,
|
|
534
|
+
errorToFailure
|
|
513
535
|
}) {
|
|
514
536
|
return async function allNetworkGetAddress(connectId, _deviceId, params) {
|
|
515
|
-
debugLog("[LedgerAdapter][REQ]", {
|
|
537
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
538
|
+
method: "allNetworkGetAddress",
|
|
539
|
+
connectId,
|
|
540
|
+
itemCount: params.bundle.length
|
|
541
|
+
});
|
|
542
|
+
const target = resolveHardwareOperationTarget(connectId, params.interactionId, "ledger");
|
|
543
|
+
if (!target.success) return target;
|
|
544
|
+
const effectiveTargetId = target.payload.targetId ?? "";
|
|
545
|
+
let releaseInteractionRetention;
|
|
546
|
+
try {
|
|
547
|
+
releaseInteractionRetention = target.payload.interactionId ? retainInteraction(target.payload.interactionId) : void 0;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
return errorToFailure(error);
|
|
550
|
+
}
|
|
516
551
|
const installContext = {};
|
|
517
552
|
const commonParams = {
|
|
518
|
-
autoInstallApp: params.autoInstallApp
|
|
553
|
+
autoInstallApp: params.autoInstallApp,
|
|
554
|
+
interactionId: target.payload.interactionId,
|
|
555
|
+
knownConnections: params.knownConnections,
|
|
556
|
+
extra: params.extra,
|
|
557
|
+
allowDeviceSelection: params.allowDeviceSelection
|
|
519
558
|
};
|
|
520
559
|
const chainFingerprints = /* @__PURE__ */ new Map();
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
560
|
+
try {
|
|
561
|
+
const result = await runAllNetworkGetAddress({
|
|
562
|
+
connectId: effectiveTargetId,
|
|
563
|
+
deviceId: _deviceId,
|
|
564
|
+
params,
|
|
565
|
+
normalizeItem: normalizeLedgerAllNetworkItem,
|
|
566
|
+
buildUnsupportedNetworkResponse: (item) => isUnsupportedLedgerAllNetworkNetwork(item) ? buildUnsupportedNetworkResponse(item) : void 0,
|
|
567
|
+
callItem: async ({ method, chain, item }) => {
|
|
568
|
+
const itemDeviceId = getItemDeviceId(item) ?? chainFingerprints.get(chain) ?? "";
|
|
569
|
+
return callAllNetworkMethod(
|
|
570
|
+
callChain,
|
|
571
|
+
effectiveTargetId,
|
|
572
|
+
itemDeviceId,
|
|
573
|
+
method,
|
|
574
|
+
item,
|
|
575
|
+
commonParams,
|
|
576
|
+
installContext
|
|
577
|
+
);
|
|
578
|
+
},
|
|
579
|
+
attachIdentity: async ({ item, chain, payload }) => attachLedgerIdentity(
|
|
580
|
+
getChainFingerprint,
|
|
581
|
+
effectiveTargetId,
|
|
534
582
|
item,
|
|
535
|
-
|
|
583
|
+
chain,
|
|
584
|
+
payload,
|
|
585
|
+
chainFingerprints,
|
|
536
586
|
installContext
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
debugLog("[LedgerAdapter][RES]", {
|
|
558
|
-
method: "allNetworkGetAddress",
|
|
559
|
-
success: result.success,
|
|
560
|
-
payload: result
|
|
561
|
-
});
|
|
562
|
-
return result;
|
|
587
|
+
),
|
|
588
|
+
shouldAbortBundle: isTopLevelAllNetworkFailure,
|
|
589
|
+
buildTopLevelFailure: (response) => {
|
|
590
|
+
const code = response.payload?.code ?? HardwareErrorCode2.DeviceMismatch;
|
|
591
|
+
return failure(
|
|
592
|
+
code,
|
|
593
|
+
response.payload?.error ?? "All-network get-address aborted",
|
|
594
|
+
response.payload?.params
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
debugLog("[LedgerAdapter][RES]", {
|
|
599
|
+
method: "allNetworkGetAddress",
|
|
600
|
+
success: result.success,
|
|
601
|
+
payload: result
|
|
602
|
+
});
|
|
603
|
+
return result;
|
|
604
|
+
} finally {
|
|
605
|
+
releaseInteractionRetention?.();
|
|
606
|
+
}
|
|
563
607
|
};
|
|
564
608
|
}
|
|
565
609
|
function isTopLevelAllNetworkFailure(response) {
|
|
@@ -567,7 +611,7 @@ function isTopLevelAllNetworkFailure(response) {
|
|
|
567
611
|
return false;
|
|
568
612
|
}
|
|
569
613
|
const code = response.payload?.code;
|
|
570
|
-
return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
|
|
614
|
+
return code === HardwareErrorCode2.DeviceMismatch || code === HardwareErrorCode2.DeviceDisconnected || code === HardwareErrorCode2.OperationTimeout || code === HardwareErrorCode2.TransportError || code === HardwareErrorCode2.InteractionEnded || code === HardwareErrorCode2.InteractionNotFound || code === HardwareErrorCode2.UserAborted || code === HardwareErrorCode2.UserRejected;
|
|
571
615
|
}
|
|
572
616
|
function getItemDeviceId(item) {
|
|
573
617
|
const { deviceId } = item;
|
|
@@ -597,8 +641,13 @@ function normalizeLedgerAllNetworkItem(method, item) {
|
|
|
597
641
|
const coin = LEDGER_BTC_NETWORK_COIN_MAP[item.network];
|
|
598
642
|
return coin ? { ...item, coin } : item;
|
|
599
643
|
}
|
|
600
|
-
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints) {
|
|
601
|
-
const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(
|
|
644
|
+
async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain, payload, chainFingerprints, context) {
|
|
645
|
+
const fingerprint = getItemDeviceId(item) || chainFingerprints.get(chain) || await bootstrapChainFingerprint(
|
|
646
|
+
getChainFingerprint,
|
|
647
|
+
context.connection?.connectId ?? connectId,
|
|
648
|
+
chain,
|
|
649
|
+
context
|
|
650
|
+
);
|
|
602
651
|
if (!fingerprint) {
|
|
603
652
|
return buildFingerprintBootstrapFailure(item, chain);
|
|
604
653
|
}
|
|
@@ -619,8 +668,8 @@ async function attachLedgerIdentity(getChainFingerprint, connectId, item, chain,
|
|
|
619
668
|
}
|
|
620
669
|
};
|
|
621
670
|
}
|
|
622
|
-
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain) {
|
|
623
|
-
const response = await getChainFingerprint(connectId, chain);
|
|
671
|
+
async function bootstrapChainFingerprint(getChainFingerprint, connectId, chain, context) {
|
|
672
|
+
const response = await getChainFingerprint(connectId, chain, context);
|
|
624
673
|
return response.success ? response.payload : "";
|
|
625
674
|
}
|
|
626
675
|
function buildFingerprintBootstrapFailure(item, chain) {
|
|
@@ -725,8 +774,27 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
725
774
|
constructor(connector, options) {
|
|
726
775
|
this.vendor = "ledger";
|
|
727
776
|
this.emitter = new TypedEventEmitter();
|
|
777
|
+
this._interactions = new InteractionRegistry({
|
|
778
|
+
vendor: "ledger",
|
|
779
|
+
onEnded: (interaction, reason) => {
|
|
780
|
+
const binding = this._pendingInteractionBindings.get(interaction.interactionId);
|
|
781
|
+
if (binding?.selectedConnection?.requestId === this._bindingSelectionRequestId) {
|
|
782
|
+
this._finishBleBinding("cancelled");
|
|
783
|
+
}
|
|
784
|
+
this._pendingInteractionBindings.delete(interaction.interactionId);
|
|
785
|
+
this.emitter.emit(SDK.INTERACTION_ENDED, {
|
|
786
|
+
type: SDK.INTERACTION_ENDED,
|
|
787
|
+
payload: { interactionId: interaction.interactionId, reason }
|
|
788
|
+
});
|
|
789
|
+
if (reason === "timeout") {
|
|
790
|
+
void this._releaseInteractionConnection(interaction);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
});
|
|
728
794
|
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
729
795
|
this._sessions = /* @__PURE__ */ new Map();
|
|
796
|
+
this._pendingInteractionBindings = /* @__PURE__ */ new Map();
|
|
797
|
+
this._verifiedBleReconnectTargets = /* @__PURE__ */ new Map();
|
|
730
798
|
this._uiRegistry = new UiRequestRegistry();
|
|
731
799
|
// BTC App rejects account index >= 100 unless display=true. Cached per
|
|
732
800
|
// adapter instance: first 100+ path asks the user once via UI request,
|
|
@@ -740,10 +808,30 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
740
808
|
this._deviceAuthenticityQueueTail = Promise.resolve();
|
|
741
809
|
// Shared across concurrent callers — only `cancel()` aborts.
|
|
742
810
|
this._doConnectAbortController = null;
|
|
811
|
+
this._unsettledConnectorOperations = /* @__PURE__ */ new Map();
|
|
812
|
+
this._connectorIdleWaiters = /* @__PURE__ */ new Set();
|
|
813
|
+
this._resetPromise = null;
|
|
814
|
+
this._stateGeneration = 0;
|
|
815
|
+
this._connectorTeardownTail = Promise.resolve();
|
|
816
|
+
this._pendingConnectorTeardowns = 0;
|
|
817
|
+
this._activeInteractionJobs = /* @__PURE__ */ new Set();
|
|
818
|
+
this._pendingInteractionDisconnects = /* @__PURE__ */ new Set();
|
|
743
819
|
this._installProgressLastEmittedValue = -Infinity;
|
|
744
820
|
this.allNetworkGetAddress = createAllNetworkGetAddress({
|
|
745
821
|
callChain: this.callChain.bind(this),
|
|
746
|
-
getChainFingerprint: (connectId, chain) =>
|
|
822
|
+
getChainFingerprint: async (connectId, chain, context) => {
|
|
823
|
+
try {
|
|
824
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
825
|
+
chain,
|
|
826
|
+
(method, params) => this.connectorCall(connectId, method, params, void 0, void 0, void 0, context)
|
|
827
|
+
);
|
|
828
|
+
return success(fingerprint);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
return this.errorToFailure(error);
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
retainInteraction: (interactionId) => this._interactions.retain(interactionId),
|
|
834
|
+
errorToFailure: (error) => this.errorToFailure(error)
|
|
747
835
|
});
|
|
748
836
|
// ---------------------------------------------------------------------------
|
|
749
837
|
// Private helpers
|
|
@@ -753,7 +841,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
753
841
|
*
|
|
754
842
|
* - If a session already exists for the given connectId, reuse it.
|
|
755
843
|
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
756
|
-
* - Otherwise: search →
|
|
844
|
+
* - Otherwise: search → one USB device auto-connects; multiple asks the host to choose.
|
|
757
845
|
*/
|
|
758
846
|
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
759
847
|
this._connectingPromise = null;
|
|
@@ -770,6 +858,12 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
770
858
|
});
|
|
771
859
|
};
|
|
772
860
|
this.deviceDisconnectHandler = (data) => {
|
|
861
|
+
const activeInteraction = this._interactions.findActiveByConnectionKey(data.connectId);
|
|
862
|
+
if (activeInteraction && this._activeInteractionJobs.has(activeInteraction.interactionId)) {
|
|
863
|
+
this._pendingInteractionDisconnects.add(activeInteraction.interactionId);
|
|
864
|
+
} else {
|
|
865
|
+
this._interactions.endByConnectionKey(data.connectId, "disconnect");
|
|
866
|
+
}
|
|
773
867
|
this._discoveredDevices.delete(data.connectId);
|
|
774
868
|
this._sessions.delete(data.connectId);
|
|
775
869
|
this.emitter.emit(DEVICE.DISCONNECT, {
|
|
@@ -826,11 +920,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
826
920
|
this._jobQueue = new DeviceJobQueue();
|
|
827
921
|
this.registerEventListeners();
|
|
828
922
|
}
|
|
923
|
+
_finishBleBinding(status) {
|
|
924
|
+
const selectionRequestId = this._bindingSelectionRequestId;
|
|
925
|
+
this._bindingSelectionRequestId = void 0;
|
|
926
|
+
if (selectionRequestId)
|
|
927
|
+
this.emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
|
|
928
|
+
type: UI_REQUEST.DEVICE_BINDING_STATUS,
|
|
929
|
+
payload: { selectionRequestId, status }
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
_isBleConnection() {
|
|
933
|
+
return isLedgerBleConnectionType(this._activeConnectionType ?? this.connector.connectionType);
|
|
934
|
+
}
|
|
829
935
|
// Transport
|
|
830
936
|
get activeTransport() {
|
|
831
|
-
return
|
|
937
|
+
return this._isBleConnection() ? "ble" : "hid";
|
|
832
938
|
}
|
|
833
939
|
getAvailableTransports() {
|
|
940
|
+
if (this.connector.availableTransports) {
|
|
941
|
+
return this.connector.availableTransports.map(
|
|
942
|
+
(transport) => transport === "ble" ? "ble" : "hid"
|
|
943
|
+
);
|
|
944
|
+
}
|
|
834
945
|
return this.activeTransport ? [this.activeTransport] : [];
|
|
835
946
|
}
|
|
836
947
|
// Connector is bound at construction; switching requires a new adapter.
|
|
@@ -847,21 +958,39 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
847
958
|
* The next operation will re-discover and re-connect automatically.
|
|
848
959
|
*/
|
|
849
960
|
resetState() {
|
|
961
|
+
void this._resetStateAndDisconnectSessions();
|
|
962
|
+
}
|
|
963
|
+
_resetStateAndDisconnectSessions() {
|
|
964
|
+
if (this._resetPromise) return this._resetPromise;
|
|
965
|
+
const sessionIds = new Set(this._sessions.values());
|
|
966
|
+
this._stateGeneration += 1;
|
|
967
|
+
this._finishBleBinding("cancelled");
|
|
968
|
+
this._interactions.endAll("runtime-reset");
|
|
969
|
+
this._doConnectAbortController?.abort();
|
|
850
970
|
this._discoveredDevices.clear();
|
|
851
971
|
this._sessions.clear();
|
|
972
|
+
this._verifiedBleReconnectTargets.clear();
|
|
852
973
|
this._connectingPromise = null;
|
|
974
|
+
this._doConnectAbortController = null;
|
|
853
975
|
this._uiRegistry.reset();
|
|
854
976
|
this._jobQueue.clear();
|
|
855
977
|
this._btcHighIndexConfirmedThisSession = false;
|
|
978
|
+
const resetPromise = this._runConnectorTeardown(async () => {
|
|
979
|
+
for (const sessionId of sessionIds) {
|
|
980
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
this._resetPromise = resetPromise;
|
|
984
|
+
return resetPromise.finally(() => {
|
|
985
|
+
if (this._resetPromise === resetPromise) {
|
|
986
|
+
this._resetPromise = null;
|
|
987
|
+
}
|
|
988
|
+
});
|
|
856
989
|
}
|
|
857
990
|
async dispose() {
|
|
858
|
-
this.
|
|
859
|
-
this._jobQueue.clear();
|
|
991
|
+
await this._resetStateAndDisconnectSessions();
|
|
860
992
|
this.unregisterEventListeners();
|
|
861
993
|
this.connector.reset();
|
|
862
|
-
this._discoveredDevices.clear();
|
|
863
|
-
this._sessions.clear();
|
|
864
|
-
this._btcHighIndexConfirmedThisSession = false;
|
|
865
994
|
this.emitter.removeAllListeners();
|
|
866
995
|
}
|
|
867
996
|
uiResponse(response) {
|
|
@@ -871,17 +1000,32 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
871
1000
|
// Device management
|
|
872
1001
|
// ---------------------------------------------------------------------------
|
|
873
1002
|
async searchDevices(options) {
|
|
1003
|
+
return this._searchDevices(options);
|
|
1004
|
+
}
|
|
1005
|
+
async _searchDevices(options, signal) {
|
|
874
1006
|
debugLog("[LedgerAdapter][REQ]", { method: "searchDevices", params: options });
|
|
875
1007
|
try {
|
|
876
1008
|
if (options?.resetSession) {
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
this.
|
|
880
|
-
|
|
881
|
-
|
|
1009
|
+
await this._resetStateAndDisconnectSessions();
|
|
1010
|
+
} else {
|
|
1011
|
+
await this._connectorTeardownTail;
|
|
1012
|
+
}
|
|
1013
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
1014
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1015
|
+
const stateGeneration = this._stateGeneration;
|
|
1016
|
+
const devices = await this.connector.searchDevices(
|
|
1017
|
+
options?.transportType ? {
|
|
1018
|
+
transportType: options.transportType,
|
|
1019
|
+
waitForAll: options.waitForAllTransports
|
|
1020
|
+
} : void 0
|
|
1021
|
+
);
|
|
1022
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1023
|
+
if (stateGeneration !== this._stateGeneration) {
|
|
1024
|
+
throw createHwkError({
|
|
1025
|
+
code: HardwareErrorCode3.UserAborted,
|
|
1026
|
+
message: "Ledger discovery was reset"
|
|
1027
|
+
});
|
|
882
1028
|
}
|
|
883
|
-
await this._ensureDevicePermission();
|
|
884
|
-
const devices = await this.connector.searchDevices();
|
|
885
1029
|
this._discoveredDevices.clear();
|
|
886
1030
|
for (const d of devices) {
|
|
887
1031
|
if (d.connectId) {
|
|
@@ -889,7 +1033,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
889
1033
|
}
|
|
890
1034
|
}
|
|
891
1035
|
if (this._discoveredDevices.size === 0) {
|
|
892
|
-
await this._ensureDevicePermission();
|
|
1036
|
+
await this._ensureDevicePermission(void 0, void 0, signal);
|
|
893
1037
|
}
|
|
894
1038
|
const result = Array.from(this._discoveredDevices.values());
|
|
895
1039
|
debugLog("[LedgerAdapter][RES]", {
|
|
@@ -908,41 +1052,172 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
908
1052
|
throw err;
|
|
909
1053
|
}
|
|
910
1054
|
}
|
|
1055
|
+
async searchDeviceTargets(options) {
|
|
1056
|
+
const devices = await this.searchDevices(options);
|
|
1057
|
+
return devices.map((device) => ({
|
|
1058
|
+
searchTargetId: device.connectId,
|
|
1059
|
+
searchTargetReusePolicy: resolveSearchTargetReusePolicy(device),
|
|
1060
|
+
vendor: "ledger",
|
|
1061
|
+
connectionType: device.connectionType,
|
|
1062
|
+
kind: "physical",
|
|
1063
|
+
label: device.label,
|
|
1064
|
+
model: device.model,
|
|
1065
|
+
modelName: device.modelName,
|
|
1066
|
+
serialNumber: device.serialNumber
|
|
1067
|
+
}));
|
|
1068
|
+
}
|
|
1069
|
+
async listConnectionTargets(options) {
|
|
1070
|
+
const targets = await this.searchDeviceTargets(options);
|
|
1071
|
+
return targets.map(({ searchTargetId, ...target }) => ({
|
|
1072
|
+
...target,
|
|
1073
|
+
targetId: searchTargetId
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
911
1076
|
// USB single-session invariant: evict all sessions, best-effort (see connectDevice).
|
|
912
|
-
async _evictAllSessions() {
|
|
1077
|
+
async _evictAllSessions(preserveInteractionId) {
|
|
1078
|
+
this._interactions.endAll("explicit", preserveInteractionId);
|
|
913
1079
|
if (this._sessions.size === 0) return;
|
|
914
1080
|
const stale = [...this._sessions.values()];
|
|
915
1081
|
this._sessions.clear();
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1082
|
+
await this._runConnectorTeardown(async () => {
|
|
1083
|
+
for (const sid of stale) {
|
|
1084
|
+
try {
|
|
1085
|
+
await this.connector.disconnect(sid);
|
|
1086
|
+
} catch {
|
|
1087
|
+
}
|
|
920
1088
|
}
|
|
921
|
-
}
|
|
1089
|
+
});
|
|
922
1090
|
}
|
|
923
1091
|
static _createDeviceBusyError(method) {
|
|
924
1092
|
return Object.assign(new Error(`Ledger device is busy while calling ${method}`), {
|
|
925
1093
|
code: HardwareErrorCode3.DeviceBusy
|
|
926
1094
|
});
|
|
927
1095
|
}
|
|
928
|
-
async connectDevice(
|
|
1096
|
+
async connectDevice(searchTargetId) {
|
|
1097
|
+
const connected = await this._connectTarget(searchTargetId);
|
|
1098
|
+
if (!connected.success) return connected;
|
|
1099
|
+
return this._createInteraction(searchTargetId, connected.payload);
|
|
1100
|
+
}
|
|
1101
|
+
async acquireInteraction(connectId, context) {
|
|
1102
|
+
try {
|
|
1103
|
+
return await this._jobQueue.enqueue(
|
|
1104
|
+
connectId || "__ledger_acquire__",
|
|
1105
|
+
async (signal) => {
|
|
1106
|
+
const transport = this._isBleConnection() ? "ble" : "usb";
|
|
1107
|
+
const hint = context.knownConnections?.find(
|
|
1108
|
+
(connection) => connection.transport === transport
|
|
1109
|
+
);
|
|
1110
|
+
const target = hint && hint.transport !== "qr" ? hint.connectId : connectId;
|
|
1111
|
+
await this._ensureDevicePermission(target, void 0, signal);
|
|
1112
|
+
const attempt = {
|
|
1113
|
+
...context,
|
|
1114
|
+
extra: context.extra ? { ...context.extra } : void 0,
|
|
1115
|
+
previousConnectId: target
|
|
1116
|
+
};
|
|
1117
|
+
try {
|
|
1118
|
+
const resolvedConnectId = await this.ensureConnected(
|
|
1119
|
+
target,
|
|
1120
|
+
signal,
|
|
1121
|
+
true,
|
|
1122
|
+
void 0,
|
|
1123
|
+
attempt
|
|
1124
|
+
);
|
|
1125
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
1126
|
+
const result = this._createInteraction(connectId, resolvedConnectId);
|
|
1127
|
+
if (result.success && attempt.selectedConnection) {
|
|
1128
|
+
this._pendingInteractionBindings.set(result.payload, attempt);
|
|
1129
|
+
}
|
|
1130
|
+
return result;
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
label: "acquireInteraction",
|
|
1138
|
+
rejectIfBusy: true,
|
|
1139
|
+
busyError: _LedgerAdapter._createDeviceBusyError("acquireInteraction")
|
|
1140
|
+
}
|
|
1141
|
+
);
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
return this.errorToFailure(error);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
_createInteraction(searchTargetId, resolvedConnectId) {
|
|
1147
|
+
this._interactions.endByConnectionKey(resolvedConnectId, "explicit");
|
|
1148
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
1149
|
+
if (sessionId) this._interactions.endByConnectionKey(sessionId, "explicit");
|
|
1150
|
+
const device = this._discoveredDevices.get(resolvedConnectId) ?? {
|
|
1151
|
+
vendor: "ledger",
|
|
1152
|
+
model: "unknown",
|
|
1153
|
+
firmwareVersion: "",
|
|
1154
|
+
deviceId: "",
|
|
1155
|
+
connectId: resolvedConnectId,
|
|
1156
|
+
connectionType: this.connector.connectionType
|
|
1157
|
+
};
|
|
1158
|
+
const interaction = this._interactions.create({
|
|
1159
|
+
searchTargetId,
|
|
1160
|
+
connectId: resolvedConnectId,
|
|
1161
|
+
device,
|
|
1162
|
+
connectionKeys: [this._sessions.get(resolvedConnectId) ?? ""]
|
|
1163
|
+
});
|
|
1164
|
+
return success(interaction.interactionId);
|
|
1165
|
+
}
|
|
1166
|
+
async _connectTarget(connectId, preserveInteractionId, signal) {
|
|
929
1167
|
debugLog("[LedgerAdapter][REQ]", { method: "connectDevice", connectId, params: { connectId } });
|
|
930
1168
|
try {
|
|
931
|
-
|
|
1169
|
+
this._assertConnectorReady("connectDevice");
|
|
1170
|
+
const discoveredType = this._discoveredDevices.get(connectId)?.connectionType;
|
|
1171
|
+
if (discoveredType === "usb" || discoveredType === "ble") {
|
|
1172
|
+
this._activeConnectionType = discoveredType;
|
|
1173
|
+
}
|
|
1174
|
+
if (this._isBleConnection() && !connectId) {
|
|
932
1175
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
933
1176
|
code: HardwareErrorCode3.DeviceNotFound
|
|
934
1177
|
});
|
|
935
1178
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1179
|
+
await this._ensureDevicePermission(connectId, void 0, signal);
|
|
1180
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1181
|
+
if (this.connector.availableTransports && this.connector.availableTransports.length > 1) {
|
|
1182
|
+
await this._evictAllSessions(preserveInteractionId);
|
|
1183
|
+
} else if (this._isBleConnection()) {
|
|
1184
|
+
const previousSessionId = this._sessions.get(connectId);
|
|
1185
|
+
this._interactions.endByConnectionKey(connectId, "explicit", preserveInteractionId);
|
|
1186
|
+
if (previousSessionId) {
|
|
1187
|
+
this._interactions.endByConnectionKey(
|
|
1188
|
+
previousSessionId,
|
|
1189
|
+
"explicit",
|
|
1190
|
+
preserveInteractionId
|
|
1191
|
+
);
|
|
1192
|
+
this._sessions.delete(connectId);
|
|
1193
|
+
await this.connector.disconnect(previousSessionId).catch(() => void 0);
|
|
1194
|
+
}
|
|
1195
|
+
} else {
|
|
1196
|
+
await this._evictAllSessions(preserveInteractionId);
|
|
1197
|
+
}
|
|
1198
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1199
|
+
const stateGeneration = this._stateGeneration;
|
|
1200
|
+
const releaseOperation = this._retainConnectorOperation(`connect:${connectId}`);
|
|
1201
|
+
let session;
|
|
1202
|
+
try {
|
|
1203
|
+
session = this.connector.availableTransports?.length ? await this.connector.connect(connectId, {
|
|
1204
|
+
transportType: this._activeConnectionType ?? "usb"
|
|
1205
|
+
}) : await this.connector.connect(connectId);
|
|
1206
|
+
if (signal?.aborted || stateGeneration !== this._stateGeneration) {
|
|
1207
|
+
await this.connector.disconnect(session.sessionId).catch(() => void 0);
|
|
1208
|
+
throw Object.assign(new Error("Ledger connection aborted"), {
|
|
1209
|
+
code: HardwareErrorCode3.UserAborted
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
} finally {
|
|
1213
|
+
releaseOperation();
|
|
938
1214
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
this._sessions.set(connectId, session.sessionId);
|
|
1215
|
+
const resolvedConnectId = session.deviceInfo?.connectId || connectId;
|
|
1216
|
+
this._sessions.set(resolvedConnectId, session.sessionId);
|
|
942
1217
|
if (session.deviceInfo) {
|
|
943
|
-
this._discoveredDevices.set(
|
|
1218
|
+
this._discoveredDevices.set(resolvedConnectId, session.deviceInfo);
|
|
944
1219
|
}
|
|
945
|
-
const result = success(
|
|
1220
|
+
const result = success(resolvedConnectId);
|
|
946
1221
|
debugLog("[LedgerAdapter][RES]", { method: "connectDevice", success: true, payload: result });
|
|
947
1222
|
return result;
|
|
948
1223
|
} catch (err) {
|
|
@@ -955,30 +1230,60 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
955
1230
|
return failureResult;
|
|
956
1231
|
}
|
|
957
1232
|
}
|
|
958
|
-
async
|
|
1233
|
+
async releaseInteraction(interactionId) {
|
|
1234
|
+
const interaction = this._interactions.find(interactionId);
|
|
1235
|
+
if (!interaction) {
|
|
1236
|
+
this._interactions.resolve(interactionId);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
const endedInteraction = this._interactions.end(interactionId, "explicit");
|
|
1240
|
+
if (!endedInteraction) return;
|
|
1241
|
+
const { connectId } = interaction;
|
|
959
1242
|
debugLog("[LedgerAdapter][REQ]", {
|
|
960
|
-
method: "
|
|
1243
|
+
method: "releaseInteraction",
|
|
961
1244
|
connectId,
|
|
962
1245
|
params: { connectId }
|
|
963
1246
|
});
|
|
964
1247
|
try {
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
await this.connector.disconnect(sessionId);
|
|
968
|
-
this._sessions.delete(connectId);
|
|
969
|
-
}
|
|
970
|
-
debugLog("[LedgerAdapter][RES]", { method: "disconnectDevice", success: true });
|
|
1248
|
+
await this._releaseInteractionConnection(endedInteraction);
|
|
1249
|
+
debugLog("[LedgerAdapter][RES]", { method: "releaseInteraction", success: true });
|
|
971
1250
|
} catch (err) {
|
|
972
1251
|
const e = err;
|
|
973
1252
|
debugLog("[LedgerAdapter][RES]", {
|
|
974
|
-
method: "
|
|
1253
|
+
method: "releaseInteraction",
|
|
975
1254
|
success: false,
|
|
976
1255
|
error: { message: e?.message, _tag: e?._tag, code: e?.code ?? e?.errorCode }
|
|
977
1256
|
});
|
|
978
1257
|
throw err;
|
|
979
1258
|
}
|
|
980
1259
|
}
|
|
981
|
-
async
|
|
1260
|
+
async _releaseInteractionConnection(interaction) {
|
|
1261
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
1262
|
+
for (const [connectId, sessionId] of this._sessions) {
|
|
1263
|
+
if (connectId === interaction.connectId || interaction.connectionKeys.includes(connectId) || interaction.connectionKeys.includes(sessionId)) {
|
|
1264
|
+
this._sessions.delete(connectId);
|
|
1265
|
+
sessionIds.add(sessionId);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
for (const sessionId of sessionIds) {
|
|
1269
|
+
await this._runConnectorTeardown(
|
|
1270
|
+
() => this.connector.disconnect(sessionId).catch(() => void 0)
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
async _releaseLostInteractionConnection(interactionId) {
|
|
1275
|
+
const endedInteraction = this._interactions.end(interactionId, "disconnect");
|
|
1276
|
+
if (!endedInteraction) return;
|
|
1277
|
+
this._discoveredDevices.delete(endedInteraction.connectId);
|
|
1278
|
+
await this._releaseInteractionConnection(endedInteraction);
|
|
1279
|
+
}
|
|
1280
|
+
async getDeviceInfo(connectIdOrInteractionId, deviceId) {
|
|
1281
|
+
let connectId;
|
|
1282
|
+
try {
|
|
1283
|
+
connectId = isHardwareInteractionId(connectIdOrInteractionId) ? this._interactions.resolve(connectIdOrInteractionId).connectId : connectIdOrInteractionId;
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
return this.errorToFailure(error);
|
|
1286
|
+
}
|
|
982
1287
|
debugLog("[LedgerAdapter][REQ]", {
|
|
983
1288
|
method: "getDeviceInfo",
|
|
984
1289
|
connectId,
|
|
@@ -1017,7 +1322,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1017
1322
|
}
|
|
1018
1323
|
}
|
|
1019
1324
|
getSupportedChains() {
|
|
1020
|
-
return ["evm", "btc", "sol", "tron"];
|
|
1325
|
+
return ["evm", "btc", "sol", "tron", "zcash"];
|
|
1021
1326
|
}
|
|
1022
1327
|
// ---------------------------------------------------------------------------
|
|
1023
1328
|
// Chain call helper
|
|
@@ -1053,13 +1358,21 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1053
1358
|
if (params && typeof params === "object") {
|
|
1054
1359
|
const {
|
|
1055
1360
|
autoInstallApp,
|
|
1361
|
+
interactionId,
|
|
1056
1362
|
passphraseState: _passphraseState,
|
|
1057
1363
|
useEmptyPassphrase: _useEmptyPassphrase,
|
|
1364
|
+
knownConnections,
|
|
1365
|
+
extra,
|
|
1366
|
+
allowDeviceSelection,
|
|
1058
1367
|
...rest
|
|
1059
1368
|
} = params;
|
|
1060
1369
|
return {
|
|
1061
1370
|
commonParams: {
|
|
1062
|
-
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0
|
|
1371
|
+
autoInstallApp: typeof autoInstallApp === "boolean" ? autoInstallApp : void 0,
|
|
1372
|
+
interactionId: typeof interactionId === "string" ? interactionId : void 0,
|
|
1373
|
+
knownConnections,
|
|
1374
|
+
extra,
|
|
1375
|
+
allowDeviceSelection: typeof allowDeviceSelection === "boolean" ? allowDeviceSelection : void 0
|
|
1063
1376
|
},
|
|
1064
1377
|
rest
|
|
1065
1378
|
};
|
|
@@ -1235,6 +1548,27 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1235
1548
|
);
|
|
1236
1549
|
}
|
|
1237
1550
|
// ---------------------------------------------------------------------------
|
|
1551
|
+
// Zcash chain methods (viewing key + shielded address reads; Zcash app >= 3.8.0)
|
|
1552
|
+
// ---------------------------------------------------------------------------
|
|
1553
|
+
zcashGetFullViewingKey(connectId, deviceId, params) {
|
|
1554
|
+
return this.callChainWithMergedParams(
|
|
1555
|
+
connectId,
|
|
1556
|
+
deviceId,
|
|
1557
|
+
"zcash",
|
|
1558
|
+
"zcashGetFullViewingKey",
|
|
1559
|
+
params
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
zcashGetShieldedAddress(connectId, deviceId, params) {
|
|
1563
|
+
return this.callChainWithMergedParams(
|
|
1564
|
+
connectId,
|
|
1565
|
+
deviceId,
|
|
1566
|
+
"zcash",
|
|
1567
|
+
"zcashGetShieldedAddress",
|
|
1568
|
+
params
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
// ---------------------------------------------------------------------------
|
|
1238
1572
|
// App management — OS-level Ledger app install / list. Bypasses fingerprint
|
|
1239
1573
|
// and chain-handler dispatch; installApp progress is forwarded to the adapter
|
|
1240
1574
|
// emitter via 'ui-event' AppInstallProgress events.
|
|
@@ -1323,7 +1657,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1323
1657
|
);
|
|
1324
1658
|
}
|
|
1325
1659
|
await this.connector.configure({ ledgerGenuineCheckWebSocketUrl: relayUrl });
|
|
1326
|
-
this.
|
|
1660
|
+
await this._resetStateAndDisconnectSessions();
|
|
1327
1661
|
}
|
|
1328
1662
|
const result = await this.connectorCall(connectId, "getDeviceGenuineCheck", {});
|
|
1329
1663
|
if (!result.isGenuine) {
|
|
@@ -1349,10 +1683,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1349
1683
|
if (relayUrl) {
|
|
1350
1684
|
try {
|
|
1351
1685
|
await this.connector.configure?.({ ledgerGenuineCheckWebSocketUrl: void 0 });
|
|
1352
|
-
this.
|
|
1686
|
+
await this._resetStateAndDisconnectSessions();
|
|
1353
1687
|
} catch {
|
|
1354
1688
|
this.connector.reset();
|
|
1355
|
-
this.
|
|
1689
|
+
await this._resetStateAndDisconnectSessions();
|
|
1356
1690
|
}
|
|
1357
1691
|
}
|
|
1358
1692
|
}
|
|
@@ -1375,13 +1709,36 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1375
1709
|
}
|
|
1376
1710
|
}, 2e3);
|
|
1377
1711
|
this._uiRegistry.cancel();
|
|
1378
|
-
|
|
1379
|
-
|
|
1712
|
+
this._finishBleBinding("cancelled");
|
|
1713
|
+
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
1714
|
+
let interactionId;
|
|
1715
|
+
if (isHardwareInteractionId(connectId)) {
|
|
1716
|
+
interactionId = connectId;
|
|
1717
|
+
} else if (!connectId && isHardwareInteractionId(activeJobId)) {
|
|
1718
|
+
interactionId = activeJobId;
|
|
1719
|
+
}
|
|
1720
|
+
let resolvedConnectId = connectId;
|
|
1721
|
+
if (interactionId) {
|
|
1722
|
+
try {
|
|
1723
|
+
resolvedConnectId = this._interactions.resolve(interactionId).connectId;
|
|
1724
|
+
} catch {
|
|
1725
|
+
resolvedConnectId = void 0;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
const interactionForPhysicalId = !interactionId && connectId ? this._interactions.findActiveByConnectionKey(connectId) : void 0;
|
|
1729
|
+
const queueKey = interactionId ?? (activeJobId === connectId ? connectId : interactionForPhysicalId?.interactionId) ?? connectId;
|
|
1730
|
+
const pendingInteractionId = interactionId ?? interactionForPhysicalId?.interactionId;
|
|
1731
|
+
if (!connectId) this._pendingInteractionBindings.clear();
|
|
1732
|
+
else if (pendingInteractionId) {
|
|
1733
|
+
this._pendingInteractionBindings.delete(pendingInteractionId);
|
|
1734
|
+
}
|
|
1735
|
+
if (queueKey) {
|
|
1736
|
+
this._jobQueue.cancelActiveAndPending(queueKey, userAbortReason);
|
|
1380
1737
|
} else {
|
|
1381
1738
|
this._jobQueue.cancelActiveAndPending(void 0, userAbortReason);
|
|
1382
1739
|
}
|
|
1383
|
-
if (
|
|
1384
|
-
const sessionId = this._sessions.get(
|
|
1740
|
+
if (resolvedConnectId) {
|
|
1741
|
+
const sessionId = this._sessions.get(resolvedConnectId) ?? resolvedConnectId;
|
|
1385
1742
|
void this.connector.cancel(sessionId);
|
|
1386
1743
|
} else {
|
|
1387
1744
|
for (const sid of this._sessions.values()) void this.connector.cancel(sid);
|
|
@@ -1393,40 +1750,83 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1393
1750
|
// ---------------------------------------------------------------------------
|
|
1394
1751
|
// Chain fingerprint
|
|
1395
1752
|
// ---------------------------------------------------------------------------
|
|
1753
|
+
/** A non-empty deviceId is an expected chain fingerprint, not a transport identifier. */
|
|
1396
1754
|
async getChainFingerprint(connectId, deviceId, chain) {
|
|
1397
1755
|
try {
|
|
1398
1756
|
const fingerprint = await this._computeChainFingerprint(
|
|
1399
1757
|
chain,
|
|
1400
1758
|
(method, params) => this.connectorCall(connectId, method, params, void 0, deviceId)
|
|
1401
1759
|
);
|
|
1760
|
+
if (deviceId) {
|
|
1761
|
+
if (fingerprint !== deviceId) {
|
|
1762
|
+
return failure2(
|
|
1763
|
+
HardwareErrorCode3.DeviceMismatch,
|
|
1764
|
+
formatDeviceMismatchError(deviceId, fingerprint)
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
if (isHardwareInteractionId(connectId)) {
|
|
1768
|
+
const interaction = this._interactions.resolve(connectId);
|
|
1769
|
+
await this._publishVerifiedBleBinding(
|
|
1770
|
+
interaction.connectId,
|
|
1771
|
+
chain,
|
|
1772
|
+
fingerprint,
|
|
1773
|
+
void 0,
|
|
1774
|
+
connectId
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1402
1778
|
return success(fingerprint);
|
|
1403
1779
|
} catch (err) {
|
|
1404
1780
|
debugError("[LedgerAdapter] getChainFingerprint error:", chain, err);
|
|
1405
1781
|
return this.errorToFailure(err);
|
|
1406
1782
|
}
|
|
1407
1783
|
}
|
|
1408
|
-
/**
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1784
|
+
/** Discovery may select a BLE target before a later call verifies its wallet. */
|
|
1785
|
+
async _publishVerifiedBleBinding(connectId, chain, fingerprint, attempt, interactionId, previousConnectId = "", signal) {
|
|
1786
|
+
const binding = interactionId ? this._pendingInteractionBindings.get(interactionId) : attempt;
|
|
1787
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== connectId) return;
|
|
1788
|
+
const acknowledged = await requestSaveDeviceBinding(
|
|
1789
|
+
this.emitter,
|
|
1790
|
+
this._uiRegistry,
|
|
1791
|
+
{
|
|
1792
|
+
selectionRequestId: binding.selectedConnection.requestId,
|
|
1793
|
+
connection: { transport: "ble", connectId },
|
|
1794
|
+
identity: { vendor: "ledger", type: "chainFingerprint", chain, value: fingerprint },
|
|
1795
|
+
extra: binding.extra
|
|
1796
|
+
},
|
|
1797
|
+
signal
|
|
1798
|
+
);
|
|
1799
|
+
if (this._bindingSelectionRequestId === binding.selectedConnection.requestId) {
|
|
1800
|
+
this._bindingSelectionRequestId = void 0;
|
|
1801
|
+
}
|
|
1802
|
+
if (interactionId) {
|
|
1803
|
+
this._interactions.resolve(interactionId);
|
|
1804
|
+
this._pendingInteractionBindings.delete(interactionId);
|
|
1805
|
+
}
|
|
1806
|
+
if (acknowledged) return;
|
|
1807
|
+
this.emitter.emit(DEVICE.LEDGER_CONNECTION_VERIFIED, {
|
|
1808
|
+
type: DEVICE.LEDGER_CONNECTION_VERIFIED,
|
|
1809
|
+
payload: {
|
|
1810
|
+
previousConnectId: binding.previousConnectId ?? previousConnectId,
|
|
1811
|
+
connectId,
|
|
1416
1812
|
chain,
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
return { success: true };
|
|
1813
|
+
fingerprint,
|
|
1814
|
+
extra: binding.extra,
|
|
1815
|
+
selectionRequestId: binding.selectedConnection.requestId
|
|
1421
1816
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
/** Verify on the acquired session without re-entering the job queue. */
|
|
1820
|
+
async _verifyDeviceFingerprintWithSession(sessionId, deviceId, chain) {
|
|
1821
|
+
if (!deviceId) return { success: true };
|
|
1822
|
+
const fingerprint = await this._computeChainFingerprint(
|
|
1823
|
+
chain,
|
|
1824
|
+
(method, params) => this._callConnector(sessionId, method, params)
|
|
1825
|
+
);
|
|
1826
|
+
if (fingerprint === deviceId) {
|
|
1827
|
+
return { success: true };
|
|
1429
1828
|
}
|
|
1829
|
+
return { success: false, expected: deviceId, actual: fingerprint };
|
|
1430
1830
|
}
|
|
1431
1831
|
/**
|
|
1432
1832
|
* Compute the chain fingerprint via a caller-supplied call strategy.
|
|
@@ -1455,6 +1855,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1455
1855
|
address = (await callMethod("solGetAddress", { path, showOnDevice: false })).address;
|
|
1456
1856
|
} else if (chain === "tron") {
|
|
1457
1857
|
address = (await callMethod("tronGetAddress", { path, showOnDevice: false })).address;
|
|
1858
|
+
} else if (chain === "zcash") {
|
|
1859
|
+
address = (await callMethod("zcashGetShieldedAddress", { path, showOnDevice: false })).address;
|
|
1458
1860
|
} else {
|
|
1459
1861
|
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
1460
1862
|
}
|
|
@@ -1584,16 +1986,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1584
1986
|
// Layer 1 entry. Caller signal only races the outer awaiter; the shared
|
|
1585
1987
|
// `_doConnect` runs under its own internal controller so caller A's cancel
|
|
1586
1988
|
// doesn't kill caller B's await.
|
|
1587
|
-
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false) {
|
|
1989
|
+
async ensureConnected(connectId, signal, allowUsbEphemeralFallback = false, preserveInteractionId, context) {
|
|
1588
1990
|
if (signal.aborted) _LedgerAdapter._throwIfAborted(signal);
|
|
1589
|
-
|
|
1991
|
+
this._assertConnectorReady("connectDevice");
|
|
1992
|
+
if (this._isBleConnection() && !connectId && !allowUsbEphemeralFallback) {
|
|
1590
1993
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1591
1994
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1592
1995
|
});
|
|
1593
1996
|
}
|
|
1594
1997
|
if (connectId && this._sessions.has(connectId)) return connectId;
|
|
1595
1998
|
if (!connectId && this._sessions.size > 0) {
|
|
1596
|
-
if (!
|
|
1999
|
+
if (!this._isBleConnection() && this._sessions.size > 1) {
|
|
1597
2000
|
throw Object.assign(
|
|
1598
2001
|
new Error(
|
|
1599
2002
|
"Ledger USB session invariant violated: more than one session is active. Please reconnect the device."
|
|
@@ -1603,15 +2006,24 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1603
2006
|
}
|
|
1604
2007
|
return this._sessions.keys().next().value;
|
|
1605
2008
|
}
|
|
1606
|
-
if (!this._connectingPromise) {
|
|
1607
|
-
|
|
1608
|
-
|
|
2009
|
+
if (!this._connectingPromise || this._doConnectAbortController?.signal.aborted) {
|
|
2010
|
+
const controller = new AbortController();
|
|
2011
|
+
this._doConnectAbortController = controller;
|
|
2012
|
+
const innerSignal = controller.signal;
|
|
1609
2013
|
this._connectingPromise = (async () => {
|
|
1610
2014
|
try {
|
|
1611
|
-
return await this._doConnect(
|
|
2015
|
+
return await this._doConnect(
|
|
2016
|
+
innerSignal,
|
|
2017
|
+
connectId,
|
|
2018
|
+
allowUsbEphemeralFallback,
|
|
2019
|
+
preserveInteractionId,
|
|
2020
|
+
context
|
|
2021
|
+
);
|
|
1612
2022
|
} finally {
|
|
1613
|
-
this.
|
|
1614
|
-
|
|
2023
|
+
if (this._doConnectAbortController === controller) {
|
|
2024
|
+
this._connectingPromise = null;
|
|
2025
|
+
this._doConnectAbortController = null;
|
|
2026
|
+
}
|
|
1615
2027
|
}
|
|
1616
2028
|
})();
|
|
1617
2029
|
}
|
|
@@ -1620,22 +2032,49 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1620
2032
|
// Layer 1 main loop — the ONLY place in SDK that emits unlock dialog.
|
|
1621
2033
|
// Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress,
|
|
1622
2034
|
// 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
|
-
|
|
2035
|
+
async _doConnect(internalSignal, targetConnectId, allowUsbEphemeralFallback = false, preserveInteractionId, context) {
|
|
2036
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2037
|
+
if (this.connector.availableTransports?.includes("usb") && this.connector.availableTransports.includes("ble")) {
|
|
2038
|
+
this._activeConnectionType = "usb";
|
|
2039
|
+
const usbDevices = await this._searchDevices({ transportType: "usb" }, internalSignal);
|
|
2040
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2041
|
+
if (usbDevices.length > 0) {
|
|
2042
|
+
const knownUsb = context?.knownConnections?.find(
|
|
2043
|
+
(connection) => connection.transport === "usb"
|
|
2044
|
+
);
|
|
2045
|
+
const usbTarget = knownUsb?.transport === "usb" ? knownUsb.connectId : usbDevices.find((device) => device.connectId === targetConnectId)?.connectId;
|
|
2046
|
+
return this._connectFirstOrSelect(
|
|
2047
|
+
usbDevices,
|
|
2048
|
+
usbTarget,
|
|
2049
|
+
allowUsbEphemeralFallback,
|
|
2050
|
+
preserveInteractionId,
|
|
2051
|
+
context,
|
|
2052
|
+
internalSignal
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
this._activeConnectionType = "ble";
|
|
2056
|
+
const knownBle = context?.knownConnections?.find(
|
|
2057
|
+
(connection) => connection.transport === "ble"
|
|
2058
|
+
);
|
|
2059
|
+
if (knownBle?.transport === "ble") {
|
|
2060
|
+
return this._connectDeviceOrThrow(
|
|
2061
|
+
knownBle.connectId,
|
|
2062
|
+
preserveInteractionId,
|
|
2063
|
+
internalSignal
|
|
2064
|
+
);
|
|
1638
2065
|
}
|
|
2066
|
+
const bleDevices = await this._searchDevices({ transportType: "ble" }, internalSignal);
|
|
2067
|
+
return this._connectFirstOrSelect(
|
|
2068
|
+
bleDevices,
|
|
2069
|
+
void 0,
|
|
2070
|
+
allowUsbEphemeralFallback,
|
|
2071
|
+
preserveInteractionId,
|
|
2072
|
+
context,
|
|
2073
|
+
internalSignal
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
if (this._isBleConnection() && targetConnectId) {
|
|
2077
|
+
return this._connectDeviceOrThrow(targetConnectId, preserveInteractionId, internalSignal);
|
|
1639
2078
|
}
|
|
1640
2079
|
let confirms = 0;
|
|
1641
2080
|
while (!internalSignal.aborted) {
|
|
@@ -1643,22 +2082,28 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1643
2082
|
type: EConnectorInteraction.Searching,
|
|
1644
2083
|
payload: { sessionId: "" }
|
|
1645
2084
|
});
|
|
1646
|
-
let devices = await this.
|
|
2085
|
+
let devices = await this._searchDevices(void 0, internalSignal);
|
|
2086
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1647
2087
|
if (devices.length === 0) {
|
|
1648
2088
|
for (let i = 0; i < 3 && !internalSignal.aborted; i += 1) {
|
|
1649
2089
|
await new Promise((resolve) => {
|
|
1650
2090
|
setTimeout(resolve, DEVICE_CONNECT_RETRY_DELAY_MS);
|
|
1651
2091
|
});
|
|
1652
|
-
|
|
2092
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
2093
|
+
devices = await this._searchDevices(void 0, internalSignal);
|
|
2094
|
+
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1653
2095
|
if (devices.length > 0) break;
|
|
1654
2096
|
}
|
|
1655
2097
|
}
|
|
1656
|
-
if (devices.length > 0) {
|
|
2098
|
+
if (devices.length > 0 || this._isBleConnection() && allowUsbEphemeralFallback) {
|
|
1657
2099
|
try {
|
|
1658
2100
|
return await this._connectFirstOrSelect(
|
|
1659
2101
|
devices,
|
|
1660
2102
|
targetConnectId,
|
|
1661
|
-
allowUsbEphemeralFallback
|
|
2103
|
+
allowUsbEphemeralFallback,
|
|
2104
|
+
preserveInteractionId,
|
|
2105
|
+
context,
|
|
2106
|
+
internalSignal
|
|
1662
2107
|
);
|
|
1663
2108
|
} catch (err) {
|
|
1664
2109
|
if (!isDeviceLockedError(err) && !isDeviceNotAdvertisingError(err) && !isDeviceDisconnectedError(err)) {
|
|
@@ -1687,45 +2132,107 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1687
2132
|
_LedgerAdapter._throwIfAborted(internalSignal);
|
|
1688
2133
|
throw new Error("_doConnect aborted");
|
|
1689
2134
|
}
|
|
1690
|
-
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false) {
|
|
2135
|
+
async _connectFirstOrSelect(devices, targetConnectId, allowUsbEphemeralFallback = false, preserveInteractionId, context, signal) {
|
|
2136
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
1691
2137
|
if (targetConnectId) {
|
|
1692
2138
|
const target = devices.find(
|
|
1693
2139
|
(d) => d.connectId === targetConnectId || d.deviceId === targetConnectId
|
|
1694
2140
|
);
|
|
1695
2141
|
if (target) {
|
|
1696
|
-
return this._connectDeviceOrThrow(target.connectId);
|
|
2142
|
+
return this._connectDeviceOrThrow(target.connectId, preserveInteractionId, signal);
|
|
1697
2143
|
}
|
|
1698
|
-
if (!
|
|
2144
|
+
if (!this._isBleConnection() && devices.length === 1 && allowUsbEphemeralFallback) {
|
|
1699
2145
|
debugLog(
|
|
1700
2146
|
`[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery`
|
|
1701
2147
|
);
|
|
1702
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2148
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveInteractionId, signal);
|
|
1703
2149
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
2150
|
+
if (!this._isBleConnection() || !allowUsbEphemeralFallback) {
|
|
2151
|
+
const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), {
|
|
2152
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
2153
|
+
});
|
|
2154
|
+
if (this._isBleConnection()) {
|
|
2155
|
+
err._tag = ERROR_TAG.DeviceNotAdvertising;
|
|
2156
|
+
}
|
|
2157
|
+
throw err;
|
|
1709
2158
|
}
|
|
1710
|
-
throw err;
|
|
1711
2159
|
}
|
|
1712
|
-
|
|
2160
|
+
const requiresBleSelection = this._isBleConnection();
|
|
2161
|
+
if (requiresBleSelection && !allowUsbEphemeralFallback) {
|
|
1713
2162
|
throw Object.assign(new Error("Ledger BLE connectId is required."), {
|
|
1714
2163
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1715
2164
|
});
|
|
1716
2165
|
}
|
|
1717
|
-
if (
|
|
1718
|
-
|
|
2166
|
+
if (requiresBleSelection && context?.allowDeviceSelection !== false) {
|
|
2167
|
+
const bindingSessionId = context?.bindingSessionId ?? this._uiRegistry.createRequestId();
|
|
2168
|
+
if (context) context.bindingSessionId = bindingSessionId;
|
|
2169
|
+
const { device, requestId } = await requestBleDeviceSelection({
|
|
2170
|
+
emitter: this.emitter,
|
|
2171
|
+
registry: this._uiRegistry,
|
|
2172
|
+
signal: signal ?? new AbortController().signal,
|
|
2173
|
+
scan: async () => (await this._searchDevices({ transportType: "ble", waitForAllTransports: true }, signal)).filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
|
|
2174
|
+
request: {
|
|
2175
|
+
devices: devices.filter((device2) => !context?.rejectedConnectIds?.has(device2.connectId)),
|
|
2176
|
+
bindingSessionId,
|
|
2177
|
+
rejectedConnectId: context?.rejectedConnectId,
|
|
2178
|
+
context: {
|
|
2179
|
+
kind: "bind-connection",
|
|
2180
|
+
transport: "ble",
|
|
2181
|
+
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2182
|
+
},
|
|
2183
|
+
extra: context?.extra
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
if (context) context.selectedConnection = { connectId: device.connectId, requestId };
|
|
2187
|
+
this._bindingSelectionRequestId = requestId;
|
|
2188
|
+
return this._connectDeviceOrThrow(device.connectId, preserveInteractionId, signal);
|
|
2189
|
+
}
|
|
2190
|
+
if (devices.length > 0 && (devices.length > 1 || requiresBleSelection)) {
|
|
2191
|
+
if (context?.allowDeviceSelection === false || !this.emitter.listenerCount(UI_REQUEST.REQUEST_SELECT_DEVICE)) {
|
|
2192
|
+
throw createHwkError({
|
|
2193
|
+
code: HardwareErrorCode3.DeviceNotFound,
|
|
2194
|
+
message: "Select a Ledger device before continuing"
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
const requestId = this._uiRegistry.createRequestId();
|
|
2198
|
+
const waitPromise = this._uiRegistry.wait(
|
|
2199
|
+
UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2200
|
+
{ requestId }
|
|
2201
|
+
);
|
|
2202
|
+
this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, {
|
|
2203
|
+
type: UI_REQUEST.REQUEST_SELECT_DEVICE,
|
|
2204
|
+
payload: {
|
|
2205
|
+
devices,
|
|
2206
|
+
requestId,
|
|
2207
|
+
context: requiresBleSelection ? {
|
|
2208
|
+
kind: "bind-connection",
|
|
2209
|
+
transport: "ble",
|
|
2210
|
+
reason: targetConnectId ? "known-connection-unavailable" : "missing-binding"
|
|
2211
|
+
} : { kind: "select-device", transport: "usb", reason: "multiple-candidates" },
|
|
2212
|
+
extra: context?.extra
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
const { sdkConnectId } = await (signal ? this._abortable(signal, waitPromise) : waitPromise);
|
|
2216
|
+
if (signal) _LedgerAdapter._throwIfAborted(signal);
|
|
2217
|
+
const selected = devices.find((device) => device.connectId === sdkConnectId);
|
|
2218
|
+
if (!selected) {
|
|
2219
|
+
throw Object.assign(new Error("Selected Ledger is no longer available"), {
|
|
2220
|
+
code: HardwareErrorCode3.DeviceNotFound
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
if (context && requiresBleSelection)
|
|
2224
|
+
context.selectedConnection = { connectId: selected.connectId, requestId };
|
|
2225
|
+
return this._connectDeviceOrThrow(selected.connectId, preserveInteractionId, signal);
|
|
1719
2226
|
}
|
|
1720
2227
|
if (devices.length !== 1) {
|
|
1721
2228
|
throw Object.assign(new Error("Ledger device not found."), {
|
|
1722
2229
|
code: HardwareErrorCode3.DeviceNotFound
|
|
1723
2230
|
});
|
|
1724
2231
|
}
|
|
1725
|
-
return this._connectDeviceOrThrow(devices[0].connectId);
|
|
2232
|
+
return this._connectDeviceOrThrow(devices[0].connectId, preserveInteractionId, signal);
|
|
1726
2233
|
}
|
|
1727
|
-
async _connectDeviceOrThrow(chosenConnectId) {
|
|
1728
|
-
const result = await this.
|
|
2234
|
+
async _connectDeviceOrThrow(chosenConnectId, preserveInteractionId, signal) {
|
|
2235
|
+
const result = await this._connectTarget(chosenConnectId, preserveInteractionId, signal);
|
|
1729
2236
|
if (!result.success) {
|
|
1730
2237
|
const payload = result.payload;
|
|
1731
2238
|
const rethrow = Object.assign(new Error(payload.error), {
|
|
@@ -1736,7 +2243,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1736
2243
|
}
|
|
1737
2244
|
throw rethrow;
|
|
1738
2245
|
}
|
|
1739
|
-
return
|
|
2246
|
+
return result.payload;
|
|
1740
2247
|
}
|
|
1741
2248
|
/**
|
|
1742
2249
|
* Call the connector with automatic session resolution and disconnect retry.
|
|
@@ -1766,26 +2273,121 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1766
2273
|
* lives in one place.
|
|
1767
2274
|
*/
|
|
1768
2275
|
async _callConnector(sessionId, method, params, signal) {
|
|
1769
|
-
|
|
2276
|
+
this._assertConnectorReady(method);
|
|
2277
|
+
const releaseOperation = this._retainConnectorOperation(`call:${sessionId}`);
|
|
2278
|
+
let promise;
|
|
2279
|
+
try {
|
|
2280
|
+
promise = this.connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2281
|
+
} catch (error) {
|
|
2282
|
+
releaseOperation();
|
|
2283
|
+
throw error;
|
|
2284
|
+
}
|
|
1770
2285
|
const result = signal ? await this._abortable(signal, promise) : await promise;
|
|
1771
2286
|
return this._unwrapConnectorResult(result);
|
|
1772
2287
|
}
|
|
2288
|
+
_assertConnectorReady(method) {
|
|
2289
|
+
if (this._resetPromise || this._pendingConnectorTeardowns > 0 || this._unsettledConnectorOperations.size > 0) {
|
|
2290
|
+
throw _LedgerAdapter._createDeviceBusyError(method);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
_runConnectorTeardown(task) {
|
|
2294
|
+
const previous = this._connectorTeardownTail;
|
|
2295
|
+
let releaseTail = () => void 0;
|
|
2296
|
+
this._connectorTeardownTail = new Promise((resolve) => {
|
|
2297
|
+
releaseTail = resolve;
|
|
2298
|
+
});
|
|
2299
|
+
this._pendingConnectorTeardowns += 1;
|
|
2300
|
+
return (async () => {
|
|
2301
|
+
try {
|
|
2302
|
+
await previous;
|
|
2303
|
+
await this._waitForConnectorOperationsToDrain();
|
|
2304
|
+
await task();
|
|
2305
|
+
} finally {
|
|
2306
|
+
this._pendingConnectorTeardowns -= 1;
|
|
2307
|
+
releaseTail();
|
|
2308
|
+
}
|
|
2309
|
+
})();
|
|
2310
|
+
}
|
|
2311
|
+
_waitForConnectorOperationsToDrain() {
|
|
2312
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2313
|
+
return Promise.resolve();
|
|
2314
|
+
}
|
|
2315
|
+
return new Promise((resolve) => {
|
|
2316
|
+
this._connectorIdleWaiters.add(resolve);
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
_retainConnectorOperation(key) {
|
|
2320
|
+
this._unsettledConnectorOperations.set(
|
|
2321
|
+
key,
|
|
2322
|
+
(this._unsettledConnectorOperations.get(key) ?? 0) + 1
|
|
2323
|
+
);
|
|
2324
|
+
let released = false;
|
|
2325
|
+
return () => {
|
|
2326
|
+
if (released) return;
|
|
2327
|
+
released = true;
|
|
2328
|
+
const remaining = (this._unsettledConnectorOperations.get(key) ?? 1) - 1;
|
|
2329
|
+
if (remaining > 0) {
|
|
2330
|
+
this._unsettledConnectorOperations.set(key, remaining);
|
|
2331
|
+
} else {
|
|
2332
|
+
this._unsettledConnectorOperations.delete(key);
|
|
2333
|
+
}
|
|
2334
|
+
if (this._unsettledConnectorOperations.size === 0) {
|
|
2335
|
+
for (const resolve of this._connectorIdleWaiters) resolve();
|
|
2336
|
+
this._connectorIdleWaiters.clear();
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
1773
2340
|
async connectorCall(connectId, method, params, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
1774
|
-
|
|
1775
|
-
|
|
2341
|
+
const positionalInteractionId = isHardwareInteractionId(connectId) ? connectId : void 0;
|
|
2342
|
+
if (positionalInteractionId && commonParams?.interactionId && positionalInteractionId !== commonParams.interactionId) {
|
|
2343
|
+
throw createHwkError({
|
|
2344
|
+
code: HardwareErrorCode3.InvalidParams,
|
|
2345
|
+
message: "Conflicting Ledger interaction ids",
|
|
2346
|
+
params: {
|
|
2347
|
+
positionalInteractionId,
|
|
2348
|
+
commonInteractionId: commonParams.interactionId
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
const interactionId = commonParams?.interactionId ?? positionalInteractionId;
|
|
2353
|
+
const interaction = interactionId ? this._interactions.resolve(interactionId) : void 0;
|
|
2354
|
+
const releaseInteractionRetention = interactionId ? this._interactions.retain(interactionId) : void 0;
|
|
2355
|
+
const effectiveConnectId = interaction?.connectId ?? connectId;
|
|
2356
|
+
debugLog("[LedgerAdapter][REQ]", {
|
|
2357
|
+
method,
|
|
2358
|
+
connectId: effectiveConnectId || "(empty)",
|
|
2359
|
+
params
|
|
2360
|
+
});
|
|
2361
|
+
const queueKey = (interactionId ?? effectiveConnectId) || "__ledger_default__";
|
|
1776
2362
|
try {
|
|
1777
2363
|
const result = await this._jobQueue.enqueue(
|
|
1778
2364
|
queueKey,
|
|
1779
|
-
async (signal) =>
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2365
|
+
async (signal) => {
|
|
2366
|
+
if (interactionId) this._activeInteractionJobs.add(interactionId);
|
|
2367
|
+
try {
|
|
2368
|
+
return await this._runConnectorCall(
|
|
2369
|
+
effectiveConnectId,
|
|
2370
|
+
method,
|
|
2371
|
+
params,
|
|
2372
|
+
signal,
|
|
2373
|
+
fingerprint,
|
|
2374
|
+
permissionDeviceId,
|
|
2375
|
+
commonParams,
|
|
2376
|
+
installContext ?? {},
|
|
2377
|
+
interactionId
|
|
2378
|
+
);
|
|
2379
|
+
} catch (error) {
|
|
2380
|
+
this._finishBleBinding(signal.aborted ? "cancelled" : "failed");
|
|
2381
|
+
throw error;
|
|
2382
|
+
} finally {
|
|
2383
|
+
if (interactionId) {
|
|
2384
|
+
this._activeInteractionJobs.delete(interactionId);
|
|
2385
|
+
if (this._pendingInteractionDisconnects.delete(interactionId)) {
|
|
2386
|
+
await this._releaseLostInteractionConnection(interactionId);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
},
|
|
1789
2391
|
{
|
|
1790
2392
|
label: method,
|
|
1791
2393
|
rejectIfBusy: true,
|
|
@@ -1806,6 +2408,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1806
2408
|
}
|
|
1807
2409
|
});
|
|
1808
2410
|
throw err;
|
|
2411
|
+
} finally {
|
|
2412
|
+
releaseInteractionRetention?.();
|
|
1809
2413
|
}
|
|
1810
2414
|
}
|
|
1811
2415
|
/**
|
|
@@ -1815,6 +2419,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1815
2419
|
_abortable(signal, promise) {
|
|
1816
2420
|
const getAbortReason = () => signal.reason ?? this._lastCancelReason ?? new Error("Aborted");
|
|
1817
2421
|
if (signal.aborted) {
|
|
2422
|
+
void promise.catch(() => void 0);
|
|
1818
2423
|
return Promise.reject(getAbortReason());
|
|
1819
2424
|
}
|
|
1820
2425
|
return new Promise((resolve, reject) => {
|
|
@@ -1841,13 +2446,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1841
2446
|
}
|
|
1842
2447
|
}
|
|
1843
2448
|
/** Actual work done under the job queue — connection, fingerprint, call, and recovery. */
|
|
1844
|
-
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext) {
|
|
2449
|
+
async _runConnectorCall(connectId, method, params, signal, fingerprint, permissionDeviceId, commonParams, installContext, interactionId, lockedRetryBudget = _LedgerAdapter.MAX_BUSINESS_RETRY_BUDGET) {
|
|
1845
2450
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2451
|
+
if (!interactionId) {
|
|
2452
|
+
await this._ensureDevicePermission(
|
|
2453
|
+
connectId,
|
|
2454
|
+
permissionDeviceId ?? fingerprint?.deviceId,
|
|
2455
|
+
signal
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
1851
2458
|
_LedgerAdapter._throwIfAborted(signal);
|
|
1852
2459
|
let effectiveParams = params;
|
|
1853
2460
|
if (method === "btcGetPublicKey") {
|
|
@@ -1861,36 +2468,133 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1861
2468
|
effectiveParams = gatedParams;
|
|
1862
2469
|
}
|
|
1863
2470
|
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
1864
|
-
|
|
1865
|
-
|
|
2471
|
+
let businessCallStarted = false;
|
|
2472
|
+
const verifiedBleTarget = connectId ? this._verifiedBleReconnectTargets.get(connectId) : void 0;
|
|
2473
|
+
const knownTransport = this._isBleConnection() ? "ble" : "usb";
|
|
2474
|
+
const hintedConnectId = commonParams?.knownConnections?.find(
|
|
2475
|
+
(connection) => connection.transport === knownTransport
|
|
2476
|
+
);
|
|
2477
|
+
const inputConnectId = hintedConnectId && hintedConnectId.transport !== "qr" ? hintedConnectId.connectId : connectId;
|
|
2478
|
+
const preferredConnectId = fingerprint && !fingerprint.skipFingerprint && verifiedBleTarget?.chain === fingerprint.chain && verifiedBleTarget.fingerprint === fingerprint.deviceId ? verifiedBleTarget.connectId : inputConnectId;
|
|
2479
|
+
const connectionAttempt = { ...commonParams };
|
|
2480
|
+
const bundleConnection = installContext?.connection;
|
|
2481
|
+
if (bundleConnection && this._sessions.get(bundleConnection.connectId) !== bundleConnection.sessionId) {
|
|
2482
|
+
throw createHwkError({
|
|
2483
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2484
|
+
message: "Ledger all-network connection ended"
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
let resolvedConnectId = interactionId ? this._interactions.resolve(interactionId).connectId : bundleConnection?.connectId ?? await this.ensureConnected(
|
|
2488
|
+
preferredConnectId,
|
|
1866
2489
|
signal,
|
|
1867
|
-
allowUsbEphemeralFallback
|
|
2490
|
+
allowUsbEphemeralFallback,
|
|
2491
|
+
void 0,
|
|
2492
|
+
connectionAttempt
|
|
1868
2493
|
);
|
|
1869
|
-
|
|
2494
|
+
let sessionId = this._sessions.get(resolvedConnectId);
|
|
2495
|
+
if (sessionId && installContext && !installContext.connection) {
|
|
2496
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2497
|
+
}
|
|
1870
2498
|
if (!sessionId) {
|
|
2499
|
+
if (interactionId) {
|
|
2500
|
+
this._interactions.end(interactionId, "disconnect");
|
|
2501
|
+
throw createHwkError({
|
|
2502
|
+
code: HardwareErrorCode3.InteractionEnded,
|
|
2503
|
+
message: "Ledger interaction connection is no longer active",
|
|
2504
|
+
params: { interactionId, reason: "disconnect" }
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
1871
2507
|
throw Object.assign(new Error("Auto-connect succeeded but no session found"), {
|
|
1872
2508
|
_tag: ERROR_TAG.DeviceSessionNotFound
|
|
1873
2509
|
});
|
|
1874
2510
|
}
|
|
1875
2511
|
try {
|
|
1876
2512
|
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2513
|
+
for (; ; ) {
|
|
2514
|
+
const fp = await this._abortable(
|
|
2515
|
+
signal,
|
|
2516
|
+
this._verifyDeviceFingerprintWithSession(
|
|
2517
|
+
sessionId,
|
|
2518
|
+
fingerprint.deviceId,
|
|
2519
|
+
fingerprint.chain
|
|
2520
|
+
)
|
|
2521
|
+
);
|
|
2522
|
+
if (fp.success) break;
|
|
2523
|
+
const binding = interactionId ? this._pendingInteractionBindings.get(interactionId) : connectionAttempt;
|
|
2524
|
+
if (!this._isBleConnection() || binding?.selectedConnection?.connectId !== resolvedConnectId) {
|
|
2525
|
+
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2526
|
+
code: HardwareErrorCode3.DeviceMismatch
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
binding.rejectedConnectIds ?? (binding.rejectedConnectIds = /* @__PURE__ */ new Set());
|
|
2530
|
+
binding.rejectedConnectIds.add(resolvedConnectId);
|
|
2531
|
+
binding.rejectedConnectId = resolvedConnectId;
|
|
2532
|
+
this._sessions.delete(resolvedConnectId);
|
|
2533
|
+
await this.connector.disconnect(sessionId);
|
|
2534
|
+
if (interactionId) this._pendingInteractionDisconnects.delete(interactionId);
|
|
2535
|
+
_LedgerAdapter._throwIfAborted(signal);
|
|
2536
|
+
resolvedConnectId = await this._connectFirstOrSelect(
|
|
2537
|
+
[],
|
|
2538
|
+
void 0,
|
|
2539
|
+
true,
|
|
2540
|
+
interactionId,
|
|
2541
|
+
binding,
|
|
2542
|
+
signal
|
|
2543
|
+
);
|
|
2544
|
+
const selectedSession = this._sessions.get(resolvedConnectId);
|
|
2545
|
+
const selectedDevice = this._discoveredDevices.get(resolvedConnectId);
|
|
2546
|
+
if (!selectedSession || !selectedDevice)
|
|
2547
|
+
throw createHwkError({
|
|
2548
|
+
code: HardwareErrorCode3.DeviceDisconnected,
|
|
2549
|
+
message: "Selected Ledger connection ended"
|
|
2550
|
+
});
|
|
2551
|
+
sessionId = selectedSession;
|
|
2552
|
+
if (interactionId)
|
|
2553
|
+
this._interactions.rebind(interactionId, {
|
|
2554
|
+
connectId: resolvedConnectId,
|
|
2555
|
+
device: selectedDevice,
|
|
2556
|
+
connectionKeys: [sessionId]
|
|
2557
|
+
});
|
|
2558
|
+
if (installContext)
|
|
2559
|
+
installContext.connection = { connectId: resolvedConnectId, sessionId };
|
|
2560
|
+
}
|
|
2561
|
+
if (!interactionId && connectionAttempt.selectedConnection?.connectId === resolvedConnectId && this._isBleConnection()) {
|
|
2562
|
+
this._verifiedBleReconnectTargets.set(connectId, {
|
|
2563
|
+
connectId: resolvedConnectId,
|
|
2564
|
+
chain: fingerprint.chain,
|
|
2565
|
+
fingerprint: fingerprint.deviceId
|
|
1888
2566
|
});
|
|
1889
2567
|
}
|
|
2568
|
+
await this._publishVerifiedBleBinding(
|
|
2569
|
+
resolvedConnectId,
|
|
2570
|
+
fingerprint.chain,
|
|
2571
|
+
fingerprint.deviceId,
|
|
2572
|
+
connectionAttempt,
|
|
2573
|
+
interactionId,
|
|
2574
|
+
connectId,
|
|
2575
|
+
signal
|
|
2576
|
+
);
|
|
1890
2577
|
}
|
|
2578
|
+
businessCallStarted = true;
|
|
1891
2579
|
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
1892
2580
|
} catch (err) {
|
|
1893
2581
|
if (signal.aborted) throw err;
|
|
2582
|
+
if (isDeviceDisconnectedError(err) || isDeviceNotAdvertisingError(err) || isTimeoutError(err) || isConnectionLevelError(err)) {
|
|
2583
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2584
|
+
if (interactionId) await this._releaseLostInteractionConnection(interactionId);
|
|
2585
|
+
else {
|
|
2586
|
+
this._sessions.delete(resolvedConnectId);
|
|
2587
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2588
|
+
}
|
|
2589
|
+
const ambiguous = businessCallStarted && !canReplayHardwareMethodAfterTransportFailure(method);
|
|
2590
|
+
const interactionParams = interactionId ? { interactionId, reason: "disconnect" } : void 0;
|
|
2591
|
+
throw createHwkError({
|
|
2592
|
+
code: interactionId ? HardwareErrorCode3.InteractionEnded : mapLedgerError(err).code,
|
|
2593
|
+
message: "Ledger operation connection was lost; start a new operation",
|
|
2594
|
+
params: ambiguous ? operationMayHaveCompletedParams(method, { interactionId }) : interactionParams,
|
|
2595
|
+
recovery: ambiguous ? { scope: "unknown" } : void 0
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
1894
2598
|
const errObj = err;
|
|
1895
2599
|
debugLog("[LedgerAdapter] connectorCall error:", method, {
|
|
1896
2600
|
message: errObj?.message,
|
|
@@ -1902,6 +2606,65 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1902
2606
|
isNotAdvertising: isDeviceNotAdvertisingError(err),
|
|
1903
2607
|
isStuckApp: isStuckAppStateError(err)
|
|
1904
2608
|
});
|
|
2609
|
+
if ((isDeviceLockedError(err) || errObj?.code === HardwareErrorCode3.DeviceLocked) && lockedRetryBudget > 0) {
|
|
2610
|
+
await this._waitForDeviceConnect(signal);
|
|
2611
|
+
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2612
|
+
throw createHwkError({
|
|
2613
|
+
code: interactionId ? HardwareErrorCode3.InteractionEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2614
|
+
message: "Ledger connection ended while waiting for unlock"
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
return this._runConnectorCall(
|
|
2618
|
+
resolvedConnectId,
|
|
2619
|
+
method,
|
|
2620
|
+
effectiveParams,
|
|
2621
|
+
signal,
|
|
2622
|
+
fingerprint,
|
|
2623
|
+
permissionDeviceId,
|
|
2624
|
+
commonParams,
|
|
2625
|
+
installContext,
|
|
2626
|
+
interactionId,
|
|
2627
|
+
lockedRetryBudget - 1
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
if (businessCallStarted && isStuckAppStateError(err)) {
|
|
2631
|
+
await this._sleepAbortable(_LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal);
|
|
2632
|
+
if (this._sessions.get(resolvedConnectId) !== sessionId) {
|
|
2633
|
+
throw createHwkError({
|
|
2634
|
+
code: interactionId ? HardwareErrorCode3.InteractionEnded : HardwareErrorCode3.DeviceDisconnected,
|
|
2635
|
+
message: "Ledger connection ended during the app transition"
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
try {
|
|
2639
|
+
return await this._callConnector(sessionId, method, effectiveParams, signal);
|
|
2640
|
+
} catch (retryErr) {
|
|
2641
|
+
if (isStuckAppStateError(retryErr)) throw err;
|
|
2642
|
+
if (isDeviceDisconnectedError(retryErr) || isDeviceNotAdvertisingError(retryErr) || isTimeoutError(retryErr) || isConnectionLevelError(retryErr)) {
|
|
2643
|
+
if (interactionId) await this._releaseLostInteractionConnection(interactionId);
|
|
2644
|
+
else {
|
|
2645
|
+
this._sessions.delete(resolvedConnectId);
|
|
2646
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2647
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2648
|
+
}
|
|
2649
|
+
throw createHwkError({
|
|
2650
|
+
code: interactionId ? HardwareErrorCode3.InteractionEnded : mapLedgerError(retryErr).code,
|
|
2651
|
+
message: `Ledger ${method} may have completed before the connection was lost`,
|
|
2652
|
+
params: !canReplayHardwareMethodAfterTransportFailure(method) ? operationMayHaveCompletedParams(method, {
|
|
2653
|
+
interactionId,
|
|
2654
|
+
reason: "disconnect"
|
|
2655
|
+
}) : { interactionId, reason: "disconnect" },
|
|
2656
|
+
recovery: !canReplayHardwareMethodAfterTransportFailure(method) ? { scope: "unknown" } : void 0
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
throw retryErr;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
if (!interactionId && err?.code === HardwareErrorCode3.DeviceMismatch) {
|
|
2663
|
+
this._sessions.delete(resolvedConnectId);
|
|
2664
|
+
this._discoveredDevices.delete(resolvedConnectId);
|
|
2665
|
+
await this.connector.disconnect(sessionId).catch(() => void 0);
|
|
2666
|
+
throw err;
|
|
2667
|
+
}
|
|
1905
2668
|
const autoInstallApp = commonParams?.autoInstallApp ?? this._defaultAutoInstallApp;
|
|
1906
2669
|
const isAppMissing = isAppNotInstalledError(err) || err?.code === HardwareErrorCode3.AppNotInstalled;
|
|
1907
2670
|
if (autoInstallApp && isAppMissing) {
|
|
@@ -1957,157 +2720,14 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
1957
2720
|
fingerprint,
|
|
1958
2721
|
permissionDeviceId,
|
|
1959
2722
|
commonParams,
|
|
1960
|
-
installContext
|
|
2723
|
+
installContext,
|
|
2724
|
+
interactionId
|
|
1961
2725
|
);
|
|
1962
2726
|
}
|
|
1963
2727
|
}
|
|
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
2728
|
throw err;
|
|
2075
2729
|
}
|
|
2076
2730
|
}
|
|
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
2731
|
_sleepAbortable(ms, signal) {
|
|
2112
2732
|
return new Promise((resolve, reject) => {
|
|
2113
2733
|
if (signal.aborted) {
|
|
@@ -2125,88 +2745,6 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2125
2745
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2126
2746
|
});
|
|
2127
2747
|
}
|
|
2128
|
-
/**
|
|
2129
|
-
* Clear stale session, reconnect, and retry the call.
|
|
2130
|
-
*
|
|
2131
|
-
* Timeout recovery starts with a full connector reset. After an APDU
|
|
2132
|
-
* timeout, DMK/transport state may still emit malformed responses; retrying
|
|
2133
|
-
* on the same DMK can poison the next chain switch.
|
|
2134
|
-
*/
|
|
2135
|
-
async _retryWithFreshConnection(targetConnectId, method, params, signal, originalErr, fingerprint) {
|
|
2136
|
-
this.connector.reset();
|
|
2137
|
-
this._sessions.clear();
|
|
2138
|
-
this._discoveredDevices.clear();
|
|
2139
|
-
this._connectingPromise = null;
|
|
2140
|
-
const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint;
|
|
2141
|
-
const retryConnectId = await this.ensureConnected(
|
|
2142
|
-
targetConnectId,
|
|
2143
|
-
signal,
|
|
2144
|
-
allowUsbEphemeralFallback
|
|
2145
|
-
);
|
|
2146
|
-
const retrySessionId = this._sessions.get(retryConnectId);
|
|
2147
|
-
if (!retrySessionId) {
|
|
2148
|
-
throw originalErr;
|
|
2149
|
-
}
|
|
2150
|
-
try {
|
|
2151
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2152
|
-
const fp = await this._abortable(
|
|
2153
|
-
signal,
|
|
2154
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2155
|
-
retrySessionId,
|
|
2156
|
-
fingerprint.deviceId,
|
|
2157
|
-
fingerprint.chain
|
|
2158
|
-
)
|
|
2159
|
-
);
|
|
2160
|
-
if (!fp.success) {
|
|
2161
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2162
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2163
|
-
});
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
return await this._callConnector(retrySessionId, method, params, signal);
|
|
2167
|
-
} catch (retryErr) {
|
|
2168
|
-
if (signal.aborted) throw retryErr;
|
|
2169
|
-
this.connector.reset();
|
|
2170
|
-
this._sessions.clear();
|
|
2171
|
-
this._discoveredDevices.clear();
|
|
2172
|
-
this._connectingPromise = null;
|
|
2173
|
-
if (!isDeviceDisconnectedError(retryErr) && !isTimeoutError(retryErr)) {
|
|
2174
|
-
throw retryErr;
|
|
2175
|
-
}
|
|
2176
|
-
debugLog(
|
|
2177
|
-
"[LedgerAdapter] fresh-session retry still failed; resetting connector and rebuilding DMK"
|
|
2178
|
-
);
|
|
2179
|
-
this.connector.reset();
|
|
2180
|
-
this._sessions.clear();
|
|
2181
|
-
this._discoveredDevices.clear();
|
|
2182
|
-
this._connectingPromise = null;
|
|
2183
|
-
const finalConnectId = await this.ensureConnected(
|
|
2184
|
-
targetConnectId,
|
|
2185
|
-
signal,
|
|
2186
|
-
allowUsbEphemeralFallback
|
|
2187
|
-
);
|
|
2188
|
-
const finalSessionId = this._sessions.get(finalConnectId);
|
|
2189
|
-
if (!finalSessionId) {
|
|
2190
|
-
throw originalErr;
|
|
2191
|
-
}
|
|
2192
|
-
if (fingerprint && !fingerprint.skipFingerprint && fingerprint.deviceId) {
|
|
2193
|
-
const fp = await this._abortable(
|
|
2194
|
-
signal,
|
|
2195
|
-
this._verifyDeviceFingerprintWithSession(
|
|
2196
|
-
finalSessionId,
|
|
2197
|
-
fingerprint.deviceId,
|
|
2198
|
-
fingerprint.chain
|
|
2199
|
-
)
|
|
2200
|
-
);
|
|
2201
|
-
if (!fp.success) {
|
|
2202
|
-
throw Object.assign(new Error(formatDeviceMismatchError(fp.expected, fp.actual)), {
|
|
2203
|
-
code: HardwareErrorCode3.DeviceMismatch
|
|
2204
|
-
});
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
return this._callConnector(finalSessionId, method, params, signal);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
2748
|
/**
|
|
2211
2749
|
* Ensure OS-level device permission (Bluetooth / USB) before proceeding.
|
|
2212
2750
|
*
|
|
@@ -2276,7 +2814,15 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2276
2814
|
if (err && typeof err === "object" && "code" in err && typeof err.code === "number") {
|
|
2277
2815
|
const e = err;
|
|
2278
2816
|
const params = e.code === HardwareErrorCode3.DevicePermissionDenied && e.reason ? { permissionDeniedReason: e.reason } : e.params;
|
|
2279
|
-
return ledgerFailure(
|
|
2817
|
+
return ledgerFailure(
|
|
2818
|
+
e.code,
|
|
2819
|
+
e.message ?? "Unknown error",
|
|
2820
|
+
e.appName,
|
|
2821
|
+
tag,
|
|
2822
|
+
params,
|
|
2823
|
+
void 0,
|
|
2824
|
+
isHwkRecoveryHint(e.recovery) ? e.recovery : void 0
|
|
2825
|
+
);
|
|
2280
2826
|
}
|
|
2281
2827
|
const mapped = mapLedgerError(err);
|
|
2282
2828
|
return ledgerFailure(mapped.code, mapped.message, mapped.appName, tag);
|
|
@@ -2303,7 +2849,7 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
2303
2849
|
deviceId: device.deviceId,
|
|
2304
2850
|
connectId: device.connectId,
|
|
2305
2851
|
label: device.name,
|
|
2306
|
-
connectionType: this.connector.connectionType,
|
|
2852
|
+
connectionType: device.connectionType ?? this.connector.connectionType,
|
|
2307
2853
|
rssi: device.rssi,
|
|
2308
2854
|
isConnectable: device.isConnectable,
|
|
2309
2855
|
serialNumber: device.serialNumber,
|
|
@@ -2603,7 +3149,7 @@ var LedgerDeviceManager = class {
|
|
|
2603
3149
|
|
|
2604
3150
|
// src/signer/SignerManager.ts
|
|
2605
3151
|
import { SignerEthBuilder } from "@ledgerhq/device-signer-kit-ethereum";
|
|
2606
|
-
import { ContextModuleBuilder } from "@ledgerhq/context-module";
|
|
3152
|
+
import { ContextModuleBuilder, ContextModuleChainID } from "@ledgerhq/context-module";
|
|
2607
3153
|
|
|
2608
3154
|
// src/signer/SignerEth.ts
|
|
2609
3155
|
import { hexToBytes } from "@onekeyfe/hwk-adapter-core";
|
|
@@ -2869,7 +3415,7 @@ var SignerManager = class _SignerManager {
|
|
|
2869
3415
|
return (args) => new SignerEthBuilder(args);
|
|
2870
3416
|
}
|
|
2871
3417
|
static _createContextModule() {
|
|
2872
|
-
const contextModule = new ContextModuleBuilder({}).removeDefaultLoaders().build();
|
|
3418
|
+
const contextModule = new ContextModuleBuilder({}).setChain(ContextModuleChainID.Ethereum).removeDefaultLoaders().build();
|
|
2873
3419
|
return _SignerManager.wrapBlindSigningReportNonBlocking(contextModule);
|
|
2874
3420
|
}
|
|
2875
3421
|
static wrapBlindSigningReportNonBlocking(contextModule) {
|
|
@@ -3339,7 +3885,8 @@ function _applySignaturesToPsbt(psbtHex, signatures) {
|
|
|
3339
3885
|
}
|
|
3340
3886
|
|
|
3341
3887
|
// src/connector/chains/sol.ts
|
|
3342
|
-
import { bytesToHex, hexToBytes as hexToBytes3 } from "@onekeyfe/hwk-adapter-core";
|
|
3888
|
+
import { bytesToHex, hexToBytes as hexToBytes3, prepareSolanaOffchainMessageV1 } from "@onekeyfe/hwk-adapter-core";
|
|
3889
|
+
import bs58 from "bs58";
|
|
3343
3890
|
|
|
3344
3891
|
// src/signer/SignerSol.ts
|
|
3345
3892
|
var SignerSol = class {
|
|
@@ -3423,8 +3970,21 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3423
3970
|
const path = normalizePath(params.path);
|
|
3424
3971
|
const messageBytes = hexToBytes3(params.message);
|
|
3425
3972
|
try {
|
|
3973
|
+
if (params.messageVersion === 1) {
|
|
3974
|
+
const preparedMessage = prepareSolanaOffchainMessageV1({
|
|
3975
|
+
message: messageBytes,
|
|
3976
|
+
requiredSigners: params.requiredSigners
|
|
3977
|
+
});
|
|
3978
|
+
const { SignMessageVersion } = await ctx.importLedgerKit(
|
|
3979
|
+
"@ledgerhq/device-signer-kit-solana"
|
|
3980
|
+
);
|
|
3981
|
+
const result2 = await solSigner.signMessage(path, preparedMessage.serializedMessage, {
|
|
3982
|
+
version: SignMessageVersion.Raw
|
|
3983
|
+
});
|
|
3984
|
+
return { signature: decodeBase58Signature(result2.signature) };
|
|
3985
|
+
}
|
|
3426
3986
|
const result = await solSigner.signMessage(path, messageBytes);
|
|
3427
|
-
return { signature: result.signature };
|
|
3987
|
+
return { signature: decodeBase58EnvelopeSignature(result.signature) };
|
|
3428
3988
|
} catch (err) {
|
|
3429
3989
|
ctx.invalidateSession(sessionId);
|
|
3430
3990
|
throw ctx.wrapError(err);
|
|
@@ -3432,11 +3992,27 @@ async function solSignMessage(ctx, sessionId, params) {
|
|
|
3432
3992
|
ctx.clearCanceller(sessionId);
|
|
3433
3993
|
}
|
|
3434
3994
|
}
|
|
3995
|
+
function decodeBase58Signature(signature) {
|
|
3996
|
+
const bytes = bs58.decode(signature);
|
|
3997
|
+
if (bytes.length !== 64) {
|
|
3998
|
+
throw new Error(`Ledger Solana signature must be 64 bytes, received ${bytes.length}`);
|
|
3999
|
+
}
|
|
4000
|
+
return bytesToHex(bytes);
|
|
4001
|
+
}
|
|
4002
|
+
function decodeBase58EnvelopeSignature(envelope) {
|
|
4003
|
+
const bytes = bs58.decode(envelope);
|
|
4004
|
+
if (bytes.length < 65 || bytes[0] !== 1) {
|
|
4005
|
+
throw new Error("Ledger Solana signature envelope is invalid");
|
|
4006
|
+
}
|
|
4007
|
+
return bytesToHex(bytes.subarray(1, 65));
|
|
4008
|
+
}
|
|
3435
4009
|
async function _createSolSigner(ctx, sessionId) {
|
|
3436
4010
|
const dmk = await ctx.getOrCreateDmk();
|
|
3437
|
-
const { ContextModuleBuilder: ContextModuleBuilder2 } = await ctx.importLedgerKit(
|
|
4011
|
+
const { ContextModuleBuilder: ContextModuleBuilder2, ContextModuleChainID: ContextModuleChainID2 } = await ctx.importLedgerKit(
|
|
4012
|
+
"@ledgerhq/context-module"
|
|
4013
|
+
);
|
|
3438
4014
|
const { SignerSolanaBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-solana");
|
|
3439
|
-
const contextModule = new ContextModuleBuilder2({}).removeDefaultLoaders().build();
|
|
4015
|
+
const contextModule = new ContextModuleBuilder2({}).setChain(ContextModuleChainID2.Solana).removeDefaultLoaders().build();
|
|
3440
4016
|
const sdkSigner = new SignerSolanaBuilder({ dmk, sessionId }).withContextModule(contextModule).build();
|
|
3441
4017
|
const signer = new SignerSol(sdkSigner);
|
|
3442
4018
|
signer.onInteraction = (interaction) => {
|
|
@@ -3452,6 +4028,86 @@ async function _createSolSigner(ctx, sessionId) {
|
|
|
3452
4028
|
return signer;
|
|
3453
4029
|
}
|
|
3454
4030
|
|
|
4031
|
+
// src/connector/chains/zcash.ts
|
|
4032
|
+
import { bytesToHex as bytesToHex2 } from "@onekeyfe/hwk-adapter-core";
|
|
4033
|
+
|
|
4034
|
+
// src/signer/SignerZcash.ts
|
|
4035
|
+
var SignerZcash = class {
|
|
4036
|
+
// eslint-disable-next-line no-useless-constructor, no-empty-function
|
|
4037
|
+
constructor(_sdk) {
|
|
4038
|
+
this._sdk = _sdk;
|
|
4039
|
+
}
|
|
4040
|
+
/** GET_VK: UFVK string (default) or raw 96-byte Orchard FVK. */
|
|
4041
|
+
async getFullViewingKey(derivationPath, options) {
|
|
4042
|
+
const action = this._sdk.getFullViewingKey(derivationPath, options);
|
|
4043
|
+
return deviceActionToPromise(
|
|
4044
|
+
action,
|
|
4045
|
+
this.onInteraction,
|
|
4046
|
+
void 0,
|
|
4047
|
+
this.onRegisterCanceller
|
|
4048
|
+
);
|
|
4049
|
+
}
|
|
4050
|
+
/** GET_SHIELDED_ADDRESS: single-Orchard-receiver UA for a 5-level transparent path. */
|
|
4051
|
+
async getShieldedAddress(derivationPath, options) {
|
|
4052
|
+
const action = this._sdk.getShieldedAddress(derivationPath, options);
|
|
4053
|
+
return deviceActionToPromise(
|
|
4054
|
+
action,
|
|
4055
|
+
this.onInteraction,
|
|
4056
|
+
void 0,
|
|
4057
|
+
this.onRegisterCanceller
|
|
4058
|
+
);
|
|
4059
|
+
}
|
|
4060
|
+
};
|
|
4061
|
+
|
|
4062
|
+
// src/connector/chains/zcash.ts
|
|
4063
|
+
async function zcashGetFullViewingKey(ctx, sessionId, params) {
|
|
4064
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4065
|
+
const path = normalizePath(params.path);
|
|
4066
|
+
const mode = params.mode ?? "ufvk";
|
|
4067
|
+
try {
|
|
4068
|
+
const result = await signer.getFullViewingKey(path, { mode });
|
|
4069
|
+
if (result.mode === "ufvk") {
|
|
4070
|
+
return { path: params.path, mode, ufvk: result.fullViewingKey };
|
|
4071
|
+
}
|
|
4072
|
+
return { path: params.path, mode, orchardFvk: bytesToHex2(result.fullViewingKey) };
|
|
4073
|
+
} catch (err) {
|
|
4074
|
+
ctx.invalidateSession(sessionId);
|
|
4075
|
+
throw ctx.wrapError(err);
|
|
4076
|
+
} finally {
|
|
4077
|
+
ctx.clearCanceller(sessionId);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
async function zcashGetShieldedAddress(ctx, sessionId, params) {
|
|
4081
|
+
const signer = await _createZcashSigner(ctx, sessionId);
|
|
4082
|
+
const path = normalizePath(params.path);
|
|
4083
|
+
try {
|
|
4084
|
+
const result = await signer.getShieldedAddress(path, {
|
|
4085
|
+
checkOnDevice: params.showOnDevice ?? false
|
|
4086
|
+
});
|
|
4087
|
+
return { address: result.address, path: params.path };
|
|
4088
|
+
} catch (err) {
|
|
4089
|
+
ctx.invalidateSession(sessionId);
|
|
4090
|
+
throw ctx.wrapError(err);
|
|
4091
|
+
} finally {
|
|
4092
|
+
ctx.clearCanceller(sessionId);
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
async function _createZcashSigner(ctx, sessionId) {
|
|
4096
|
+
const dmk = await ctx.getOrCreateDmk();
|
|
4097
|
+
const { SignerZcashBuilder } = await ctx.importLedgerKit("@ledgerhq/device-signer-kit-zcash");
|
|
4098
|
+
const sdkSigner = new SignerZcashBuilder({ dmk, sessionId }).build();
|
|
4099
|
+
const signer = new SignerZcash(sdkSigner);
|
|
4100
|
+
signer.onInteraction = (interaction) => {
|
|
4101
|
+
debugLog("[LedgerConnector] zcash.onInteraction:", interaction);
|
|
4102
|
+
ctx.emit("ui-event", {
|
|
4103
|
+
type: collapseSignerInteraction(interaction),
|
|
4104
|
+
payload: { sessionId }
|
|
4105
|
+
});
|
|
4106
|
+
};
|
|
4107
|
+
signer.onRegisterCanceller = (cancel) => ctx.registerCanceller(sessionId, cancel);
|
|
4108
|
+
return signer;
|
|
4109
|
+
}
|
|
4110
|
+
|
|
3455
4111
|
// src/connector/chains/tron.ts
|
|
3456
4112
|
import { HardwareErrorCode as HardwareErrorCode7 } from "@onekeyfe/hwk-adapter-core";
|
|
3457
4113
|
import Trx from "@ledgerhq/hw-app-trx";
|
|
@@ -3472,6 +4128,7 @@ var APP_NAME_MAP = {
|
|
|
3472
4128
|
BTC: "Bitcoin",
|
|
3473
4129
|
SOL: "Solana",
|
|
3474
4130
|
TRX: "Tron",
|
|
4131
|
+
ZEC: "Zcash",
|
|
3475
4132
|
XRP: "XRP",
|
|
3476
4133
|
ADA: "Cardano",
|
|
3477
4134
|
DOT: "Polkadot",
|
|
@@ -3985,7 +4642,7 @@ var DeviceApps = class {
|
|
|
3985
4642
|
seTargetId: v.seTargetId,
|
|
3986
4643
|
mcuTargetId: v.mcuTargetId,
|
|
3987
4644
|
seVersion: v.seVersion,
|
|
3988
|
-
seFlagsHex:
|
|
4645
|
+
seFlagsHex: bytesToHex3(v.seFlags),
|
|
3989
4646
|
mcuSephVersion: v.mcuSephVersion,
|
|
3990
4647
|
mcuBootloaderVersion: v.mcuBootloaderVersion,
|
|
3991
4648
|
hwVersion: v.hwVersion
|
|
@@ -4059,7 +4716,7 @@ var DeviceApps = class {
|
|
|
4059
4716
|
}
|
|
4060
4717
|
}
|
|
4061
4718
|
};
|
|
4062
|
-
function
|
|
4719
|
+
function bytesToHex3(bytes) {
|
|
4063
4720
|
if (!bytes) return "";
|
|
4064
4721
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4065
4722
|
}
|
|
@@ -4102,7 +4759,8 @@ var METHOD_PREFIX_TO_APP_NAME = {
|
|
|
4102
4759
|
evm: "Ethereum",
|
|
4103
4760
|
btc: "Bitcoin",
|
|
4104
4761
|
sol: "Solana",
|
|
4105
|
-
tron: "Tron"
|
|
4762
|
+
tron: "Tron",
|
|
4763
|
+
zcash: "Zcash"
|
|
4106
4764
|
};
|
|
4107
4765
|
var HARDWARE_ERROR_CODE_VALUES = new Set(
|
|
4108
4766
|
Object.values(HardwareErrorCode8).filter((value) => typeof value === "number")
|
|
@@ -4142,6 +4800,8 @@ async function defaultLedgerKitImporter(pkg) {
|
|
|
4142
4800
|
return import("@ledgerhq/device-signer-kit-bitcoin");
|
|
4143
4801
|
case "@ledgerhq/device-signer-kit-solana":
|
|
4144
4802
|
return import("@ledgerhq/device-signer-kit-solana");
|
|
4803
|
+
case "@ledgerhq/device-signer-kit-zcash":
|
|
4804
|
+
return import("@ledgerhq/device-signer-kit-zcash");
|
|
4145
4805
|
case "@ledgerhq/context-module":
|
|
4146
4806
|
return import("@ledgerhq/context-module");
|
|
4147
4807
|
default:
|
|
@@ -4559,6 +5219,11 @@ var LedgerConnectorBase = class {
|
|
|
4559
5219
|
return solSignTransaction(ctx, sessionId, params);
|
|
4560
5220
|
case "solSignMessage":
|
|
4561
5221
|
return solSignMessage(ctx, sessionId, params);
|
|
5222
|
+
// ZCASH
|
|
5223
|
+
case "zcashGetFullViewingKey":
|
|
5224
|
+
return zcashGetFullViewingKey(ctx, sessionId, params);
|
|
5225
|
+
case "zcashGetShieldedAddress":
|
|
5226
|
+
return zcashGetShieldedAddress(ctx, sessionId, params);
|
|
4562
5227
|
// TRON
|
|
4563
5228
|
case "tronGetAddress":
|
|
4564
5229
|
return tronGetAddress(ctx, sessionId, params);
|
|
@@ -4888,7 +5553,7 @@ var LedgerConnectorBase = class {
|
|
|
4888
5553
|
* at every catch site. Falls through unchanged for unknown methods.
|
|
4889
5554
|
*/
|
|
4890
5555
|
_ctxForMethod(method) {
|
|
4891
|
-
const prefix = /^(evm|btc|sol|tron)/.exec(method)?.[1];
|
|
5556
|
+
const prefix = /^(evm|btc|sol|tron|zcash)/.exec(method)?.[1];
|
|
4892
5557
|
const defaultAppName = prefix ? METHOD_PREFIX_TO_APP_NAME[prefix] : void 0;
|
|
4893
5558
|
if (!defaultAppName) return this._ctx;
|
|
4894
5559
|
return {
|
|
@@ -4932,6 +5597,7 @@ export {
|
|
|
4932
5597
|
SignerEth,
|
|
4933
5598
|
SignerManager,
|
|
4934
5599
|
SignerSol,
|
|
5600
|
+
SignerZcash,
|
|
4935
5601
|
debugLog,
|
|
4936
5602
|
deviceActionToPromise,
|
|
4937
5603
|
isDeviceLockedError,
|