@onekeyfe/hwk-keystone-adapter 1.2.3-alpha.8 → 1.2.5-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +88 -222
- package/dist/index.d.ts +88 -222
- package/dist/index.js +547 -795
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +547 -795
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -106,6 +106,9 @@ var TronSignature = class _TronSignature extends RegistryItem2 {
|
|
|
106
106
|
static fromDataItem(dataItem) {
|
|
107
107
|
const map = dataItem.getData();
|
|
108
108
|
const signature = map[2 /* signature */];
|
|
109
|
+
if (!Buffer.isBuffer(signature)) {
|
|
110
|
+
throw new Error("Keystone tron-signature UR is missing its signature bytes");
|
|
111
|
+
}
|
|
109
112
|
const requestIdItem = map[1 /* requestId */];
|
|
110
113
|
const requestId = requestIdItem?.getData();
|
|
111
114
|
return new _TronSignature(signature, requestId);
|
|
@@ -136,14 +139,22 @@ function toSdkUr(ur) {
|
|
|
136
139
|
function fromSdkUr(ur) {
|
|
137
140
|
return { urType: ur.type, urData: ur.cbor.toString("hex") };
|
|
138
141
|
}
|
|
139
|
-
function
|
|
140
|
-
if (hex.length !==
|
|
142
|
+
function splitEvmSignature(hex) {
|
|
143
|
+
if (hex.length < 130 || hex.length % 2 !== 0) {
|
|
141
144
|
throw new Error(
|
|
142
|
-
`Keystone returned a ${hex.length / 2}-byte signature; expected 65 bytes (r|s|v)`
|
|
145
|
+
`Keystone returned a ${hex.length / 2}-byte signature; expected at least 65 bytes (r|s|v)`
|
|
143
146
|
);
|
|
144
147
|
}
|
|
145
148
|
return { r: hex.slice(0, 64), s: hex.slice(64, 128), v: hex.slice(128) };
|
|
146
149
|
}
|
|
150
|
+
function requireSignatureBytes(hex, expectedBytes, chain) {
|
|
151
|
+
if (hex.length !== expectedBytes * 2) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Keystone returned a ${hex.length / 2}-byte ${chain} signature; expected ${expectedBytes} bytes`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return hex;
|
|
157
|
+
}
|
|
147
158
|
function requireBip32MasterFingerprint(value) {
|
|
148
159
|
const fingerprint = parseBip32MasterFingerprint(value);
|
|
149
160
|
if (!fingerprint) {
|
|
@@ -151,12 +162,24 @@ function requireBip32MasterFingerprint(value) {
|
|
|
151
162
|
}
|
|
152
163
|
return fingerprint;
|
|
153
164
|
}
|
|
165
|
+
function toParsedAccount(key) {
|
|
166
|
+
return {
|
|
167
|
+
chain: key.chain,
|
|
168
|
+
path: key.path,
|
|
169
|
+
publicKey: key.publicKey,
|
|
170
|
+
extendedPublicKey: key.extendedPublicKey,
|
|
171
|
+
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
172
|
+
name: key.name
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function relativeHdPath(relativeDerivePath) {
|
|
176
|
+
return `m/${relativeDerivePath.replace(/^m\//i, "")}`;
|
|
177
|
+
}
|
|
154
178
|
var KeystoneUrEngine = class {
|
|
155
179
|
constructor(origin = "OneKey") {
|
|
156
180
|
this.sdk = new KeystoneSDK({ origin });
|
|
157
181
|
}
|
|
158
|
-
// --- Account sync (
|
|
159
|
-
// wallet-initiated KeyDerivation request/response over either channel) ---
|
|
182
|
+
// --- Account sync (device-initiated export or KeyDerivation response) ---
|
|
160
183
|
parseMultiAccounts(ur) {
|
|
161
184
|
const parsed = this.sdk.parseMultiAccounts(toSdkUr(ur));
|
|
162
185
|
return {
|
|
@@ -164,46 +187,15 @@ var KeystoneUrEngine = class {
|
|
|
164
187
|
device: parsed.device,
|
|
165
188
|
deviceId: parsed.deviceId,
|
|
166
189
|
deviceVersion: parsed.deviceVersion,
|
|
167
|
-
accounts: parsed.keys.map(
|
|
168
|
-
(key) => ({
|
|
169
|
-
chain: key.chain,
|
|
170
|
-
path: key.path,
|
|
171
|
-
publicKey: key.publicKey,
|
|
172
|
-
extendedPublicKey: key.extendedPublicKey,
|
|
173
|
-
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
174
|
-
name: key.name
|
|
175
|
-
})
|
|
176
|
-
)
|
|
190
|
+
accounts: parsed.keys.map(toParsedAccount)
|
|
177
191
|
};
|
|
178
192
|
}
|
|
179
193
|
parseHDKey(ur) {
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
chain: key.chain,
|
|
183
|
-
path: key.path,
|
|
184
|
-
publicKey: key.publicKey,
|
|
185
|
-
extendedPublicKey: key.extendedPublicKey,
|
|
186
|
-
xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
|
|
187
|
-
name: key.name
|
|
188
|
-
};
|
|
194
|
+
return toParsedAccount(this.sdk.parseHDKey(toSdkUr(ur)));
|
|
189
195
|
}
|
|
190
196
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* showing. The device replies with a `crypto-multi-accounts` UR — parse it
|
|
194
|
-
* with `parseMultiAccounts`. Used for the implicit "sync this wallet's xfp
|
|
195
|
-
* before the first sign" round trip as well as an explicit account import.
|
|
196
|
-
*
|
|
197
|
-
* `version: V1` is required — verified against real Keystone hardware and
|
|
198
|
-
* `keystone3-firmware`'s `CheckHardwareCallRequestIsLegal` source: an
|
|
199
|
-
* unversioned/V0 request is validated as a legacy Cardano-only request
|
|
200
|
-
* (`m/1852'/1815'/...`) and firmware rejects every other chain's path with
|
|
201
|
-
* `PRS_PARSING_ERROR` (device-shown message: "路径不受支持" / "path not
|
|
202
|
-
* supported"), regardless of the path's shape. V1 is what actually enables
|
|
203
|
-
* the general per-chain path whitelist (includes `m/44'/60'` for ETH, the
|
|
204
|
-
* standard BTC purposes, etc.). The SDK itself defaults to V0 unless a
|
|
205
|
-
* truthy `version` is passed — omitting this silently produces a request
|
|
206
|
-
* every non-Cardano device rejects.
|
|
197
|
+
* Builds a KeyDerivation request. `version: V1` is required: firmware validates V0 (the SDK
|
|
198
|
+
* default) as Cardano-only and rejects other paths with `PRS_PARSING_ERROR`.
|
|
207
199
|
*/
|
|
208
200
|
buildKeyDerivationRequest(input) {
|
|
209
201
|
const ur = this.sdk.generateKeyDerivationCall({
|
|
@@ -217,14 +209,8 @@ var KeystoneUrEngine = class {
|
|
|
217
209
|
return fromSdkUr(ur);
|
|
218
210
|
}
|
|
219
211
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* `crypto-hdkey` for a single-key response some firmware paths use instead.
|
|
223
|
-
* Both are normalized to the same `KeystoneParsedMultiAccounts` shape — a
|
|
224
|
-
* single `crypto-hdkey` becomes a one-entry account list, with its own
|
|
225
|
-
* `origin.sourceFingerprint` promoted to `masterFingerprint` (correct for a
|
|
226
|
-
* key derived directly from the seed, which every request this engine
|
|
227
|
-
* builds asks for).
|
|
212
|
+
* Normalizes `crypto-multi-accounts` and the single-key `crypto-hdkey` some firmware returns. The
|
|
213
|
+
* hdkey source fingerprint is the mfp only because every request here derives from the seed.
|
|
228
214
|
*/
|
|
229
215
|
parseAccountResponse(ur) {
|
|
230
216
|
if (ur.urType === "crypto-hdkey") {
|
|
@@ -255,37 +241,19 @@ var KeystoneUrEngine = class {
|
|
|
255
241
|
}
|
|
256
242
|
parseEthSignature(ur) {
|
|
257
243
|
const signature = this.sdk.eth.parseSignature(toSdkUr(ur));
|
|
258
|
-
return { requestId: signature.requestId, ...
|
|
244
|
+
return { requestId: signature.requestId, ...splitEvmSignature(signature.signature) };
|
|
259
245
|
}
|
|
260
|
-
/**
|
|
261
|
-
* Derive one EVM address offline from an already-synced account xpub —
|
|
262
|
-
* verified against the same `@keystonehq/bc-ur-registry-eth` helper the
|
|
263
|
-
* Keystone-based OneKey air-gap demo uses in production
|
|
264
|
-
* (`generateAddressFromXpub`), so no unverified assumption about what a
|
|
265
|
-
* leaf-path KeyDerivation request would return. `relativeDerivePath` is
|
|
266
|
-
* relative to the xpub's own depth, e.g. `'0/0'` for an account xpub.
|
|
267
|
-
*/
|
|
246
|
+
/** Derives an EVM address offline; `relativeDerivePath` is relative to the xpub, e.g. `'0/0'`. */
|
|
268
247
|
deriveEvmAddressFromXpub(xpub, relativeDerivePath) {
|
|
269
|
-
return generateAddressFromXpub(xpub,
|
|
248
|
+
return generateAddressFromXpub(xpub, relativeHdPath(relativeDerivePath));
|
|
270
249
|
}
|
|
271
250
|
// --- BTC (PSBT transaction signing + plain message signing) ---
|
|
272
251
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* always emits standard mainnet-xpub version bytes (`0488B21E`) regardless
|
|
276
|
-
* of the account's purpose/script type (verified against
|
|
277
|
-
* `@keystonehq/bc-ur-registry`'s `CryptoHDKey.getBip32Key`, which hardcodes
|
|
278
|
-
* that version rather than switching to a SLIP-132 ypub/zpub prefix per
|
|
279
|
-
* script type) — so `hdkey.fromExtendedKey` parses it correctly for every
|
|
280
|
-
* `scriptType` without needing custom version bytes configured.
|
|
281
|
-
*
|
|
282
|
-
* `p2tr` is deliberately not handled: taproot output-key tweaking (BIP-341)
|
|
283
|
-
* needs an elliptic-curve library wired via bitcoinjs-lib's `initEccLib`,
|
|
284
|
-
* which this package doesn't set up yet — every other payment function
|
|
285
|
-
* here needs no such library.
|
|
252
|
+
* `CryptoHDKey` always emits mainnet xpub version bytes (`0488B21E`), so `hdkey` parses it as is.
|
|
253
|
+
* `p2tr` needs an ECC lib for BIP-341 tweaking, not wired here.
|
|
286
254
|
*/
|
|
287
255
|
deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType) {
|
|
288
|
-
const node = HDKey.fromExtendedKey(xpub).derive(
|
|
256
|
+
const node = HDKey.fromExtendedKey(xpub).derive(relativeHdPath(relativeDerivePath));
|
|
289
257
|
if (!node.publicKey) throw new Error("HDKey derivation did not produce a public key");
|
|
290
258
|
return this.deriveBtcAddressFromPublicKey(
|
|
291
259
|
Buffer.from(node.publicKey).toString("hex"),
|
|
@@ -338,7 +306,7 @@ var KeystoneUrEngine = class {
|
|
|
338
306
|
requestId: params.requestId,
|
|
339
307
|
signData: params.messageHex,
|
|
340
308
|
dataType: 1,
|
|
341
|
-
// BtcSignRequest.DataType.message
|
|
309
|
+
// BtcSignRequest.DataType.message; PSBT signing never goes through this path.
|
|
342
310
|
accounts: params.accounts,
|
|
343
311
|
origin: params.origin
|
|
344
312
|
});
|
|
@@ -367,7 +335,10 @@ var KeystoneUrEngine = class {
|
|
|
367
335
|
}
|
|
368
336
|
parseSolSignature(ur) {
|
|
369
337
|
const signature = this.sdk.sol.parseSignature(toSdkUr(ur));
|
|
370
|
-
return {
|
|
338
|
+
return {
|
|
339
|
+
requestId: signature.requestId,
|
|
340
|
+
signature: requireSignatureBytes(signature.signature, 64, "SOL")
|
|
341
|
+
};
|
|
371
342
|
}
|
|
372
343
|
// --- TRON ---
|
|
373
344
|
/**
|
|
@@ -392,37 +363,22 @@ var KeystoneUrEngine = class {
|
|
|
392
363
|
const requestId = signature.getRequestId();
|
|
393
364
|
return {
|
|
394
365
|
requestId: requestId ? uuidStringify(requestId) : void 0,
|
|
395
|
-
signature: signature.getSignature().toString("hex")
|
|
366
|
+
signature: requireSignatureBytes(signature.getSignature().toString("hex"), 65, "TRON")
|
|
396
367
|
};
|
|
397
368
|
}
|
|
398
369
|
/**
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
* derivation (verified against Keystone's own `formatAddress()` in
|
|
402
|
-
* `keystone-sdk`'s TRON chain source) — only the final text encoding
|
|
403
|
-
* differs (base58check with a `0x41` version byte, not checksummed hex).
|
|
404
|
-
* Reusing `generateAddressFromXpub` here means no new hashing dependency:
|
|
405
|
-
* strip its "0x" and re-encode the same 20 bytes.
|
|
370
|
+
* TRON uses the same 20 address bytes as EVM (Keystone's `formatAddress()`), re-encoded as
|
|
371
|
+
* base58check with the `0x41` version byte.
|
|
406
372
|
*/
|
|
407
373
|
deriveTronAddressFromXpub(xpub, relativeDerivePath) {
|
|
408
|
-
const evmStyleHex = generateAddressFromXpub(
|
|
409
|
-
xpub,
|
|
410
|
-
`m/${relativeDerivePath.replace(/^m\//i, "")}`
|
|
411
|
-
);
|
|
374
|
+
const evmStyleHex = generateAddressFromXpub(xpub, relativeHdPath(relativeDerivePath));
|
|
412
375
|
const addressBytes = Buffer.concat([
|
|
413
376
|
Buffer.from([TRON_ADDRESS_PREFIX]),
|
|
414
|
-
Buffer.from(evmStyleHex
|
|
377
|
+
Buffer.from(stripHexPrefix(evmStyleHex), "hex")
|
|
415
378
|
]);
|
|
416
379
|
return bs58check.encode(addressBytes);
|
|
417
380
|
}
|
|
418
|
-
/**
|
|
419
|
-
* Split an account-level xpub back into the BIP-32 fields a host needs to
|
|
420
|
-
* treat it as a real extended key (`BtcPublicKey`). Everything here is
|
|
421
|
-
* carried inside the xpub's own serialization — depth, the PARENT key
|
|
422
|
-
* fingerprint (not the seed's master fingerprint), the chain code and the
|
|
423
|
-
* compressed public key — so this is a pure decode with no device round
|
|
424
|
-
* trip; the xpub itself was already device-verified when it was synced.
|
|
425
|
-
*/
|
|
381
|
+
/** Decodes an xpub's BIP-32 fields; `parentFingerprint` is the parent key's, not the mfp. */
|
|
426
382
|
parseXpubMeta(xpub) {
|
|
427
383
|
const raw = Buffer.from(bs58check.decode(xpub));
|
|
428
384
|
if (raw.length !== 78) {
|
|
@@ -610,8 +566,10 @@ function keystoneCancelQueueKeys(identifiers) {
|
|
|
610
566
|
var KEYSTONE_USB_REATTACH_PROBE_ATTEMPTS = 4;
|
|
611
567
|
var KEYSTONE_USB_REATTACH_PROBE_INTERVAL_MS = 500;
|
|
612
568
|
function resolveHardwareOperationTarget(positionalTargetId, operationId) {
|
|
613
|
-
|
|
614
|
-
|
|
569
|
+
return resolveGenericHardwareOperationTarget(positionalTargetId, operationId, "keystone");
|
|
570
|
+
}
|
|
571
|
+
function keySchema(chain, path) {
|
|
572
|
+
return { path, curve: chain === "sol" ? "ed25519" : "secp256k1" };
|
|
615
573
|
}
|
|
616
574
|
function waitForKeystoneUsbReattachProbe() {
|
|
617
575
|
return new Promise((resolve) => {
|
|
@@ -665,13 +623,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
665
623
|
this._usbConnectTail = Promise.resolve();
|
|
666
624
|
this._unsettledUsbOperations = /* @__PURE__ */ new Map();
|
|
667
625
|
this._usbIdleWaiters = /* @__PURE__ */ new Set();
|
|
626
|
+
this._abandonedUsbConnects = 0;
|
|
668
627
|
this._usbTeardownTail = Promise.resolve();
|
|
669
628
|
this._pendingUsbTeardowns = 0;
|
|
670
629
|
this._handleUsbDisconnect = ({ connectId }) => {
|
|
671
630
|
const record = Array.from(this._devices.values()).find((item) => item.usbSessionId === connectId);
|
|
672
631
|
if (!record) return;
|
|
673
632
|
this._operations.endByConnectionKey(connectId, "disconnect");
|
|
674
|
-
this._operations.endByConnectionKey(record.connectId, "disconnect");
|
|
675
633
|
record.usbSessionId = void 0;
|
|
676
634
|
const info = toDeviceInfo(record);
|
|
677
635
|
if (record.qrSynced) {
|
|
@@ -685,8 +643,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
685
643
|
this.emitter.emit("ui-event", event);
|
|
686
644
|
};
|
|
687
645
|
// ---------------------------------------------------------------------------
|
|
688
|
-
// All-network bundle
|
|
689
|
-
// there's exactly one implementation of each chain's address logic.
|
|
646
|
+
// All-network bundle, dispatched to the per-chain methods below.
|
|
690
647
|
// ---------------------------------------------------------------------------
|
|
691
648
|
this.allNetworkGetAddress = async (connectId, deviceId, params) => {
|
|
692
649
|
const operationTarget = resolveHardwareOperationTarget(connectId, params.operationId);
|
|
@@ -695,11 +652,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
695
652
|
const { operationId } = operationTarget.payload;
|
|
696
653
|
let cancelScope;
|
|
697
654
|
try {
|
|
698
|
-
const prefetched = await this._prefetchAllNetworkAccounts(
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
);
|
|
655
|
+
const prefetched = await this._prefetchAllNetworkAccounts(effectiveConnectId, deviceId, {
|
|
656
|
+
...params,
|
|
657
|
+
operationId
|
|
658
|
+
});
|
|
703
659
|
const { book } = prefetched;
|
|
704
660
|
const itemDeviceId = deviceId || prefetched.walletId || "";
|
|
705
661
|
const scope = this._jobQueue.createCancelScope(
|
|
@@ -755,9 +711,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
755
711
|
}
|
|
756
712
|
});
|
|
757
713
|
},
|
|
758
|
-
//
|
|
759
|
-
//
|
|
760
|
-
// existing "carry on with the next chain" behaviour.
|
|
714
|
+
// Cancel, refusal or a lost session ends the batch; other per-item
|
|
715
|
+
// failures carry on with the next chain.
|
|
761
716
|
shouldAbortBundle: (response, { index }) => {
|
|
762
717
|
const { code } = response.payload ?? {};
|
|
763
718
|
return cancelledIndexes.has(index) || isUserRefusal(code) || isConnectionLost(code);
|
|
@@ -788,7 +743,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
788
743
|
getAvailableTransports() {
|
|
789
744
|
return this._usbConnector ? ["qr", "usb"] : ["qr"];
|
|
790
745
|
}
|
|
791
|
-
/** Pins
|
|
746
|
+
/** Pins calls to 'qr' or 'usb'; any other value clears the pin back to auto routing. */
|
|
792
747
|
switchTransport(type) {
|
|
793
748
|
this._forcedTransport = type === "qr" || type === "usb" ? type : void 0;
|
|
794
749
|
return Promise.resolve();
|
|
@@ -801,6 +756,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
801
756
|
this._uiRegistry.cancel();
|
|
802
757
|
this._jobQueue.clear();
|
|
803
758
|
this._searchDeviceTargets.clear();
|
|
759
|
+
await this._usbConnectTail;
|
|
804
760
|
await this._resetUsbSessions();
|
|
805
761
|
this._usbConnector?.off("device-disconnect", this._handleUsbDisconnect);
|
|
806
762
|
this._usbConnector?.off("ui-event", this._handleUsbUiEvent);
|
|
@@ -812,14 +768,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
812
768
|
// Device table
|
|
813
769
|
// ---------------------------------------------------------------------------
|
|
814
770
|
/**
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
* there is no physical descriptor to enumerate. When a USB connector is
|
|
818
|
-
* configured, its raw scan results are appended as-is: a USB descriptor has
|
|
819
|
-
* no mfp until `connectDevice()` actually opens+claims it (see
|
|
820
|
-
* `KeystoneUsbConnectorBase.searchDevices`), so these entries carry an
|
|
821
|
-
* empty `deviceId` and exist purely so a host can list "plugged in, click
|
|
822
|
-
* to connect" candidates.
|
|
771
|
+
* Returns known wallets plus USB scan results; USB entries have no mfp until `connectDevice()`
|
|
772
|
+
* claims them. Explicit QR discovery returns one virtual target.
|
|
823
773
|
*/
|
|
824
774
|
async searchDevices(options) {
|
|
825
775
|
const searchGeneration = /* @__PURE__ */ Symbol("keystone-device-search");
|
|
@@ -855,18 +805,15 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
855
805
|
if (this._activeSearchGeneration !== searchGeneration) return [];
|
|
856
806
|
const placeholders = usbDevices.map((d) => ({
|
|
857
807
|
vendor: "keystone",
|
|
858
|
-
// Empty, not 'unknown':
|
|
859
|
-
//
|
|
860
|
-
// string would beat the host's own default-name fallback and surface as
|
|
861
|
-
// a device called "unknown".
|
|
808
|
+
// Empty, not 'unknown': all models share one vid/pid, and a placeholder
|
|
809
|
+
// string would beat the host's default-name fallback.
|
|
862
810
|
model: d.model ?? "",
|
|
863
811
|
modelName: d.modelName,
|
|
864
|
-
//
|
|
812
|
+
// Unknown until the device is opened.
|
|
865
813
|
firmwareVersion: "0.0.0",
|
|
866
814
|
deviceId: d.deviceId,
|
|
867
815
|
connectId: d.connectId,
|
|
868
816
|
connectionType: "usb",
|
|
869
|
-
// The USB product name, which IS readable at enumeration time.
|
|
870
817
|
label: d.name,
|
|
871
818
|
serialNumber: d.serialNumber,
|
|
872
819
|
capabilities: d.capabilities ?? { persistentDeviceIdentity: false },
|
|
@@ -886,20 +833,25 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
886
833
|
Array.from(this._devices.values()).map((record) => record.usbSessionId).filter((sessionId) => Boolean(sessionId))
|
|
887
834
|
);
|
|
888
835
|
for (const sessionId of sessionIds) {
|
|
889
|
-
|
|
890
|
-
await this._usbConnector.disconnect(sessionId);
|
|
891
|
-
} catch {
|
|
892
|
-
}
|
|
893
|
-
this._handleUsbDisconnect({ connectId: sessionId });
|
|
836
|
+
await this._retireUsbSession(sessionId);
|
|
894
837
|
}
|
|
895
838
|
});
|
|
896
839
|
}
|
|
840
|
+
/** Best-effort disconnect; the session is retired either way so it cannot be selected again. */
|
|
841
|
+
async _retireUsbSession(sessionId) {
|
|
842
|
+
if (this._usbConnector) {
|
|
843
|
+
try {
|
|
844
|
+
await this._usbConnector.disconnect(sessionId);
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
this._handleUsbDisconnect({ connectId: sessionId });
|
|
849
|
+
}
|
|
897
850
|
async searchDeviceTargets(options) {
|
|
898
851
|
const devices = await this.searchDevices(options);
|
|
899
852
|
return devices.map((device) => ({
|
|
900
853
|
searchTargetId: device.connectId,
|
|
901
|
-
//
|
|
902
|
-
// selectable handle remains owned by the current discovery snapshot.
|
|
854
|
+
// The wallet id is stable only after the USB/QR handshake.
|
|
903
855
|
searchTargetReusePolicy: "current-discovery",
|
|
904
856
|
vendor: device.vendor,
|
|
905
857
|
connectionType: device.connectionType,
|
|
@@ -918,13 +870,23 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
918
870
|
}));
|
|
919
871
|
}
|
|
920
872
|
async connectDevice(searchTargetId) {
|
|
873
|
+
const scope = this._jobQueue.createCancelScope(searchTargetId);
|
|
921
874
|
try {
|
|
922
875
|
let connected;
|
|
923
876
|
let selectedConnectionType;
|
|
924
877
|
const discoveredConnectionType = this._searchDeviceTargets.get(searchTargetId);
|
|
925
878
|
if (discoveredConnectionType === "qr") {
|
|
926
879
|
selectedConnectionType = "qr";
|
|
927
|
-
|
|
880
|
+
const abortColdStart = () => this._jobQueue.cancelActiveAndPending(
|
|
881
|
+
KEYSTONE_COLD_START_JOB_LABEL,
|
|
882
|
+
scope.signal.reason
|
|
883
|
+
);
|
|
884
|
+
scope.signal.addEventListener("abort", abortColdStart, { once: true });
|
|
885
|
+
try {
|
|
886
|
+
connected = await this.importFromQr();
|
|
887
|
+
} finally {
|
|
888
|
+
scope.signal.removeEventListener("abort", abortColdStart);
|
|
889
|
+
}
|
|
928
890
|
} else if (discoveredConnectionType === "usb") {
|
|
929
891
|
selectedConnectionType = "usb";
|
|
930
892
|
if (!this._usbConnector) {
|
|
@@ -933,7 +895,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
933
895
|
"No USB connector configured for this Keystone adapter"
|
|
934
896
|
);
|
|
935
897
|
}
|
|
936
|
-
connected = await this._connectUsb({}, searchTargetId);
|
|
898
|
+
connected = await this._connectUsb({}, searchTargetId, scope.signal);
|
|
937
899
|
} else if (hasHardwareRuntimeIdPrefix(searchTargetId)) {
|
|
938
900
|
return failure(
|
|
939
901
|
HardwareErrorCode.DeviceNotFound,
|
|
@@ -954,9 +916,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
954
916
|
);
|
|
955
917
|
} else {
|
|
956
918
|
selectedConnectionType = "usb";
|
|
957
|
-
connected = await this._connectUsb(target);
|
|
919
|
+
connected = await this._connectUsb(target, void 0, scope.signal);
|
|
958
920
|
}
|
|
959
921
|
}
|
|
922
|
+
_KeystoneAdapter._throwIfAborted(scope.signal);
|
|
960
923
|
if (!connected.success) return connected;
|
|
961
924
|
const record = this._devices.get(connected.payload.deviceId);
|
|
962
925
|
this._operations.endByConnectionKey(connected.payload.connectId, "explicit");
|
|
@@ -974,14 +937,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
974
937
|
return success(operation.operationId);
|
|
975
938
|
} catch (err) {
|
|
976
939
|
return this._errorToFailure(err);
|
|
940
|
+
} finally {
|
|
941
|
+
scope.release();
|
|
977
942
|
}
|
|
978
943
|
}
|
|
979
944
|
/**
|
|
980
|
-
* QR has
|
|
981
|
-
*
|
|
982
|
-
* this closes the connector session and either demotes the record back to
|
|
983
|
-
* QR-only (if it was ever QR-synced) or removes it entirely (pure-USB
|
|
984
|
-
* wallet that was never seen over QR) — see §4.2 of the design doc.
|
|
945
|
+
* QR has nothing to tear down. A USB session is closed, and the record is demoted to QR-only or
|
|
946
|
+
* removed if it was never QR-synced.
|
|
985
947
|
*/
|
|
986
948
|
async releaseOperation(operationId) {
|
|
987
949
|
const operation = this._operations.find(operationId);
|
|
@@ -1005,15 +967,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1005
967
|
return;
|
|
1006
968
|
}
|
|
1007
969
|
const { usbSessionId } = record;
|
|
1008
|
-
await this._runUsbTeardown(
|
|
1009
|
-
if (this._usbConnector) {
|
|
1010
|
-
try {
|
|
1011
|
-
await this._usbConnector.disconnect(usbSessionId);
|
|
1012
|
-
} catch {
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
this._handleUsbDisconnect({ connectId: usbSessionId });
|
|
1016
|
-
});
|
|
970
|
+
await this._runUsbTeardown(() => this._retireUsbSession(usbSessionId));
|
|
1017
971
|
}
|
|
1018
972
|
getDeviceInfo(connectIdOrOperationId, deviceId) {
|
|
1019
973
|
try {
|
|
@@ -1091,8 +1045,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1091
1045
|
this.emitter.off(event, listener);
|
|
1092
1046
|
}
|
|
1093
1047
|
// ---------------------------------------------------------------------------
|
|
1094
|
-
// Explicit account import
|
|
1095
|
-
// signing calls don't each pay for their own cold-sync round trip.
|
|
1048
|
+
// Explicit account import: call before signing to avoid a cold sync per call.
|
|
1096
1049
|
// ---------------------------------------------------------------------------
|
|
1097
1050
|
async importFromQr(options = {}) {
|
|
1098
1051
|
try {
|
|
@@ -1103,7 +1056,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1103
1056
|
responseUr = await this._requestQrScanAndAwaitResponse(displayDevice, signal);
|
|
1104
1057
|
} else {
|
|
1105
1058
|
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1106
|
-
schemas: [
|
|
1059
|
+
schemas: [keySchema("evm", KEYSTONE_WALLET_ID_PATH)],
|
|
1107
1060
|
origin: this._origin
|
|
1108
1061
|
});
|
|
1109
1062
|
responseUr = await this._requestQrDisplayAndAwaitResponse(
|
|
@@ -1124,12 +1077,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1124
1077
|
// ---------------------------------------------------------------------------
|
|
1125
1078
|
// EVM
|
|
1126
1079
|
// ---------------------------------------------------------------------------
|
|
1127
|
-
async evmGetAddress(connectIdArg, deviceIdArg,
|
|
1128
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1080
|
+
async evmGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1081
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1129
1082
|
if (!operationTarget.success) return operationTarget;
|
|
1130
1083
|
const connectId = operationTarget.payload.targetId;
|
|
1131
1084
|
const deviceId = deviceIdArg ?? void 0;
|
|
1132
|
-
const params = paramsArg;
|
|
1133
1085
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "evmGetAddress requires params");
|
|
1134
1086
|
if (!params.path)
|
|
1135
1087
|
return failure(HardwareErrorCode.InvalidParams, "evmGetAddress requires params.path");
|
|
@@ -1140,39 +1092,24 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1140
1092
|
"evmGetAddress requires a full leaf path, e.g. m/44'/60'/0'/0/0"
|
|
1141
1093
|
);
|
|
1142
1094
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
throw createHwkError({
|
|
1156
|
-
code: HardwareErrorCode.MethodNotSupported,
|
|
1157
|
-
message: "Keystone did not return an extended public key for this account path"
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
const address = this.urEngine.deriveEvmAddressFromXpub(
|
|
1161
|
-
account.extendedPublicKey,
|
|
1162
|
-
relativeDerivePath
|
|
1163
|
-
);
|
|
1164
|
-
return success({ address, path: normalizePath(params.path) });
|
|
1165
|
-
});
|
|
1166
|
-
} catch (err) {
|
|
1167
|
-
return this._errorToFailure(err);
|
|
1168
|
-
}
|
|
1095
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1096
|
+
const xpub = await this._fetchAccountXpub(
|
|
1097
|
+
connectId,
|
|
1098
|
+
deviceId,
|
|
1099
|
+
"evm",
|
|
1100
|
+
accountPath,
|
|
1101
|
+
signal,
|
|
1102
|
+
book
|
|
1103
|
+
);
|
|
1104
|
+
const address = this.urEngine.deriveEvmAddressFromXpub(xpub, relativeDerivePath);
|
|
1105
|
+
return success({ address, path: normalizePath(params.path) });
|
|
1106
|
+
});
|
|
1169
1107
|
}
|
|
1170
|
-
async evmSignTransaction(connectIdArg, deviceIdArg,
|
|
1171
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1108
|
+
async evmSignTransaction(connectIdArg, deviceIdArg, params) {
|
|
1109
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1172
1110
|
if (!operationTarget.success) return operationTarget;
|
|
1173
1111
|
const connectId = operationTarget.payload.targetId;
|
|
1174
1112
|
const deviceId = deviceIdArg ?? void 0;
|
|
1175
|
-
const params = paramsArg;
|
|
1176
1113
|
if (!params)
|
|
1177
1114
|
return failure(HardwareErrorCode.InvalidParams, "evmSignTransaction requires params");
|
|
1178
1115
|
if (!params.path) {
|
|
@@ -1186,48 +1123,34 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1186
1123
|
}
|
|
1187
1124
|
const rawTxHex = stripHex(params.serializedTx);
|
|
1188
1125
|
const path = normalizePath(params.path);
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
);
|
|
1212
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1213
|
-
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
1214
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1215
|
-
return success({
|
|
1216
|
-
v: ensure0x(sig.v),
|
|
1217
|
-
r: ensure0x(sig.r),
|
|
1218
|
-
s: ensure0x(sig.s)
|
|
1219
|
-
});
|
|
1220
|
-
});
|
|
1221
|
-
} catch (err) {
|
|
1222
|
-
return this._errorToFailure(err);
|
|
1223
|
-
}
|
|
1126
|
+
const dataType = parseInt(rawTxHex.slice(0, 2), 16) < 192 ? "typedTransaction" : "transaction";
|
|
1127
|
+
return this._signWithWallet({
|
|
1128
|
+
connectId,
|
|
1129
|
+
deviceId,
|
|
1130
|
+
chain: "evm",
|
|
1131
|
+
operationName: "evmSignTransaction",
|
|
1132
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
|
|
1133
|
+
requestId,
|
|
1134
|
+
unsignedTxHex: rawTxHex,
|
|
1135
|
+
dataType,
|
|
1136
|
+
path,
|
|
1137
|
+
xfp,
|
|
1138
|
+
chainId: params.chainId,
|
|
1139
|
+
origin: this._origin
|
|
1140
|
+
}),
|
|
1141
|
+
parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
|
|
1142
|
+
toPayload: (sig) => ({
|
|
1143
|
+
v: ensure0x(sig.v),
|
|
1144
|
+
r: ensure0x(sig.r),
|
|
1145
|
+
s: ensure0x(sig.s)
|
|
1146
|
+
})
|
|
1147
|
+
});
|
|
1224
1148
|
}
|
|
1225
|
-
async evmSignMessage(connectIdArg, deviceIdArg,
|
|
1226
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1149
|
+
async evmSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1150
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1227
1151
|
if (!operationTarget.success) return operationTarget;
|
|
1228
1152
|
const connectId = operationTarget.payload.targetId;
|
|
1229
1153
|
const deviceId = deviceIdArg ?? void 0;
|
|
1230
|
-
const params = paramsArg;
|
|
1231
1154
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "evmSignMessage requires params");
|
|
1232
1155
|
if (!params.path || params.message === void 0) {
|
|
1233
1156
|
return failure(
|
|
@@ -1237,43 +1160,29 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1237
1160
|
}
|
|
1238
1161
|
const messageHex = params.hex ? stripHex(params.message) : Buffer.from(params.message, "utf8").toString("hex");
|
|
1239
1162
|
const path = normalizePath(params.path);
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
true,
|
|
1258
|
-
connectId,
|
|
1259
|
-
signal,
|
|
1260
|
-
"evmSignMessage"
|
|
1261
|
-
);
|
|
1262
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1263
|
-
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
1264
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1265
|
-
return success({ signature: ensure0x(sig.r + sig.s + sig.v) });
|
|
1266
|
-
});
|
|
1267
|
-
} catch (err) {
|
|
1268
|
-
return this._errorToFailure(err);
|
|
1269
|
-
}
|
|
1163
|
+
return this._signWithWallet({
|
|
1164
|
+
connectId,
|
|
1165
|
+
deviceId,
|
|
1166
|
+
chain: "evm",
|
|
1167
|
+
operationName: "evmSignMessage",
|
|
1168
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
|
|
1169
|
+
requestId,
|
|
1170
|
+
unsignedTxHex: messageHex,
|
|
1171
|
+
dataType: "personalMessage",
|
|
1172
|
+
path,
|
|
1173
|
+
xfp,
|
|
1174
|
+
chainId: params.chainId,
|
|
1175
|
+
origin: this._origin
|
|
1176
|
+
}),
|
|
1177
|
+
parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
|
|
1178
|
+
toPayload: (sig) => ({ signature: ensure0x(sig.r + sig.s + sig.v) })
|
|
1179
|
+
});
|
|
1270
1180
|
}
|
|
1271
|
-
async evmSignTypedData(connectIdArg, deviceIdArg,
|
|
1272
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1181
|
+
async evmSignTypedData(connectIdArg, deviceIdArg, params) {
|
|
1182
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1273
1183
|
if (!operationTarget.success) return operationTarget;
|
|
1274
1184
|
const connectId = operationTarget.payload.targetId;
|
|
1275
1185
|
const deviceId = deviceIdArg ?? void 0;
|
|
1276
|
-
const params = paramsArg;
|
|
1277
1186
|
if (!params)
|
|
1278
1187
|
return failure(HardwareErrorCode.InvalidParams, "evmSignTypedData requires params");
|
|
1279
1188
|
if (!params.path) {
|
|
@@ -1290,49 +1199,33 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1290
1199
|
"utf8"
|
|
1291
1200
|
).toString("hex");
|
|
1292
1201
|
const path = normalizePath(params.path);
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
true,
|
|
1311
|
-
connectId,
|
|
1312
|
-
signal,
|
|
1313
|
-
"evmSignTypedData"
|
|
1314
|
-
);
|
|
1315
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1316
|
-
const sig = this.urEngine.parseEthSignature(responseUr);
|
|
1317
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1318
|
-
return success({ signature: ensure0x(sig.r + sig.s + sig.v) });
|
|
1319
|
-
});
|
|
1320
|
-
} catch (err) {
|
|
1321
|
-
return this._errorToFailure(err);
|
|
1322
|
-
}
|
|
1202
|
+
return this._signWithWallet({
|
|
1203
|
+
connectId,
|
|
1204
|
+
deviceId,
|
|
1205
|
+
chain: "evm",
|
|
1206
|
+
operationName: "evmSignTypedData",
|
|
1207
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
|
|
1208
|
+
requestId,
|
|
1209
|
+
unsignedTxHex: signDataHex,
|
|
1210
|
+
dataType: "typedData",
|
|
1211
|
+
path,
|
|
1212
|
+
xfp,
|
|
1213
|
+
chainId: params.chainId,
|
|
1214
|
+
origin: this._origin
|
|
1215
|
+
}),
|
|
1216
|
+
parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
|
|
1217
|
+
toPayload: (sig) => ({ signature: ensure0x(sig.r + sig.s + sig.v) })
|
|
1218
|
+
});
|
|
1323
1219
|
}
|
|
1324
1220
|
// ---------------------------------------------------------------------------
|
|
1325
|
-
// BTC
|
|
1326
|
-
//
|
|
1327
|
-
// phase doesn't wire in yet; structured-field tx signing needs host-side
|
|
1328
|
-
// PSBT construction. Both are real, bounded follow-ups, not silent gaps.
|
|
1221
|
+
// BTC: 44'/49'/84' addresses derive offline from the account xpub; P2TR addresses
|
|
1222
|
+
// and structured-field tx signing are unsupported (the host builds a PSBT instead).
|
|
1329
1223
|
// ---------------------------------------------------------------------------
|
|
1330
|
-
async btcGetAddress(connectIdArg, deviceIdArg,
|
|
1331
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1224
|
+
async btcGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1225
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1332
1226
|
if (!operationTarget.success) return operationTarget;
|
|
1333
1227
|
const connectId = operationTarget.payload.targetId;
|
|
1334
1228
|
const deviceId = deviceIdArg ?? void 0;
|
|
1335
|
-
const params = paramsArg;
|
|
1336
1229
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "btcGetAddress requires params");
|
|
1337
1230
|
if (!params.path)
|
|
1338
1231
|
return failure(HardwareErrorCode.InvalidParams, "btcGetAddress requires params.path");
|
|
@@ -1360,49 +1253,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1360
1253
|
if (!isKeystoneSignableBtcAccountPath(accountPath)) {
|
|
1361
1254
|
return failure(HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1362
1255
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
throw createHwkError({
|
|
1376
|
-
code: HardwareErrorCode.MethodNotSupported,
|
|
1377
|
-
message: "Keystone did not return an extended public key for this account path"
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
const address = this.urEngine.deriveBtcAddressFromXpub(
|
|
1381
|
-
account.extendedPublicKey,
|
|
1382
|
-
relativeDerivePath,
|
|
1383
|
-
scriptType
|
|
1384
|
-
);
|
|
1385
|
-
return success({ address, path: normalizedPath });
|
|
1386
|
-
});
|
|
1387
|
-
} catch (err) {
|
|
1388
|
-
return this._errorToFailure(err);
|
|
1389
|
-
}
|
|
1256
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1257
|
+
const xpub = await this._fetchAccountXpub(
|
|
1258
|
+
connectId,
|
|
1259
|
+
deviceId,
|
|
1260
|
+
"btc",
|
|
1261
|
+
accountPath,
|
|
1262
|
+
signal,
|
|
1263
|
+
book
|
|
1264
|
+
);
|
|
1265
|
+
const address = this.urEngine.deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType);
|
|
1266
|
+
return success({ address, path: normalizedPath });
|
|
1267
|
+
});
|
|
1390
1268
|
}
|
|
1391
1269
|
/**
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1394
|
-
* actually syncs, and it is what a host needs to derive a whole account's
|
|
1395
|
-
* addresses offline. Unlike `btcGetAddress` this has no script-type
|
|
1396
|
-
* restriction: an xpub is script-type agnostic, so `86'` (taproot) works
|
|
1397
|
-
* here even though deriving a taproot ADDRESS still needs an EC library
|
|
1398
|
-
* this package doesn't wire in.
|
|
1270
|
+
* `params.path` must be an account path (`m/84'/0'/0'`), the level Keystone syncs. An xpub is
|
|
1271
|
+
* script-type agnostic, so `86'` works here even though `btcGetAddress` rejects it.
|
|
1399
1272
|
*/
|
|
1400
|
-
async btcGetPublicKey(connectIdArg, deviceIdArg,
|
|
1401
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1273
|
+
async btcGetPublicKey(connectIdArg, deviceIdArg, params, book) {
|
|
1274
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1402
1275
|
if (!operationTarget.success) return operationTarget;
|
|
1403
1276
|
const connectId = operationTarget.payload.targetId;
|
|
1404
1277
|
const deviceId = deviceIdArg ?? void 0;
|
|
1405
|
-
const params = paramsArg;
|
|
1406
1278
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params");
|
|
1407
1279
|
if (!params.path)
|
|
1408
1280
|
return failure(HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params.path");
|
|
@@ -1416,36 +1288,25 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1416
1288
|
if (!isKeystoneSignableBtcAccountPath(accountPath)) {
|
|
1417
1289
|
return failure(HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1418
1290
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
const meta = this.urEngine.parseXpubMeta(account.extendedPublicKey);
|
|
1437
|
-
return success({
|
|
1438
|
-
xpub: account.extendedPublicKey,
|
|
1439
|
-
publicKey: meta.publicKey,
|
|
1440
|
-
chainCode: meta.chainCode,
|
|
1441
|
-
depth: meta.depth,
|
|
1442
|
-
fingerprint: meta.parentFingerprint,
|
|
1443
|
-
path: normalizePath(accountPath)
|
|
1444
|
-
});
|
|
1291
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1292
|
+
const xpub = await this._fetchAccountXpub(
|
|
1293
|
+
connectId,
|
|
1294
|
+
deviceId,
|
|
1295
|
+
"btc",
|
|
1296
|
+
accountPath,
|
|
1297
|
+
signal,
|
|
1298
|
+
book
|
|
1299
|
+
);
|
|
1300
|
+
const meta = this.urEngine.parseXpubMeta(xpub);
|
|
1301
|
+
return success({
|
|
1302
|
+
xpub,
|
|
1303
|
+
publicKey: meta.publicKey,
|
|
1304
|
+
chainCode: meta.chainCode,
|
|
1305
|
+
depth: meta.depth,
|
|
1306
|
+
fingerprint: meta.parentFingerprint,
|
|
1307
|
+
path: normalizePath(accountPath)
|
|
1445
1308
|
});
|
|
1446
|
-
}
|
|
1447
|
-
return this._errorToFailure(err);
|
|
1448
|
-
}
|
|
1309
|
+
});
|
|
1449
1310
|
}
|
|
1450
1311
|
async btcSignTransaction(_connectId, _deviceId, _params) {
|
|
1451
1312
|
return this._unsupported(
|
|
@@ -1453,12 +1314,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1453
1314
|
"use btcSignPsbt \u2014 structured input/output signing needs host-side PSBT construction, not yet wired in"
|
|
1454
1315
|
);
|
|
1455
1316
|
}
|
|
1456
|
-
async btcSignPsbt(connectIdArg, deviceIdArg,
|
|
1457
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1317
|
+
async btcSignPsbt(connectIdArg, deviceIdArg, params) {
|
|
1318
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1458
1319
|
if (!operationTarget.success) return operationTarget;
|
|
1459
1320
|
const connectId = operationTarget.payload.targetId;
|
|
1460
1321
|
const deviceId = deviceIdArg ?? void 0;
|
|
1461
|
-
const params = paramsArg;
|
|
1462
1322
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "btcSignPsbt requires params");
|
|
1463
1323
|
if (!params.psbt)
|
|
1464
1324
|
return failure(HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.psbt");
|
|
@@ -1467,33 +1327,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1467
1327
|
if (!isKeystoneSignableBtcAccountPath(splitAccountPath(params.path).accountPath)) {
|
|
1468
1328
|
return failure(HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1469
1329
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
});
|
|
1487
|
-
} catch (err) {
|
|
1488
|
-
return this._errorToFailure(err);
|
|
1489
|
-
}
|
|
1330
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1331
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
|
|
1332
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1333
|
+
const requestUr = this.urEngine.buildBtcPsbtRequest(stripHex(params.psbt));
|
|
1334
|
+
const responseUr = await this._resolveUr(
|
|
1335
|
+
record,
|
|
1336
|
+
requestUr,
|
|
1337
|
+
true,
|
|
1338
|
+
connectId,
|
|
1339
|
+
signal,
|
|
1340
|
+
"btcSignPsbt"
|
|
1341
|
+
);
|
|
1342
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1343
|
+
const signedPsbt = this.urEngine.parseBtcPsbt(responseUr);
|
|
1344
|
+
return success({ signedPsbt });
|
|
1345
|
+
});
|
|
1490
1346
|
}
|
|
1491
|
-
async btcSignMessage(connectIdArg, deviceIdArg,
|
|
1492
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1347
|
+
async btcSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1348
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1493
1349
|
if (!operationTarget.success) return operationTarget;
|
|
1494
1350
|
const connectId = operationTarget.payload.targetId;
|
|
1495
1351
|
const deviceId = deviceIdArg ?? void 0;
|
|
1496
|
-
const params = paramsArg;
|
|
1497
1352
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "btcSignMessage requires params");
|
|
1498
1353
|
if (!params.path || params.message === void 0) {
|
|
1499
1354
|
return failure(
|
|
@@ -1506,80 +1361,55 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1506
1361
|
if (!isKeystoneSignableBtcAccountPath(splitAccountPath(path).accountPath)) {
|
|
1507
1362
|
return failure(HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
|
|
1508
1363
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
true,
|
|
1524
|
-
connectId,
|
|
1525
|
-
signal,
|
|
1526
|
-
"btcSignMessage"
|
|
1527
|
-
);
|
|
1528
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1529
|
-
const sig = this.urEngine.parseBtcSignature(responseUr);
|
|
1530
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1531
|
-
return success({ signature: sig.signature });
|
|
1532
|
-
});
|
|
1533
|
-
} catch (err) {
|
|
1534
|
-
return this._errorToFailure(err);
|
|
1535
|
-
}
|
|
1364
|
+
return this._signWithWallet({
|
|
1365
|
+
connectId,
|
|
1366
|
+
deviceId,
|
|
1367
|
+
chain: "btc",
|
|
1368
|
+
operationName: "btcSignMessage",
|
|
1369
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildBtcMessageSignRequest({
|
|
1370
|
+
requestId,
|
|
1371
|
+
messageHex,
|
|
1372
|
+
accounts: [{ path, xfp }],
|
|
1373
|
+
origin: this._origin
|
|
1374
|
+
}),
|
|
1375
|
+
parseResponse: (ur) => this.urEngine.parseBtcSignature(ur),
|
|
1376
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1377
|
+
});
|
|
1536
1378
|
}
|
|
1537
1379
|
async btcGetMasterFingerprint(connectIdArg, deviceIdArg, paramsArg) {
|
|
1538
1380
|
const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
|
|
1539
1381
|
if (!operationTarget.success) return operationTarget;
|
|
1540
1382
|
const connectId = operationTarget.payload.targetId;
|
|
1541
1383
|
const deviceId = deviceIdArg ?? void 0;
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
});
|
|
1547
|
-
} catch (err) {
|
|
1548
|
-
return this._errorToFailure(err);
|
|
1549
|
-
}
|
|
1384
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1385
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
|
|
1386
|
+
return success({ masterFingerprint: record.masterFingerprint });
|
|
1387
|
+
});
|
|
1550
1388
|
}
|
|
1551
1389
|
// ---------------------------------------------------------------------------
|
|
1552
1390
|
// SOL
|
|
1553
1391
|
// ---------------------------------------------------------------------------
|
|
1554
|
-
async solGetAddress(connectIdArg, deviceIdArg,
|
|
1555
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1392
|
+
async solGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1393
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1556
1394
|
if (!operationTarget.success) return operationTarget;
|
|
1557
1395
|
const connectId = operationTarget.payload.targetId;
|
|
1558
1396
|
const deviceId = deviceIdArg ?? void 0;
|
|
1559
|
-
const params = paramsArg;
|
|
1560
1397
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "solGetAddress requires params");
|
|
1561
1398
|
if (!params.path)
|
|
1562
1399
|
return failure(HardwareErrorCode.InvalidParams, "solGetAddress requires params.path");
|
|
1563
1400
|
const path = normalizePath(params.path);
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
const address = bs58.encode(Buffer.from(account.publicKey, "hex"));
|
|
1571
|
-
return success({ address, path });
|
|
1572
|
-
});
|
|
1573
|
-
} catch (err) {
|
|
1574
|
-
return this._errorToFailure(err);
|
|
1575
|
-
}
|
|
1401
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1402
|
+
const { account } = await this._fetchAccount(connectId, deviceId, "sol", path, signal, book);
|
|
1403
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1404
|
+
const address = bs58.encode(Buffer.from(account.publicKey, "hex"));
|
|
1405
|
+
return success({ address, path });
|
|
1406
|
+
});
|
|
1576
1407
|
}
|
|
1577
|
-
async solSignTransaction(connectIdArg, deviceIdArg,
|
|
1578
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1408
|
+
async solSignTransaction(connectIdArg, deviceIdArg, params) {
|
|
1409
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1579
1410
|
if (!operationTarget.success) return operationTarget;
|
|
1580
1411
|
const connectId = operationTarget.payload.targetId;
|
|
1581
1412
|
const deviceId = deviceIdArg ?? void 0;
|
|
1582
|
-
const params = paramsArg;
|
|
1583
1413
|
if (!params)
|
|
1584
1414
|
return failure(HardwareErrorCode.InvalidParams, "solSignTransaction requires params");
|
|
1585
1415
|
if (!params.path || !params.serializedTx) {
|
|
@@ -1589,42 +1419,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1589
1419
|
);
|
|
1590
1420
|
}
|
|
1591
1421
|
const path = normalizePath(params.path);
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
true,
|
|
1609
|
-
connectId,
|
|
1610
|
-
signal,
|
|
1611
|
-
"solSignTransaction"
|
|
1612
|
-
);
|
|
1613
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1614
|
-
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1615
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1616
|
-
return success({ signature: sig.signature });
|
|
1617
|
-
});
|
|
1618
|
-
} catch (err) {
|
|
1619
|
-
return this._errorToFailure(err);
|
|
1620
|
-
}
|
|
1422
|
+
return this._signWithWallet({
|
|
1423
|
+
connectId,
|
|
1424
|
+
deviceId,
|
|
1425
|
+
chain: "sol",
|
|
1426
|
+
operationName: "solSignTransaction",
|
|
1427
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildSolSignRequest({
|
|
1428
|
+
requestId,
|
|
1429
|
+
unsignedPayloadHex: stripHex(params.serializedTx),
|
|
1430
|
+
dataType: "transaction",
|
|
1431
|
+
path,
|
|
1432
|
+
xfp,
|
|
1433
|
+
origin: this._origin
|
|
1434
|
+
}),
|
|
1435
|
+
parseResponse: (ur) => this.urEngine.parseSolSignature(ur),
|
|
1436
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1437
|
+
});
|
|
1621
1438
|
}
|
|
1622
|
-
async solSignMessage(connectIdArg, deviceIdArg,
|
|
1623
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1439
|
+
async solSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1440
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1624
1441
|
if (!operationTarget.success) return operationTarget;
|
|
1625
1442
|
const connectId = operationTarget.payload.targetId;
|
|
1626
1443
|
const deviceId = deviceIdArg ?? void 0;
|
|
1627
|
-
const params = paramsArg;
|
|
1628
1444
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "solSignMessage requires params");
|
|
1629
1445
|
if (!params.path || !params.message) {
|
|
1630
1446
|
return failure(
|
|
@@ -1633,50 +1449,32 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1633
1449
|
);
|
|
1634
1450
|
}
|
|
1635
1451
|
const path = normalizePath(params.path);
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
true,
|
|
1653
|
-
connectId,
|
|
1654
|
-
signal,
|
|
1655
|
-
"solSignMessage"
|
|
1656
|
-
);
|
|
1657
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1658
|
-
const sig = this.urEngine.parseSolSignature(responseUr);
|
|
1659
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1660
|
-
return success({ signature: sig.signature });
|
|
1661
|
-
});
|
|
1662
|
-
} catch (err) {
|
|
1663
|
-
return this._errorToFailure(err);
|
|
1664
|
-
}
|
|
1452
|
+
return this._signWithWallet({
|
|
1453
|
+
connectId,
|
|
1454
|
+
deviceId,
|
|
1455
|
+
chain: "sol",
|
|
1456
|
+
operationName: "solSignMessage",
|
|
1457
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildSolSignRequest({
|
|
1458
|
+
requestId,
|
|
1459
|
+
unsignedPayloadHex: stripHex(params.message),
|
|
1460
|
+
dataType: "message",
|
|
1461
|
+
path,
|
|
1462
|
+
xfp,
|
|
1463
|
+
origin: this._origin
|
|
1464
|
+
}),
|
|
1465
|
+
parseResponse: (ur) => this.urEngine.parseSolSignature(ur),
|
|
1466
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1467
|
+
});
|
|
1665
1468
|
}
|
|
1666
1469
|
// ---------------------------------------------------------------------------
|
|
1667
|
-
// TRON
|
|
1668
|
-
//
|
|
1669
|
-
// (tags 5201/5202, firmware >= 2.5.0). The OneKey Pro air-gap encoding under
|
|
1670
|
-
// the same tag is a different layout the firmware rejects. Not keystone-sdk's
|
|
1671
|
-
// bundled `sdk.tron` module either (protobuf-based, unverified response
|
|
1672
|
-
// semantics).
|
|
1470
|
+
// TRON: keystone-sdk-rust layout (tags 5201/5202, firmware >= 2.5.0), not sdk.tron's
|
|
1471
|
+
// protobuf module; OneKey Pro's encoding under the same tag is rejected by the firmware.
|
|
1673
1472
|
// ---------------------------------------------------------------------------
|
|
1674
|
-
async tronGetAddress(connectIdArg, deviceIdArg,
|
|
1675
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1473
|
+
async tronGetAddress(connectIdArg, deviceIdArg, params, book) {
|
|
1474
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1676
1475
|
if (!operationTarget.success) return operationTarget;
|
|
1677
1476
|
const connectId = operationTarget.payload.targetId;
|
|
1678
1477
|
const deviceId = deviceIdArg ?? void 0;
|
|
1679
|
-
const params = paramsArg;
|
|
1680
1478
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "tronGetAddress requires params");
|
|
1681
1479
|
if (!params.path)
|
|
1682
1480
|
return failure(HardwareErrorCode.InvalidParams, "tronGetAddress requires params.path");
|
|
@@ -1687,39 +1485,24 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1687
1485
|
"tronGetAddress requires a full leaf path, e.g. m/44'/195'/0'/0/0"
|
|
1688
1486
|
);
|
|
1689
1487
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
throw createHwkError({
|
|
1703
|
-
code: HardwareErrorCode.MethodNotSupported,
|
|
1704
|
-
message: "Keystone did not return an extended public key for this account path"
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
const address = this.urEngine.deriveTronAddressFromXpub(
|
|
1708
|
-
account.extendedPublicKey,
|
|
1709
|
-
relativeDerivePath
|
|
1710
|
-
);
|
|
1711
|
-
return success({ address, path: normalizePath(params.path) });
|
|
1712
|
-
});
|
|
1713
|
-
} catch (err) {
|
|
1714
|
-
return this._errorToFailure(err);
|
|
1715
|
-
}
|
|
1488
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1489
|
+
const xpub = await this._fetchAccountXpub(
|
|
1490
|
+
connectId,
|
|
1491
|
+
deviceId,
|
|
1492
|
+
"tron",
|
|
1493
|
+
accountPath,
|
|
1494
|
+
signal,
|
|
1495
|
+
book
|
|
1496
|
+
);
|
|
1497
|
+
const address = this.urEngine.deriveTronAddressFromXpub(xpub, relativeDerivePath);
|
|
1498
|
+
return success({ address, path: normalizePath(params.path) });
|
|
1499
|
+
});
|
|
1716
1500
|
}
|
|
1717
|
-
async tronSignTransaction(connectIdArg, deviceIdArg,
|
|
1718
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1501
|
+
async tronSignTransaction(connectIdArg, deviceIdArg, params) {
|
|
1502
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1719
1503
|
if (!operationTarget.success) return operationTarget;
|
|
1720
1504
|
const connectId = operationTarget.payload.targetId;
|
|
1721
1505
|
const deviceId = deviceIdArg ?? void 0;
|
|
1722
|
-
const params = paramsArg;
|
|
1723
1506
|
if (!params)
|
|
1724
1507
|
return failure(HardwareErrorCode.InvalidParams, "tronSignTransaction requires params");
|
|
1725
1508
|
if (!params.path) {
|
|
@@ -1733,41 +1516,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1733
1516
|
}
|
|
1734
1517
|
const rawTxHex = stripHex(params.rawTxHex);
|
|
1735
1518
|
const path = normalizePath(params.path);
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
connectId,
|
|
1753
|
-
signal,
|
|
1754
|
-
"tronSignTransaction"
|
|
1755
|
-
);
|
|
1756
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1757
|
-
const sig = this.urEngine.parseTronSignature(responseUr);
|
|
1758
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1759
|
-
return success({ signature: sig.signature });
|
|
1760
|
-
});
|
|
1761
|
-
} catch (err) {
|
|
1762
|
-
return this._errorToFailure(err);
|
|
1763
|
-
}
|
|
1519
|
+
return this._signWithWallet({
|
|
1520
|
+
connectId,
|
|
1521
|
+
deviceId,
|
|
1522
|
+
chain: "tron",
|
|
1523
|
+
operationName: "tronSignTransaction",
|
|
1524
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildTronSignRequest({
|
|
1525
|
+
requestId,
|
|
1526
|
+
rawTxHex,
|
|
1527
|
+
path,
|
|
1528
|
+
xfp,
|
|
1529
|
+
origin: this._origin
|
|
1530
|
+
}),
|
|
1531
|
+
parseResponse: (ur) => this.urEngine.parseTronSignature(ur),
|
|
1532
|
+
// Keystone signs rawTxHex as given and returns only the signature, so no serializedTx.
|
|
1533
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1534
|
+
});
|
|
1764
1535
|
}
|
|
1765
|
-
async tronSignMessage(connectIdArg, deviceIdArg,
|
|
1766
|
-
const operationTarget = resolveHardwareOperationTarget(connectIdArg,
|
|
1536
|
+
async tronSignMessage(connectIdArg, deviceIdArg, params) {
|
|
1537
|
+
const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
|
|
1767
1538
|
if (!operationTarget.success) return operationTarget;
|
|
1768
1539
|
const connectId = operationTarget.payload.targetId;
|
|
1769
1540
|
const deviceId = deviceIdArg ?? void 0;
|
|
1770
|
-
const params = paramsArg;
|
|
1771
1541
|
if (!params) return failure(HardwareErrorCode.InvalidParams, "tronSignMessage requires params");
|
|
1772
1542
|
if (!params.path) {
|
|
1773
1543
|
return failure(HardwareErrorCode.InvalidParams, "tronSignMessage requires params.path");
|
|
@@ -1783,41 +1553,75 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1783
1553
|
}
|
|
1784
1554
|
const messageHex = stripHex(params.messageHex);
|
|
1785
1555
|
const path = normalizePath(params.path);
|
|
1556
|
+
return this._signWithWallet({
|
|
1557
|
+
connectId,
|
|
1558
|
+
deviceId,
|
|
1559
|
+
chain: "tron",
|
|
1560
|
+
operationName: "tronSignMessage",
|
|
1561
|
+
buildRequest: (requestId, xfp) => this.urEngine.buildTronSignRequest({
|
|
1562
|
+
requestId,
|
|
1563
|
+
rawTxHex: messageHex,
|
|
1564
|
+
path,
|
|
1565
|
+
xfp,
|
|
1566
|
+
signType: 2 /* PersonalMessage */,
|
|
1567
|
+
origin: this._origin
|
|
1568
|
+
}),
|
|
1569
|
+
parseResponse: (ur) => this.urEngine.parseTronSignature(ur),
|
|
1570
|
+
toPayload: (sig) => ({ signature: sig.signature })
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
// ---------------------------------------------------------------------------
|
|
1574
|
+
// Internals
|
|
1575
|
+
// ---------------------------------------------------------------------------
|
|
1576
|
+
async _runJob(connectId, deviceId, job) {
|
|
1786
1577
|
try {
|
|
1787
|
-
return await this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId),
|
|
1788
|
-
const { record } = await this._ensureWalletKnown(connectId, deviceId, "tron", signal);
|
|
1789
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1790
|
-
const requestId = uuidv4();
|
|
1791
|
-
const requestUr = this.urEngine.buildTronSignRequest({
|
|
1792
|
-
requestId,
|
|
1793
|
-
rawTxHex: messageHex,
|
|
1794
|
-
path,
|
|
1795
|
-
xfp: record.masterFingerprint,
|
|
1796
|
-
signType: 2 /* PersonalMessage */,
|
|
1797
|
-
origin: this._origin
|
|
1798
|
-
});
|
|
1799
|
-
const responseUr = await this._resolveUr(
|
|
1800
|
-
record,
|
|
1801
|
-
requestUr,
|
|
1802
|
-
true,
|
|
1803
|
-
connectId,
|
|
1804
|
-
signal,
|
|
1805
|
-
"tronSignMessage"
|
|
1806
|
-
);
|
|
1807
|
-
_KeystoneAdapter._throwIfAborted(signal);
|
|
1808
|
-
const sig = this.urEngine.parseTronSignature(responseUr);
|
|
1809
|
-
_KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
|
|
1810
|
-
return success({ signature: sig.signature });
|
|
1811
|
-
});
|
|
1578
|
+
return await this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId), job);
|
|
1812
1579
|
} catch (err) {
|
|
1813
1580
|
return this._errorToFailure(err);
|
|
1814
1581
|
}
|
|
1815
1582
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1583
|
+
async _fetchAccountXpub(connectId, deviceId, hwkChain, accountPath, signal, book) {
|
|
1584
|
+
const { account } = await this._fetchAccount(
|
|
1585
|
+
connectId,
|
|
1586
|
+
deviceId,
|
|
1587
|
+
hwkChain,
|
|
1588
|
+
accountPath,
|
|
1589
|
+
signal,
|
|
1590
|
+
book
|
|
1591
|
+
);
|
|
1592
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1593
|
+
if (!account.extendedPublicKey) {
|
|
1594
|
+
throw createHwkError({
|
|
1595
|
+
code: HardwareErrorCode.MethodNotSupported,
|
|
1596
|
+
message: "Keystone did not return an extended public key for this account path"
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
return account.extendedPublicKey;
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Signing needs only the wallet's mfp: the device re-derives the key from path+xfp, so no
|
|
1603
|
+
* per-path account lookup happens here.
|
|
1604
|
+
*/
|
|
1605
|
+
_signWithWallet(args) {
|
|
1606
|
+
const { connectId, deviceId } = args;
|
|
1607
|
+
return this._runJob(connectId, deviceId, async (signal) => {
|
|
1608
|
+
const { record } = await this._ensureWalletKnown(connectId, deviceId, args.chain, signal);
|
|
1609
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1610
|
+
const requestId = uuidv4();
|
|
1611
|
+
const requestUr = args.buildRequest(requestId, record.masterFingerprint);
|
|
1612
|
+
const responseUr = await this._resolveUr(
|
|
1613
|
+
record,
|
|
1614
|
+
requestUr,
|
|
1615
|
+
true,
|
|
1616
|
+
connectId,
|
|
1617
|
+
signal,
|
|
1618
|
+
args.operationName
|
|
1619
|
+
);
|
|
1620
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1621
|
+
const signature = args.parseResponse(responseUr);
|
|
1622
|
+
_KeystoneAdapter._assertRequestIdMatches(requestId, signature.requestId);
|
|
1623
|
+
return success(args.toPayload(signature));
|
|
1624
|
+
});
|
|
1821
1625
|
}
|
|
1822
1626
|
_allNetworkSyncSchema(item) {
|
|
1823
1627
|
if (!isAllNetworkMethodName(String(item.methodName)) || !item.path) return void 0;
|
|
@@ -1840,16 +1644,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1840
1644
|
}
|
|
1841
1645
|
}
|
|
1842
1646
|
/**
|
|
1843
|
-
* QR
|
|
1844
|
-
*
|
|
1845
|
-
* chains or derivation paths the host bundled. USB keeps its proven
|
|
1846
|
-
* one-path-at-a-time export flow because the device limits that channel.
|
|
1647
|
+
* QR answers the whole bundle with one request QR and one scan. USB exports one path per
|
|
1648
|
+
* request because the device limits that channel.
|
|
1847
1649
|
*/
|
|
1848
1650
|
async _prefetchAllNetworkAccounts(connectId, deviceId, params) {
|
|
1849
1651
|
return this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId), async (signal) => {
|
|
1850
1652
|
const target = this._resolveTarget(connectId, deviceId);
|
|
1851
|
-
|
|
1852
|
-
let record = targetRecord;
|
|
1653
|
+
let { record } = target;
|
|
1853
1654
|
const requestedByKey = /* @__PURE__ */ new Map();
|
|
1854
1655
|
for (const item of params.bundle) {
|
|
1855
1656
|
const schema = this._allNetworkSyncSchema(item);
|
|
@@ -1859,11 +1660,23 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1859
1660
|
if (!requestedByKey.size) {
|
|
1860
1661
|
return { book };
|
|
1861
1662
|
}
|
|
1663
|
+
const requestedSchemas = Array.from(requestedByKey.values());
|
|
1664
|
+
const assertAllBooked = () => {
|
|
1665
|
+
for (const schema of requestedSchemas) {
|
|
1666
|
+
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1667
|
+
throw createHwkError({
|
|
1668
|
+
code: HardwareErrorCode.DeviceMismatch,
|
|
1669
|
+
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1862
1674
|
const operation = isHardwareOperationId2(params.operationId) ? this._operations.find(params.operationId) : void 0;
|
|
1863
1675
|
const operationRoute = operation ? this._operationRoutes.get(operation.connectId) : void 0;
|
|
1864
1676
|
const routedToQr = operationRoute?.operationId === operation?.operationId && operationRoute?.connectionType === "qr";
|
|
1865
1677
|
if (this._forcedTransport !== "qr" && !routedToQr && !record?.usbSessionId && this._usbConnector && target.expectedWalletId) {
|
|
1866
1678
|
record = await this._tryUsbAttach(
|
|
1679
|
+
signal,
|
|
1867
1680
|
target.expectedWalletId,
|
|
1868
1681
|
record?.masterFingerprint ?? target.expectedMasterFingerprint
|
|
1869
1682
|
) ?? record;
|
|
@@ -1879,8 +1692,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1879
1692
|
});
|
|
1880
1693
|
}
|
|
1881
1694
|
};
|
|
1882
|
-
const
|
|
1883
|
-
for (const schema of missingSchemas2) {
|
|
1695
|
+
for (const schema of requestedSchemas) {
|
|
1884
1696
|
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1885
1697
|
assertBundleSession();
|
|
1886
1698
|
const synced = await this._fetchAccount(
|
|
@@ -1889,20 +1701,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1889
1701
|
schema.hwkChain,
|
|
1890
1702
|
schema.path,
|
|
1891
1703
|
signal,
|
|
1892
|
-
|
|
1704
|
+
book
|
|
1893
1705
|
);
|
|
1894
1706
|
record = synced.record;
|
|
1895
1707
|
assertBundleSession();
|
|
1896
1708
|
}
|
|
1897
1709
|
}
|
|
1898
|
-
|
|
1899
|
-
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1900
|
-
throw createHwkError({
|
|
1901
|
-
code: HardwareErrorCode.DeviceMismatch,
|
|
1902
|
-
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1903
|
-
});
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1710
|
+
assertAllBooked();
|
|
1906
1711
|
return { book, walletId: record.walletId };
|
|
1907
1712
|
}
|
|
1908
1713
|
if (this._forcedTransport === "usb") {
|
|
@@ -1911,16 +1716,14 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1911
1716
|
message: "USB channel is not connected for this Keystone wallet"
|
|
1912
1717
|
});
|
|
1913
1718
|
}
|
|
1914
|
-
const missingSchemas = Array.from(requestedByKey.values());
|
|
1915
1719
|
const schemas = [
|
|
1916
1720
|
{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH },
|
|
1917
|
-
...
|
|
1721
|
+
...requestedSchemas.filter(
|
|
1722
|
+
(schema) => normalizePath(schema.path) !== KEYSTONE_WALLET_ID_PATH
|
|
1723
|
+
)
|
|
1918
1724
|
];
|
|
1919
1725
|
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
1920
|
-
schemas: schemas.map((schema) => (
|
|
1921
|
-
path: schema.path,
|
|
1922
|
-
curve: schema.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
1923
|
-
})),
|
|
1726
|
+
schemas: schemas.map((schema) => keySchema(schema.hwkChain, schema.path)),
|
|
1924
1727
|
origin: this._origin
|
|
1925
1728
|
});
|
|
1926
1729
|
const responseUr = await this._requestQrDisplayAndAwaitResponse(
|
|
@@ -1939,20 +1742,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1939
1742
|
const normalizedPath = normalizePath(account.path);
|
|
1940
1743
|
const hwkChain = requestedChainByPath.get(normalizedPath) ?? inferHwkChainFromPath(normalizedPath);
|
|
1941
1744
|
if (hwkChain) {
|
|
1942
|
-
book.set(accountKey(hwkChain, normalizedPath), {
|
|
1943
|
-
...account,
|
|
1944
|
-
hwkChain
|
|
1945
|
-
});
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
for (const schema of missingSchemas) {
|
|
1949
|
-
if (!book.has(accountKey(schema.hwkChain, schema.path))) {
|
|
1950
|
-
throw createHwkError({
|
|
1951
|
-
code: HardwareErrorCode.DeviceMismatch,
|
|
1952
|
-
message: `Keystone did not return the requested derivation path (${schema.path})`
|
|
1953
|
-
});
|
|
1745
|
+
book.set(accountKey(hwkChain, normalizedPath), { ...account, hwkChain });
|
|
1954
1746
|
}
|
|
1955
1747
|
}
|
|
1748
|
+
assertAllBooked();
|
|
1956
1749
|
return { book, walletId: record.walletId };
|
|
1957
1750
|
});
|
|
1958
1751
|
}
|
|
@@ -1961,15 +1754,12 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1961
1754
|
* fingerprint is protocol metadata and is never accepted as a lookup key.
|
|
1962
1755
|
*/
|
|
1963
1756
|
_resolveTarget(connectId, deviceId) {
|
|
1964
|
-
|
|
1965
|
-
if (isHardwareOperationId2(connectId)) {
|
|
1966
|
-
resolvedConnectId = this._operations.resolve(connectId).connectId;
|
|
1967
|
-
}
|
|
1757
|
+
const resolvedConnectId = isHardwareOperationId2(connectId) ? this._operations.resolve(connectId).connectId : connectId;
|
|
1968
1758
|
const identifiers = [deviceId, resolvedConnectId].filter(
|
|
1969
1759
|
(identifier) => Boolean(identifier)
|
|
1970
1760
|
);
|
|
1971
1761
|
if (!identifiers.length) return {};
|
|
1972
|
-
const walletIds = identifiers.map((identifier) =>
|
|
1762
|
+
const walletIds = identifiers.map((identifier) => keystoneWalletIdFromIdentifier(identifier)).filter((walletId) => Boolean(walletId));
|
|
1973
1763
|
if (!walletIds.length) {
|
|
1974
1764
|
throw createHwkError({
|
|
1975
1765
|
code: HardwareErrorCode.DeviceNotFound,
|
|
@@ -1983,7 +1773,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
1983
1773
|
message: "Keystone connectId and deviceId identify different wallets"
|
|
1984
1774
|
});
|
|
1985
1775
|
}
|
|
1986
|
-
if (identifiers.some((identifier) => !
|
|
1776
|
+
if (identifiers.some((identifier) => !keystoneWalletIdFromIdentifier(identifier))) {
|
|
1987
1777
|
throw createHwkError({
|
|
1988
1778
|
code: HardwareErrorCode.DeviceMismatch,
|
|
1989
1779
|
message: "Keystone connectId and deviceId are inconsistent"
|
|
@@ -2013,13 +1803,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2013
1803
|
return walletId;
|
|
2014
1804
|
}
|
|
2015
1805
|
/**
|
|
2016
|
-
* Folds a parsed account
|
|
2017
|
-
*
|
|
2018
|
-
* `_resolveUr` routes a KeyDerivation sync over USB when the target record
|
|
2019
|
-
* already has a live session, so this must NOT unconditionally mark
|
|
2020
|
-
* `qrSynced`, or a USB-only wallet would wrongly survive a later USB
|
|
2021
|
-
* disconnect as a "QR-synced, demote to QR-only" entry instead of being
|
|
2022
|
-
* dropped outright (see `releaseOperation`).
|
|
1806
|
+
* Folds a parsed account response into the device table. Only non-USB round trips mark
|
|
1807
|
+
* `qrSynced`, so a USB-only wallet is dropped on disconnect.
|
|
2023
1808
|
*/
|
|
2024
1809
|
_upsertDeviceRecord(parsed, options) {
|
|
2025
1810
|
const walletId = deriveKeystoneWalletId(parsed.accounts);
|
|
@@ -2045,34 +1830,38 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2045
1830
|
return record;
|
|
2046
1831
|
}
|
|
2047
1832
|
/**
|
|
2048
|
-
* Opens
|
|
2049
|
-
*
|
|
2050
|
-
* one fixed account-level xpub and derives the stable wallet id. A
|
|
2051
|
-
* QR-synced entry becomes
|
|
2052
|
-
* `{qr, usb}`-capable in place (one `device-changed`, not a second
|
|
2053
|
-
* `device-connect`); a wallet never seen before becomes a new USB-only
|
|
2054
|
-
* entry. See §4.2 of the design doc.
|
|
1833
|
+
* Opens a permitted USB Keystone, reads its mfp, and derives the wallet id from the identity
|
|
1834
|
+
* xpub. A known wallet gains USB in place (`device-changed`); an unseen one becomes USB-only.
|
|
2055
1835
|
*/
|
|
2056
|
-
async _connectUsb(expected, searchTargetId) {
|
|
2057
|
-
if (this._pendingUsbTeardowns > 0) {
|
|
1836
|
+
async _connectUsb(expected, searchTargetId, signal) {
|
|
1837
|
+
if (this._pendingUsbTeardowns > 0 || this._abandonedUsbConnects > 0) {
|
|
2058
1838
|
return failure(
|
|
2059
1839
|
HardwareErrorCode.DeviceBusyInternal,
|
|
2060
1840
|
"Keystone USB session is being released"
|
|
2061
1841
|
);
|
|
2062
1842
|
}
|
|
2063
|
-
|
|
2064
|
-
let
|
|
2065
|
-
|
|
2066
|
-
|
|
1843
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1844
|
+
let abandoned = false;
|
|
1845
|
+
const onAbort = () => {
|
|
1846
|
+
abandoned = true;
|
|
1847
|
+
this._abandonedUsbConnects += 1;
|
|
1848
|
+
};
|
|
1849
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1850
|
+
const pending = this._usbConnectTail.then(async () => {
|
|
1851
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1852
|
+
return this._connectUsbExclusive(expected, searchTargetId, signal);
|
|
2067
1853
|
});
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
|
|
1854
|
+
const settled = pending.finally(() => {
|
|
1855
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1856
|
+
if (abandoned) this._abandonedUsbConnects -= 1;
|
|
1857
|
+
});
|
|
1858
|
+
this._usbConnectTail = settled.then(
|
|
1859
|
+
() => void 0,
|
|
1860
|
+
() => void 0
|
|
1861
|
+
);
|
|
1862
|
+
return signal ? _KeystoneAdapter._abortable(signal, settled) : settled;
|
|
2074
1863
|
}
|
|
2075
|
-
async _connectUsbExclusive(expected, searchTargetId) {
|
|
1864
|
+
async _connectUsbExclusive(expected, searchTargetId, signal) {
|
|
2076
1865
|
if (!this._usbConnector) {
|
|
2077
1866
|
return failure(
|
|
2078
1867
|
HardwareErrorCode.TransportNotAvailable,
|
|
@@ -2093,6 +1882,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2093
1882
|
const connector = this._usbConnector;
|
|
2094
1883
|
const session = await (connector.connectTarget ? connector.connectTarget(connectTarget) : connector.connect(searchTargetId ?? expected.expectedMasterFingerprint));
|
|
2095
1884
|
sessionId = session.sessionId;
|
|
1885
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2096
1886
|
const raw = session.deviceInfo.raw;
|
|
2097
1887
|
const mfpValue = parseBip32MasterFingerprint2(raw?.masterFingerprint);
|
|
2098
1888
|
if (!mfpValue) {
|
|
@@ -2109,7 +1899,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2109
1899
|
});
|
|
2110
1900
|
}
|
|
2111
1901
|
const identityRequest = this.urEngine.buildKeyDerivationRequest({
|
|
2112
|
-
schemas: [
|
|
1902
|
+
schemas: [keySchema("evm", KEYSTONE_WALLET_ID_PATH)],
|
|
2113
1903
|
origin: this._origin
|
|
2114
1904
|
});
|
|
2115
1905
|
const identityResult = await this._callUsbConnector(
|
|
@@ -2117,6 +1907,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2117
1907
|
"resolveUr",
|
|
2118
1908
|
identityRequest
|
|
2119
1909
|
);
|
|
1910
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2120
1911
|
if (!identityResult.success) {
|
|
2121
1912
|
throw rehydrateConnectorError(identityResult.error);
|
|
2122
1913
|
}
|
|
@@ -2127,21 +1918,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2127
1918
|
});
|
|
2128
1919
|
const walletId = deriveKeystoneWalletId(parsed.accounts);
|
|
2129
1920
|
const previousUsbSessionId = this._devices.get(walletId)?.usbSessionId;
|
|
2130
|
-
const record = this._upsertDeviceRecord(parsed, {
|
|
2131
|
-
viaUsb: true,
|
|
2132
|
-
usbSessionId: session.sessionId
|
|
2133
|
-
});
|
|
2134
1921
|
if (previousUsbSessionId && previousUsbSessionId !== session.sessionId) {
|
|
2135
1922
|
try {
|
|
2136
1923
|
await this._usbConnector.disconnect(previousUsbSessionId);
|
|
2137
1924
|
} catch {
|
|
2138
1925
|
}
|
|
2139
1926
|
}
|
|
1927
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1928
|
+
const record = this._upsertDeviceRecord(parsed, {
|
|
1929
|
+
viaUsb: true,
|
|
1930
|
+
usbSessionId: session.sessionId
|
|
1931
|
+
});
|
|
1932
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2140
1933
|
record.hadUsbSession = true;
|
|
2141
1934
|
record.model = session.deviceInfo.modelName ?? session.deviceInfo.model ?? record.model;
|
|
2142
1935
|
record.deviceVersion = session.deviceInfo.firmwareVersion ?? record.deviceVersion;
|
|
2143
|
-
|
|
2144
|
-
return success(info);
|
|
1936
|
+
return success(toDeviceInfo(record));
|
|
2145
1937
|
} catch (err) {
|
|
2146
1938
|
if (sessionId) {
|
|
2147
1939
|
try {
|
|
@@ -2153,13 +1945,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2153
1945
|
}
|
|
2154
1946
|
}
|
|
2155
1947
|
/**
|
|
2156
|
-
*
|
|
2157
|
-
*
|
|
2158
|
-
* used both after an adapter restart and after a QR-only period, so plugging
|
|
2159
|
-
* USB back in affects the next business call without changing an in-flight
|
|
2160
|
-
* QR operation.
|
|
1948
|
+
* Probes already-authorized USB devices without a permission picker, then attaches and verifies
|
|
1949
|
+
* the requested wallet if present.
|
|
2161
1950
|
*/
|
|
2162
|
-
async _tryUsbAttach(expectedWalletId, expectedMasterFingerprint, options) {
|
|
1951
|
+
async _tryUsbAttach(signal, expectedWalletId, expectedMasterFingerprint, options) {
|
|
1952
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2163
1953
|
if (!expectedWalletId || this._forcedTransport === "qr" || !this._usbConnector) {
|
|
2164
1954
|
return void 0;
|
|
2165
1955
|
}
|
|
@@ -2167,17 +1957,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2167
1957
|
let availableDevices = [];
|
|
2168
1958
|
let lastSearchError;
|
|
2169
1959
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1960
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2170
1961
|
try {
|
|
2171
|
-
availableDevices = await
|
|
1962
|
+
availableDevices = await _KeystoneAdapter._abortable(
|
|
1963
|
+
signal,
|
|
1964
|
+
this._usbConnector.searchDevices({ purpose: "availability" })
|
|
1965
|
+
);
|
|
2172
1966
|
lastSearchError = void 0;
|
|
2173
1967
|
} catch (error) {
|
|
2174
1968
|
lastSearchError = error;
|
|
2175
1969
|
}
|
|
1970
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
2176
1971
|
if (availableDevices.length) {
|
|
2177
1972
|
break;
|
|
2178
1973
|
}
|
|
2179
1974
|
if (attempt < maxAttempts) {
|
|
2180
|
-
await waitForKeystoneUsbReattachProbe();
|
|
1975
|
+
await _KeystoneAdapter._abortable(signal, waitForKeystoneUsbReattachProbe());
|
|
2181
1976
|
}
|
|
2182
1977
|
}
|
|
2183
1978
|
if (!availableDevices.length) {
|
|
@@ -2192,8 +1987,16 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2192
1987
|
for (const device of candidates) {
|
|
2193
1988
|
attached = await this._connectUsb(
|
|
2194
1989
|
{ expectedWalletId, expectedMasterFingerprint },
|
|
2195
|
-
device?.connectId
|
|
1990
|
+
device?.connectId,
|
|
1991
|
+
signal
|
|
2196
1992
|
);
|
|
1993
|
+
_KeystoneAdapter._throwIfAborted(signal);
|
|
1994
|
+
if (!attached.success && attached.payload.code === HardwareErrorCode.DeviceBusyInternal) {
|
|
1995
|
+
throw createHwkError({
|
|
1996
|
+
...attached.payload,
|
|
1997
|
+
message: attached.payload.error
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2197
2000
|
if (attached.success) {
|
|
2198
2001
|
record = this._devices.get(expectedWalletId);
|
|
2199
2002
|
if (record) break;
|
|
@@ -2202,14 +2005,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2202
2005
|
return record;
|
|
2203
2006
|
}
|
|
2204
2007
|
/**
|
|
2205
|
-
*
|
|
2206
|
-
*
|
|
2207
|
-
* device row for the target wallet. USB is the preferred channel: a known
|
|
2208
|
-
* wallet record with no live session gets ONE best-effort re-attach here,
|
|
2209
|
-
* and anything that goes wrong just leaves the call on QR. A
|
|
2210
|
-
* `switchTransport('qr')` pin forces QR even for a USB-attached wallet;
|
|
2211
|
-
* `switchTransport('usb')` on a wallet with no live USB session fails
|
|
2212
|
-
* closed rather than silently falling back to QR.
|
|
2008
|
+
* Decides QR vs USB for one UR round trip. A pinned 'usb' with no live session fails closed;
|
|
2009
|
+
* otherwise a failed USB reattach leaves the call on QR.
|
|
2213
2010
|
*/
|
|
2214
2011
|
async _resolveUr(record, requestUr, animated, operationId, signal, operationName) {
|
|
2215
2012
|
const releaseOperationRetention = isHardwareOperationId2(operationId) ? this._operations.retain(operationId) : void 0;
|
|
@@ -2232,17 +2029,15 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2232
2029
|
const operation = this._operations.resolve(operationId);
|
|
2233
2030
|
const route = this._operationRoutes.get(operation.connectId);
|
|
2234
2031
|
if (!route || route.operationId !== operationId) {
|
|
2235
|
-
this.
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
params: { operationId, reason: "disconnect" }
|
|
2240
|
-
});
|
|
2032
|
+
throw this._endDisconnectedOperation(
|
|
2033
|
+
operationId,
|
|
2034
|
+
"Keystone hardware operation is no longer connected"
|
|
2035
|
+
);
|
|
2241
2036
|
}
|
|
2242
2037
|
interactionRoute = route;
|
|
2243
2038
|
}
|
|
2244
2039
|
if (!interactionRoute && !this._forcedTransport && record && !record.usbSessionId && this._usbConnector) {
|
|
2245
|
-
await this._tryUsbAttach(record.walletId, record.masterFingerprint, {
|
|
2040
|
+
await this._tryUsbAttach(signal, record.walletId, record.masterFingerprint, {
|
|
2246
2041
|
waitForReenumeration: record.hadUsbSession
|
|
2247
2042
|
});
|
|
2248
2043
|
}
|
|
@@ -2250,15 +2045,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2250
2045
|
if (wantUsb) {
|
|
2251
2046
|
if (!record?.usbSessionId || !this._usbConnector) {
|
|
2252
2047
|
if (interactionRoute) {
|
|
2253
|
-
this.
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
params: {
|
|
2258
|
-
operationId: interactionRoute.operationId,
|
|
2259
|
-
reason: "disconnect"
|
|
2260
|
-
}
|
|
2261
|
-
});
|
|
2048
|
+
throw this._endDisconnectedOperation(
|
|
2049
|
+
interactionRoute.operationId,
|
|
2050
|
+
"Keystone operation USB connection was lost"
|
|
2051
|
+
);
|
|
2262
2052
|
}
|
|
2263
2053
|
throw createHwkError({
|
|
2264
2054
|
code: HardwareErrorCode.TransportNotAvailable,
|
|
@@ -2273,15 +2063,17 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2273
2063
|
);
|
|
2274
2064
|
if (result.success) return result.payload;
|
|
2275
2065
|
const usbError = rehydrateConnectorError(result.error);
|
|
2276
|
-
const usbErrorOrigin = usbError
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2066
|
+
const { origin: usbErrorOrigin, code: usbErrorCode } = usbError;
|
|
2067
|
+
if (usbErrorCode === HardwareErrorCode.PayloadTooLarge && usbErrorOrigin !== "device") {
|
|
2068
|
+
if (!interactionRoute && !this._forcedTransport) {
|
|
2069
|
+
const displayDevice2 = toDeviceInfo(record);
|
|
2070
|
+
return this._requestQrDisplayAndAwaitResponse(
|
|
2071
|
+
displayDevice2,
|
|
2072
|
+
{ ...requestUr, animated },
|
|
2073
|
+
signal
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
throw usbError;
|
|
2285
2077
|
}
|
|
2286
2078
|
const deviceAnswered = usbErrorOrigin !== void 0 ? usbErrorOrigin === "device" : usbErrorCode === HardwareErrorCode.UserRejected || usbErrorCode === HardwareErrorCode.UserAborted || usbErrorCode === HardwareErrorCode.DeviceLocked || usbErrorCode === HardwareErrorCode.DeviceMismatch;
|
|
2287
2079
|
if (!deviceAnswered) {
|
|
@@ -2291,19 +2083,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2291
2083
|
}
|
|
2292
2084
|
record.usbSessionId = void 0;
|
|
2293
2085
|
if (interactionRoute) {
|
|
2294
|
-
this.
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
params: operationName ? operationMayHaveCompletedParams(operationName, {
|
|
2300
|
-
operationId: interactionRoute.operationId,
|
|
2301
|
-
reason: "disconnect"
|
|
2302
|
-
}) : {
|
|
2303
|
-
operationId: interactionRoute.operationId,
|
|
2304
|
-
reason: "disconnect"
|
|
2305
|
-
}
|
|
2306
|
-
});
|
|
2086
|
+
throw this._endDisconnectedOperation(
|
|
2087
|
+
interactionRoute.operationId,
|
|
2088
|
+
operationName ? `Keystone ${operationName} may have completed before the USB connection was lost` : "Keystone operation USB connection was lost",
|
|
2089
|
+
operationName
|
|
2090
|
+
);
|
|
2307
2091
|
}
|
|
2308
2092
|
if (operationName) {
|
|
2309
2093
|
throw createHwkError({
|
|
@@ -2337,51 +2121,49 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2337
2121
|
throw error;
|
|
2338
2122
|
}
|
|
2339
2123
|
}
|
|
2124
|
+
/** Ends the operation as disconnected and returns the error to throw. */
|
|
2125
|
+
_endDisconnectedOperation(operationId, message, operationName) {
|
|
2126
|
+
this._operations.end(operationId, "disconnect");
|
|
2127
|
+
const params = { operationId, reason: "disconnect" };
|
|
2128
|
+
return createHwkError({
|
|
2129
|
+
code: HardwareErrorCode.OperationEnded,
|
|
2130
|
+
message,
|
|
2131
|
+
recovery: operationName ? { scope: "unknown" } : void 0,
|
|
2132
|
+
params: operationName ? operationMayHaveCompletedParams(operationName, params) : params
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2340
2135
|
/**
|
|
2341
|
-
*
|
|
2342
|
-
*
|
|
2343
|
-
* retained on the record. A failed USB export ends this operation without
|
|
2344
|
-
* replaying the request or switching transports.
|
|
2136
|
+
* Fetches one account's key material for this operation only; `book` reuses earlier fetches. A
|
|
2137
|
+
* failed USB export ends the operation without replaying or switching transports.
|
|
2345
2138
|
*/
|
|
2346
|
-
async _fetchAccount(connectId, deviceId, hwkChain, syncPath, signal,
|
|
2139
|
+
async _fetchAccount(connectId, deviceId, hwkChain, syncPath, signal, book) {
|
|
2347
2140
|
const target = this._resolveTarget(connectId, deviceId);
|
|
2141
|
+
const operationConnectionType = isHardwareOperationId2(connectId) ? this._operations.resolve(connectId).connectionType : void 0;
|
|
2348
2142
|
const key = accountKey(hwkChain, syncPath);
|
|
2349
2143
|
let existingRecord = target.record;
|
|
2350
2144
|
if (!existingRecord) {
|
|
2351
|
-
|
|
2145
|
+
existingRecord = await this._tryUsbAttach(signal, target.expectedWalletId);
|
|
2352
2146
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2353
|
-
existingRecord = attached;
|
|
2354
2147
|
}
|
|
2355
|
-
const book = options?.book;
|
|
2356
2148
|
const booked = book?.get(key);
|
|
2357
2149
|
if (existingRecord && booked) {
|
|
2358
2150
|
return { record: existingRecord, account: booked };
|
|
2359
2151
|
}
|
|
2360
|
-
if (existingRecord && !existingRecord.usbSessionId && this._forcedTransport === "usb") {
|
|
2361
|
-
await this._tryUsbAttach(existingRecord.walletId, existingRecord.masterFingerprint, {
|
|
2152
|
+
if (!operationConnectionType && existingRecord && !existingRecord.usbSessionId && this._forcedTransport === "usb") {
|
|
2153
|
+
await this._tryUsbAttach(signal, existingRecord.walletId, existingRecord.masterFingerprint, {
|
|
2362
2154
|
waitForReenumeration: existingRecord.hadUsbSession
|
|
2363
2155
|
});
|
|
2364
2156
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2365
2157
|
existingRecord = this._devices.get(existingRecord.walletId) ?? existingRecord;
|
|
2366
2158
|
}
|
|
2367
|
-
const useSinglePathUsbExport = this._forcedTransport !== "qr" && Boolean(existingRecord?.usbSessionId);
|
|
2368
|
-
const
|
|
2369
|
-
|
|
2370
|
-
];
|
|
2371
|
-
const schemaPaths = useSinglePathUsbExport || requestedSchemaPaths.some((schema) => normalizePath(schema.path) === KEYSTONE_WALLET_ID_PATH) ? requestedSchemaPaths : [{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH }, ...requestedSchemaPaths];
|
|
2372
|
-
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
2373
|
-
schemas: schemaPaths.map((s) => ({
|
|
2374
|
-
path: s.path,
|
|
2375
|
-
curve: s.hwkChain === "sol" ? "ed25519" : "secp256k1"
|
|
2376
|
-
})),
|
|
2377
|
-
origin: this._origin
|
|
2378
|
-
});
|
|
2379
|
-
const resolvedViaUsb = useSinglePathUsbExport;
|
|
2159
|
+
const useSinglePathUsbExport = operationConnectionType ? operationConnectionType === "usb" : this._forcedTransport !== "qr" && Boolean(existingRecord?.usbSessionId);
|
|
2160
|
+
const schemas = useSinglePathUsbExport || normalizePath(syncPath) === KEYSTONE_WALLET_ID_PATH ? [keySchema(hwkChain, syncPath)] : [keySchema("evm", KEYSTONE_WALLET_ID_PATH), keySchema(hwkChain, syncPath)];
|
|
2161
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({ schemas, origin: this._origin });
|
|
2380
2162
|
const responseUr = await this._resolveUr(existingRecord, requestUr, false, connectId, signal);
|
|
2381
2163
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2382
2164
|
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
2383
2165
|
let record;
|
|
2384
|
-
if (
|
|
2166
|
+
if (useSinglePathUsbExport && existingRecord) {
|
|
2385
2167
|
if (parsed.masterFingerprint !== existingRecord.masterFingerprint) {
|
|
2386
2168
|
throw createHwkError({
|
|
2387
2169
|
code: HardwareErrorCode.DeviceMismatch,
|
|
@@ -2392,7 +2174,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2392
2174
|
} else {
|
|
2393
2175
|
this._assertParsedIdentity(parsed, target);
|
|
2394
2176
|
record = this._upsertDeviceRecord(parsed, {
|
|
2395
|
-
viaUsb: Boolean(existingRecord?.usbSessionId)
|
|
2177
|
+
viaUsb: operationConnectionType !== "qr" && Boolean(existingRecord?.usbSessionId)
|
|
2396
2178
|
});
|
|
2397
2179
|
}
|
|
2398
2180
|
const account = parsed.accounts.find((a) => normalizePath(a.path) === syncPath);
|
|
@@ -2418,52 +2200,27 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2418
2200
|
return { record, account: entry };
|
|
2419
2201
|
}
|
|
2420
2202
|
/**
|
|
2421
|
-
*
|
|
2422
|
-
*
|
|
2423
|
-
* specific cached path. Syncs the account-level path for `chain` as a
|
|
2424
|
-
* throwaway probe when the wallet record isn't already known.
|
|
2425
|
-
*
|
|
2426
|
-
* `CHAIN_FINGERPRINT_PATHS[chain]` is a 5-segment LEAF path for `evm`
|
|
2427
|
-
* (`m/44'/60'/0'/0/0`) — sending that verbatim as a KeyDerivation request
|
|
2428
|
-
* asks Keystone for a non-standard path. Keystone's own docs
|
|
2429
|
-
* (dev.keyst.one's multichain KeyDerivation example) show the ETH
|
|
2430
|
-
* account-level path as `m/44'/60'/0'` (3 segments), same as what
|
|
2431
|
-
* `_fetchAccount` already requests — so
|
|
2432
|
-
* truncate through `splitAccountPath` here too instead of using the raw
|
|
2433
|
-
* fingerprint leaf path. `btc`/`sol` are already 3-segment account paths
|
|
2434
|
-
* and pass through unchanged.
|
|
2203
|
+
* Resolves which wallet is attached, without a per-path account. `CHAIN_FINGERPRINT_PATHS.evm` is
|
|
2204
|
+
* a leaf path, so it is cut to the account path Keystone expects.
|
|
2435
2205
|
*/
|
|
2436
2206
|
async _ensureWalletKnown(connectId, deviceId, chain, signal) {
|
|
2437
2207
|
const target = this._resolveTarget(connectId, deviceId);
|
|
2438
2208
|
if (target.record) return { record: target.record };
|
|
2439
|
-
const attached = await this._tryUsbAttach(target.expectedWalletId);
|
|
2209
|
+
const attached = await this._tryUsbAttach(signal, target.expectedWalletId);
|
|
2440
2210
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2441
2211
|
if (attached) return { record: attached };
|
|
2442
2212
|
const { accountPath } = splitAccountPath(CHAIN_FINGERPRINT_PATHS[chain]);
|
|
2443
|
-
const schemas = accountPath === KEYSTONE_WALLET_ID_PATH ? [
|
|
2444
|
-
|
|
2445
|
-
{
|
|
2446
|
-
path: accountPath,
|
|
2447
|
-
curve: chain === "sol" ? "ed25519" : "secp256k1"
|
|
2448
|
-
}
|
|
2449
|
-
];
|
|
2450
|
-
const requestUr = this.urEngine.buildKeyDerivationRequest({
|
|
2451
|
-
schemas,
|
|
2452
|
-
origin: this._origin
|
|
2453
|
-
});
|
|
2213
|
+
const schemas = accountPath === KEYSTONE_WALLET_ID_PATH ? [keySchema("evm", KEYSTONE_WALLET_ID_PATH)] : [keySchema("evm", KEYSTONE_WALLET_ID_PATH), keySchema(chain, accountPath)];
|
|
2214
|
+
const requestUr = this.urEngine.buildKeyDerivationRequest({ schemas, origin: this._origin });
|
|
2454
2215
|
const responseUr = await this._resolveUr(void 0, requestUr, false, connectId, signal);
|
|
2455
2216
|
_KeystoneAdapter._throwIfAborted(signal);
|
|
2456
2217
|
const parsed = this.urEngine.parseAccountResponse(responseUr);
|
|
2457
2218
|
this._assertParsedIdentity(parsed, target);
|
|
2458
|
-
|
|
2459
|
-
return { record };
|
|
2219
|
+
return { record: this._upsertDeviceRecord(parsed) };
|
|
2460
2220
|
}
|
|
2461
2221
|
/**
|
|
2462
|
-
* The operation the running
|
|
2463
|
-
*
|
|
2464
|
-
* (`keystoneQueueKey`); a call without one queues under the wallet id or the
|
|
2465
|
-
* wallet connectId, and the live operation routed to that connection is still
|
|
2466
|
-
* the owner. Only a cold start comes back undefined.
|
|
2222
|
+
* The operation owning the running job, so a mid-call UI request can name it. Unpinned jobs map
|
|
2223
|
+
* through the operation route; a cold start returns undefined.
|
|
2467
2224
|
*/
|
|
2468
2225
|
_activeOperationId() {
|
|
2469
2226
|
const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
|
|
@@ -2481,9 +2238,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2481
2238
|
return walletId ? this._operationRoutes.get(walletConnectId(walletId)) : void 0;
|
|
2482
2239
|
}
|
|
2483
2240
|
/**
|
|
2484
|
-
* A QR prompt has no transport to interrupt
|
|
2485
|
-
*
|
|
2486
|
-
* cancel reach the wait at all.
|
|
2241
|
+
* A QR prompt has no transport to interrupt; releasing the registry slot on abort is what lets
|
|
2242
|
+
* a cancel reach the wait before the QR timeout.
|
|
2487
2243
|
*/
|
|
2488
2244
|
async _awaitQrResponse(requestType, pending, signal) {
|
|
2489
2245
|
if (!signal) return pending;
|
|
@@ -2552,8 +2308,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2552
2308
|
const releaseOperation = this._retainUsbOperation(`call:${sessionId}`);
|
|
2553
2309
|
let rawCall;
|
|
2554
2310
|
try {
|
|
2555
|
-
|
|
2556
|
-
rawCall = connector.call(sessionId, method, params).finally(releaseOperation);
|
|
2311
|
+
rawCall = this._usbConnector.call(sessionId, method, params).finally(releaseOperation);
|
|
2557
2312
|
} catch (error) {
|
|
2558
2313
|
releaseOperation();
|
|
2559
2314
|
throw error;
|
|
@@ -2603,13 +2358,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2603
2358
|
};
|
|
2604
2359
|
}
|
|
2605
2360
|
static _abortable(signal, promise) {
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
}
|
|
2361
|
+
const abortReason = () => signal.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
2362
|
+
if (signal.aborted) return Promise.reject(abortReason());
|
|
2609
2363
|
return new Promise((resolve, reject) => {
|
|
2610
|
-
const onAbort = () =>
|
|
2611
|
-
reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
2612
|
-
};
|
|
2364
|
+
const onAbort = () => reject(abortReason());
|
|
2613
2365
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2614
2366
|
promise.then(
|
|
2615
2367
|
(value) => {
|
|
@@ -2624,7 +2376,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
|
|
|
2624
2376
|
});
|
|
2625
2377
|
}
|
|
2626
2378
|
static _throwIfAborted(signal) {
|
|
2627
|
-
if (signal
|
|
2379
|
+
if (signal?.aborted) {
|
|
2628
2380
|
throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
2629
2381
|
}
|
|
2630
2382
|
}
|