@onekeyfe/hwk-keystone-adapter 1.2.3-alpha.4 → 1.2.5-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ var TronSignRequest = class _TronSignRequest extends import_bc_ur_registry.Regis
67
67
  this.getSignData = () => this.signData;
68
68
  this.getSignType = () => this.signType;
69
69
  this.getDerivationPath = () => this.derivationPath.getPath();
70
+ this.getOrigin = () => this.origin;
70
71
  this.toDataItem = () => {
71
72
  const map = {};
72
73
  if (this.requestId) {
@@ -139,6 +140,9 @@ var TronSignature = class _TronSignature extends import_bc_ur_registry2.Registry
139
140
  static fromDataItem(dataItem) {
140
141
  const map = dataItem.getData();
141
142
  const signature = map[2 /* signature */];
143
+ if (!Buffer.isBuffer(signature)) {
144
+ throw new Error("Keystone tron-signature UR is missing its signature bytes");
145
+ }
142
146
  const requestIdItem = map[1 /* requestId */];
143
147
  const requestId = requestIdItem?.getData();
144
148
  return new _TronSignature(signature, requestId);
@@ -169,14 +173,22 @@ function toSdkUr(ur) {
169
173
  function fromSdkUr(ur) {
170
174
  return { urType: ur.type, urData: ur.cbor.toString("hex") };
171
175
  }
172
- function splitSignature65(hex) {
173
- if (hex.length !== 130) {
176
+ function splitEvmSignature(hex) {
177
+ if (hex.length < 130 || hex.length % 2 !== 0) {
174
178
  throw new Error(
175
- `Keystone returned a ${hex.length / 2}-byte signature; expected 65 bytes (r|s|v)`
179
+ `Keystone returned a ${hex.length / 2}-byte signature; expected at least 65 bytes (r|s|v)`
176
180
  );
177
181
  }
178
182
  return { r: hex.slice(0, 64), s: hex.slice(64, 128), v: hex.slice(128) };
179
183
  }
184
+ function requireSignatureBytes(hex, expectedBytes, chain) {
185
+ if (hex.length !== expectedBytes * 2) {
186
+ throw new Error(
187
+ `Keystone returned a ${hex.length / 2}-byte ${chain} signature; expected ${expectedBytes} bytes`
188
+ );
189
+ }
190
+ return hex;
191
+ }
180
192
  function requireBip32MasterFingerprint(value) {
181
193
  const fingerprint = (0, import_hwk_adapter_core.parseBip32MasterFingerprint)(value);
182
194
  if (!fingerprint) {
@@ -184,12 +196,24 @@ function requireBip32MasterFingerprint(value) {
184
196
  }
185
197
  return fingerprint;
186
198
  }
199
+ function toParsedAccount(key) {
200
+ return {
201
+ chain: key.chain,
202
+ path: key.path,
203
+ publicKey: key.publicKey,
204
+ extendedPublicKey: key.extendedPublicKey,
205
+ xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
206
+ name: key.name
207
+ };
208
+ }
209
+ function relativeHdPath(relativeDerivePath) {
210
+ return `m/${relativeDerivePath.replace(/^m\//i, "")}`;
211
+ }
187
212
  var KeystoneUrEngine = class {
188
213
  constructor(origin = "OneKey") {
189
214
  this.sdk = new import_keystone_sdk.KeystoneSDK({ origin });
190
215
  }
191
- // --- Account sync (works for both a device-initiated QR export and a
192
- // wallet-initiated KeyDerivation request/response over either channel) ---
216
+ // --- Account sync (device-initiated export or KeyDerivation response) ---
193
217
  parseMultiAccounts(ur) {
194
218
  const parsed = this.sdk.parseMultiAccounts(toSdkUr(ur));
195
219
  return {
@@ -197,46 +221,15 @@ var KeystoneUrEngine = class {
197
221
  device: parsed.device,
198
222
  deviceId: parsed.deviceId,
199
223
  deviceVersion: parsed.deviceVersion,
200
- accounts: parsed.keys.map(
201
- (key) => ({
202
- chain: key.chain,
203
- path: key.path,
204
- publicKey: key.publicKey,
205
- extendedPublicKey: key.extendedPublicKey,
206
- xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
207
- name: key.name
208
- })
209
- )
224
+ accounts: parsed.keys.map(toParsedAccount)
210
225
  };
211
226
  }
212
227
  parseHDKey(ur) {
213
- const key = this.sdk.parseHDKey(toSdkUr(ur));
214
- return {
215
- chain: key.chain,
216
- path: key.path,
217
- publicKey: key.publicKey,
218
- extendedPublicKey: key.extendedPublicKey,
219
- xfp: key.xfp ? requireBip32MasterFingerprint(key.xfp) : void 0,
220
- name: key.name
221
- };
228
+ return toParsedAccount(this.sdk.parseHDKey(toSdkUr(ur)));
222
229
  }
223
230
  /**
224
- * Build a `qr-hardware-call` (KeyDerivation) request: the host asks for
225
- * specific paths instead of waiting for whatever the device happens to be
226
- * showing. The device replies with a `crypto-multi-accounts` UR — parse it
227
- * with `parseMultiAccounts`. Used for the implicit "sync this wallet's xfp
228
- * before the first sign" round trip as well as an explicit account import.
229
- *
230
- * `version: V1` is required — verified against real Keystone hardware and
231
- * `keystone3-firmware`'s `CheckHardwareCallRequestIsLegal` source: an
232
- * unversioned/V0 request is validated as a legacy Cardano-only request
233
- * (`m/1852'/1815'/...`) and firmware rejects every other chain's path with
234
- * `PRS_PARSING_ERROR` (device-shown message: "路径不受支持" / "path not
235
- * supported"), regardless of the path's shape. V1 is what actually enables
236
- * the general per-chain path whitelist (includes `m/44'/60'` for ETH, the
237
- * standard BTC purposes, etc.). The SDK itself defaults to V0 unless a
238
- * truthy `version` is passed — omitting this silently produces a request
239
- * every non-Cardano device rejects.
231
+ * Builds a KeyDerivation request. `version: V1` is required: firmware validates V0 (the SDK
232
+ * default) as Cardano-only and rejects other paths with `PRS_PARSING_ERROR`.
240
233
  */
241
234
  buildKeyDerivationRequest(input) {
242
235
  const ur = this.sdk.generateKeyDerivationCall({
@@ -250,14 +243,8 @@ var KeystoneUrEngine = class {
250
243
  return fromSdkUr(ur);
251
244
  }
252
245
  /**
253
- * Parse the response to a KeyDerivation request (or a device-initiated
254
- * account export): `crypto-multi-accounts` for a multi-schema request,
255
- * `crypto-hdkey` for a single-key response some firmware paths use instead.
256
- * Both are normalized to the same `KeystoneParsedMultiAccounts` shape — a
257
- * single `crypto-hdkey` becomes a one-entry account list, with its own
258
- * `origin.sourceFingerprint` promoted to `masterFingerprint` (correct for a
259
- * key derived directly from the seed, which every request this engine
260
- * builds asks for).
246
+ * Normalizes `crypto-multi-accounts` and the single-key `crypto-hdkey` some firmware returns. The
247
+ * hdkey source fingerprint is the mfp only because every request here derives from the seed.
261
248
  */
262
249
  parseAccountResponse(ur) {
263
250
  if (ur.urType === "crypto-hdkey") {
@@ -288,37 +275,19 @@ var KeystoneUrEngine = class {
288
275
  }
289
276
  parseEthSignature(ur) {
290
277
  const signature = this.sdk.eth.parseSignature(toSdkUr(ur));
291
- return { requestId: signature.requestId, ...splitSignature65(signature.signature) };
278
+ return { requestId: signature.requestId, ...splitEvmSignature(signature.signature) };
292
279
  }
293
- /**
294
- * Derive one EVM address offline from an already-synced account xpub —
295
- * verified against the same `@keystonehq/bc-ur-registry-eth` helper the
296
- * Keystone-based OneKey air-gap demo uses in production
297
- * (`generateAddressFromXpub`), so no unverified assumption about what a
298
- * leaf-path KeyDerivation request would return. `relativeDerivePath` is
299
- * relative to the xpub's own depth, e.g. `'0/0'` for an account xpub.
300
- */
280
+ /** Derives an EVM address offline; `relativeDerivePath` is relative to the xpub, e.g. `'0/0'`. */
301
281
  deriveEvmAddressFromXpub(xpub, relativeDerivePath) {
302
- return (0, import_bc_ur_registry_eth.generateAddressFromXpub)(xpub, `m/${relativeDerivePath.replace(/^m\//i, "")}`);
282
+ return (0, import_bc_ur_registry_eth.generateAddressFromXpub)(xpub, relativeHdPath(relativeDerivePath));
303
283
  }
304
284
  // --- BTC (PSBT transaction signing + plain message signing) ---
305
285
  /**
306
- * Derive one BTC address offline from an already-synced account xpub, the
307
- * same way `deriveEvmAddressFromXpub` does. Keystone's `CryptoHDKey`
308
- * always emits standard mainnet-xpub version bytes (`0488B21E`) regardless
309
- * of the account's purpose/script type (verified against
310
- * `@keystonehq/bc-ur-registry`'s `CryptoHDKey.getBip32Key`, which hardcodes
311
- * that version rather than switching to a SLIP-132 ypub/zpub prefix per
312
- * script type) — so `hdkey.fromExtendedKey` parses it correctly for every
313
- * `scriptType` without needing custom version bytes configured.
314
- *
315
- * `p2tr` is deliberately not handled: taproot output-key tweaking (BIP-341)
316
- * needs an elliptic-curve library wired via bitcoinjs-lib's `initEccLib`,
317
- * which this package doesn't set up yet — every other payment function
318
- * here needs no such library.
286
+ * `CryptoHDKey` always emits mainnet xpub version bytes (`0488B21E`), so `hdkey` parses it as is.
287
+ * `p2tr` needs an ECC lib for BIP-341 tweaking, not wired here.
319
288
  */
320
289
  deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType) {
321
- const node = import_hdkey.default.fromExtendedKey(xpub).derive(`m/${relativeDerivePath.replace(/^m\//i, "")}`);
290
+ const node = import_hdkey.default.fromExtendedKey(xpub).derive(relativeHdPath(relativeDerivePath));
322
291
  if (!node.publicKey) throw new Error("HDKey derivation did not produce a public key");
323
292
  return this.deriveBtcAddressFromPublicKey(
324
293
  Buffer.from(node.publicKey).toString("hex"),
@@ -371,7 +340,7 @@ var KeystoneUrEngine = class {
371
340
  requestId: params.requestId,
372
341
  signData: params.messageHex,
373
342
  dataType: 1,
374
- // BtcSignRequest.DataType.message — PSBT signing never goes through this path.
343
+ // BtcSignRequest.DataType.message; PSBT signing never goes through this path.
375
344
  accounts: params.accounts,
376
345
  origin: params.origin
377
346
  });
@@ -400,7 +369,10 @@ var KeystoneUrEngine = class {
400
369
  }
401
370
  parseSolSignature(ur) {
402
371
  const signature = this.sdk.sol.parseSignature(toSdkUr(ur));
403
- return { requestId: signature.requestId, signature: signature.signature };
372
+ return {
373
+ requestId: signature.requestId,
374
+ signature: requireSignatureBytes(signature.signature, 64, "SOL")
375
+ };
404
376
  }
405
377
  // --- TRON ---
406
378
  /**
@@ -425,37 +397,22 @@ var KeystoneUrEngine = class {
425
397
  const requestId = signature.getRequestId();
426
398
  return {
427
399
  requestId: requestId ? (0, import_uuid.stringify)(requestId) : void 0,
428
- signature: signature.getSignature().toString("hex")
400
+ signature: requireSignatureBytes(signature.getSignature().toString("hex"), 65, "TRON")
429
401
  };
430
402
  }
431
403
  /**
432
- * Derive one TRON address offline from an already-synced account xpub.
433
- * TRON reuses EVM's exact secp256k1-pubkey → keccak256 → last-20-bytes
434
- * derivation (verified against Keystone's own `formatAddress()` in
435
- * `keystone-sdk`'s TRON chain source) — only the final text encoding
436
- * differs (base58check with a `0x41` version byte, not checksummed hex).
437
- * Reusing `generateAddressFromXpub` here means no new hashing dependency:
438
- * strip its "0x" and re-encode the same 20 bytes.
404
+ * TRON uses the same 20 address bytes as EVM (Keystone's `formatAddress()`), re-encoded as
405
+ * base58check with the `0x41` version byte.
439
406
  */
440
407
  deriveTronAddressFromXpub(xpub, relativeDerivePath) {
441
- const evmStyleHex = (0, import_bc_ur_registry_eth.generateAddressFromXpub)(
442
- xpub,
443
- `m/${relativeDerivePath.replace(/^m\//i, "")}`
444
- );
408
+ const evmStyleHex = (0, import_bc_ur_registry_eth.generateAddressFromXpub)(xpub, relativeHdPath(relativeDerivePath));
445
409
  const addressBytes = Buffer.concat([
446
410
  Buffer.from([TRON_ADDRESS_PREFIX]),
447
- Buffer.from(evmStyleHex.replace(/^0x/i, ""), "hex")
411
+ Buffer.from(stripHexPrefix(evmStyleHex), "hex")
448
412
  ]);
449
413
  return import_bs58check.default.encode(addressBytes);
450
414
  }
451
- /**
452
- * Split an account-level xpub back into the BIP-32 fields a host needs to
453
- * treat it as a real extended key (`BtcPublicKey`). Everything here is
454
- * carried inside the xpub's own serialization — depth, the PARENT key
455
- * fingerprint (not the seed's master fingerprint), the chain code and the
456
- * compressed public key — so this is a pure decode with no device round
457
- * trip; the xpub itself was already device-verified when it was synced.
458
- */
415
+ /** Decodes an xpub's BIP-32 fields; `parentFingerprint` is the parent key's, not the mfp. */
459
416
  parseXpubMeta(xpub) {
460
417
  const raw = Buffer.from(import_bs58check.default.decode(xpub));
461
418
  if (raw.length !== 78) {
@@ -473,7 +430,7 @@ var KeystoneUrEngine = class {
473
430
  // src/adapter/KeystoneAdapter.ts
474
431
  var import_bs58 = __toESM(require("bs58"));
475
432
  var import_uuid2 = require("uuid");
476
- var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
433
+ var import_hwk_adapter_core4 = require("@onekeyfe/hwk-adapter-core");
477
434
 
478
435
  // src/adapter/deviceTable.ts
479
436
  var import_hwk_adapter_core2 = require("@onekeyfe/hwk-adapter-core");
@@ -584,21 +541,47 @@ function placeholderDeviceInfo() {
584
541
  };
585
542
  }
586
543
 
544
+ // src/utils/queueKey.ts
545
+ var import_hwk_adapter_core3 = require("@onekeyfe/hwk-adapter-core");
546
+ var KEYSTONE_COLD_START_JOB_LABEL = "keystone-cold-start";
547
+ function keystoneQueueKey(connectId, deviceId) {
548
+ return (0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) ? connectId : deviceId || connectId || KEYSTONE_COLD_START_JOB_LABEL;
549
+ }
550
+ function keystoneWalletIdFromIdentifier(identifier) {
551
+ const value = identifier.startsWith(KEYSTONE_WALLET_CONNECT_ID_PREFIX) ? identifier.slice(KEYSTONE_WALLET_CONNECT_ID_PREFIX.length) : identifier;
552
+ return /^[0-9a-f]{64}$/i.test(value) ? value.toLowerCase() : void 0;
553
+ }
554
+ function keystoneCancelQueueKeys(identifiers) {
555
+ const keys = /* @__PURE__ */ new Set();
556
+ for (const identifier of identifiers) {
557
+ if (identifier) {
558
+ keys.add(identifier);
559
+ const walletId = keystoneWalletIdFromIdentifier(identifier);
560
+ if (walletId) keys.add(walletId);
561
+ }
562
+ }
563
+ return keys;
564
+ }
565
+
587
566
  // src/adapter/KeystoneAdapter.ts
588
- var COLD_START_JOB_LABEL = "keystone-cold-start";
589
567
  var KEYSTONE_USB_REATTACH_PROBE_ATTEMPTS = 4;
590
568
  var KEYSTONE_USB_REATTACH_PROBE_INTERVAL_MS = 500;
591
569
  function resolveHardwareOperationTarget(positionalTargetId, operationId) {
592
- const result = (0, import_hwk_adapter_core3.resolveHardwareOperationTarget)(positionalTargetId, operationId, "keystone");
593
- return result;
570
+ return (0, import_hwk_adapter_core4.resolveHardwareOperationTarget)(positionalTargetId, operationId, "keystone");
571
+ }
572
+ function keySchema(chain, path) {
573
+ return { path, curve: chain === "sol" ? "ed25519" : "secp256k1" };
594
574
  }
595
575
  function waitForKeystoneUsbReattachProbe() {
596
576
  return new Promise((resolve) => {
597
577
  setTimeout(resolve, KEYSTONE_USB_REATTACH_PROBE_INTERVAL_MS);
598
578
  });
599
579
  }
600
- function getKeystoneJobId(connectId, deviceId) {
601
- return (0, import_hwk_adapter_core3.isHardwareOperationId)(connectId) ? connectId : deviceId || connectId || COLD_START_JOB_LABEL;
580
+ function buildCancelledFailure(signal) {
581
+ const reason = signal.reason;
582
+ const code = typeof reason?.code === "number" ? reason.code : import_hwk_adapter_core4.HardwareErrorCode.UserAborted;
583
+ const message = typeof reason?.message === "string" ? reason.message : "";
584
+ return (0, import_hwk_adapter_core4.failure)(code, message || "All-network get-address cancelled");
602
585
  }
603
586
  var BIP44_COIN_TYPE_TO_CHAIN = {
604
587
  60: "evm",
@@ -613,17 +596,20 @@ function inferHwkChainFromPath(path) {
613
596
  var KeystoneAdapter = class _KeystoneAdapter {
614
597
  constructor(options) {
615
598
  this.vendor = "keystone";
616
- this.emitter = new import_hwk_adapter_core3.TypedEventEmitter();
599
+ // No protocol-level cancel exists over Keystone USB, and QR has no transport
600
+ // to interrupt at all. Cancelling ends the wait, not the device's prompt.
601
+ this.cancelCapability = "stops-waiting";
602
+ this.emitter = new import_hwk_adapter_core4.TypedEventEmitter();
617
603
  this._operationRoutes = /* @__PURE__ */ new Map();
618
- this._operations = new import_hwk_adapter_core3.OperationRegistry({
604
+ this._operations = new import_hwk_adapter_core4.OperationRegistry({
619
605
  vendor: "keystone",
620
606
  onEnded: (operation, reason) => {
621
607
  const route = this._operationRoutes.get(operation.connectId);
622
608
  if (route?.operationId === operation.operationId) {
623
609
  this._operationRoutes.delete(operation.connectId);
624
610
  }
625
- this.emitter.emit(import_hwk_adapter_core3.SDK.OPERATION_ENDED, {
626
- type: import_hwk_adapter_core3.SDK.OPERATION_ENDED,
611
+ this.emitter.emit(import_hwk_adapter_core4.SDK.OPERATION_ENDED, {
612
+ type: import_hwk_adapter_core4.SDK.OPERATION_ENDED,
627
613
  payload: { operationId: operation.operationId, reason }
628
614
  });
629
615
  if (reason === "timeout") {
@@ -631,54 +617,62 @@ var KeystoneAdapter = class _KeystoneAdapter {
631
617
  }
632
618
  }
633
619
  });
634
- this._uiRegistry = new import_hwk_adapter_core3.UiRequestRegistry();
620
+ this._uiRegistry = new import_hwk_adapter_core4.UiRequestRegistry();
635
621
  this._devices = /* @__PURE__ */ new Map();
636
622
  this._searchDeviceTargets = /* @__PURE__ */ new Map();
637
623
  /** Serializes USB open/identity handshakes that run outside the job queue. */
638
624
  this._usbConnectTail = Promise.resolve();
639
625
  this._unsettledUsbOperations = /* @__PURE__ */ new Map();
640
626
  this._usbIdleWaiters = /* @__PURE__ */ new Set();
627
+ this._abandonedUsbConnects = 0;
641
628
  this._usbTeardownTail = Promise.resolve();
642
629
  this._pendingUsbTeardowns = 0;
643
630
  this._handleUsbDisconnect = ({ connectId }) => {
644
631
  const record = Array.from(this._devices.values()).find((item) => item.usbSessionId === connectId);
645
632
  if (!record) return;
646
633
  this._operations.endByConnectionKey(connectId, "disconnect");
647
- this._operations.endByConnectionKey(record.connectId, "disconnect");
648
634
  record.usbSessionId = void 0;
649
635
  const info = toDeviceInfo(record);
650
636
  if (record.qrSynced) {
651
- this.emitter.emit(import_hwk_adapter_core3.DEVICE.CHANGED, { type: import_hwk_adapter_core3.DEVICE.CHANGED, payload: info });
637
+ this.emitter.emit(import_hwk_adapter_core4.DEVICE.CHANGED, { type: import_hwk_adapter_core4.DEVICE.CHANGED, payload: info });
652
638
  } else {
653
639
  this._devices.delete(record.walletId);
654
- this.emitter.emit(import_hwk_adapter_core3.DEVICE.DISCONNECT, { type: import_hwk_adapter_core3.DEVICE.DISCONNECT, payload: info });
640
+ this.emitter.emit(import_hwk_adapter_core4.DEVICE.DISCONNECT, { type: import_hwk_adapter_core4.DEVICE.DISCONNECT, payload: info });
655
641
  }
656
642
  };
657
643
  this._handleUsbUiEvent = (event) => {
658
644
  this.emitter.emit("ui-event", event);
659
645
  };
660
646
  // ---------------------------------------------------------------------------
661
- // All-network bundle — dispatches to the same per-chain methods below, so
662
- // there's exactly one implementation of each chain's address logic.
647
+ // All-network bundle, dispatched to the per-chain methods below.
663
648
  // ---------------------------------------------------------------------------
664
649
  this.allNetworkGetAddress = async (connectId, deviceId, params) => {
665
650
  const operationTarget = resolveHardwareOperationTarget(connectId, params.operationId);
666
651
  if (!operationTarget.success) return operationTarget;
667
652
  const effectiveConnectId = operationTarget.payload.targetId ?? "";
668
653
  const { operationId } = operationTarget.payload;
654
+ let cancelScope;
669
655
  try {
670
- const prefetched = await this._prefetchAllNetworkAccounts(
671
- effectiveConnectId,
672
- deviceId,
673
- params
674
- );
656
+ const prefetched = await this._prefetchAllNetworkAccounts(effectiveConnectId, deviceId, {
657
+ ...params,
658
+ operationId
659
+ });
675
660
  const { book } = prefetched;
676
661
  const itemDeviceId = deviceId || prefetched.walletId || "";
677
- return await (0, import_hwk_adapter_core3.runAllNetworkGetAddress)({
662
+ const scope = this._jobQueue.createCancelScope(
663
+ keystoneQueueKey(effectiveConnectId, itemDeviceId)
664
+ );
665
+ cancelScope = scope;
666
+ const cancelledIndexes = /* @__PURE__ */ new Set();
667
+ return await (0, import_hwk_adapter_core4.runAllNetworkGetAddress)({
678
668
  connectId: effectiveConnectId,
679
669
  deviceId: itemDeviceId,
680
670
  params,
681
- callItem: async ({ method, item }) => {
671
+ callItem: async ({ method, item, index }) => {
672
+ if (scope.signal.aborted) {
673
+ cancelledIndexes.add(index);
674
+ return buildCancelledFailure(scope.signal);
675
+ }
682
676
  const commonArgs = {
683
677
  path: item.path,
684
678
  showOnDevice: item.showOnDevice,
@@ -696,8 +690,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
696
690
  case "tronGetAddress":
697
691
  return this.tronGetAddress(effectiveConnectId, itemDeviceId, commonArgs, book);
698
692
  default:
699
- return (0, import_hwk_adapter_core3.failure)(
700
- import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
693
+ return (0, import_hwk_adapter_core4.failure)(
694
+ import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
701
695
  `Unsupported method: ${String(method)}`
702
696
  );
703
697
  }
@@ -717,16 +711,24 @@ var KeystoneAdapter = class _KeystoneAdapter {
717
711
  } : void 0
718
712
  }
719
713
  });
714
+ },
715
+ // Cancel, refusal or a lost session ends the batch; other per-item
716
+ // failures carry on with the next chain.
717
+ shouldAbortBundle: (response, { index }) => {
718
+ const { code } = response.payload ?? {};
719
+ return cancelledIndexes.has(index) || (0, import_hwk_adapter_core4.isUserRefusal)(code) || (0, import_hwk_adapter_core4.isConnectionLost)(code);
720
720
  }
721
721
  });
722
722
  } catch (err) {
723
723
  return this._errorToFailure(err);
724
+ } finally {
725
+ cancelScope?.release();
724
726
  }
725
727
  };
726
728
  this._origin = options?.origin ?? "OneKey";
727
729
  this._qrTimeoutMs = options?.qrTimeoutMs;
728
730
  this.urEngine = new KeystoneUrEngine(this._origin);
729
- this._jobQueue = new import_hwk_adapter_core3.DeviceJobQueue();
731
+ this._jobQueue = new import_hwk_adapter_core4.DeviceJobQueue();
730
732
  this._usbConnector = options?.usbConnector;
731
733
  this._usbConnector?.on("device-disconnect", this._handleUsbDisconnect);
732
734
  this._usbConnector?.on("ui-event", this._handleUsbUiEvent);
@@ -742,7 +744,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
742
744
  getAvailableTransports() {
743
745
  return this._usbConnector ? ["qr", "usb"] : ["qr"];
744
746
  }
745
- /** Pins subsequent calls to 'qr' or 'usb' (routing otherwise defaults to "USB when the target wallet has a live session, else QR" — see `_resolveUr`). Any other value clears the pin back to auto. */
747
+ /** Pins calls to 'qr' or 'usb'; any other value clears the pin back to auto routing. */
746
748
  switchTransport(type) {
747
749
  this._forcedTransport = type === "qr" || type === "usb" ? type : void 0;
748
750
  return Promise.resolve();
@@ -755,6 +757,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
755
757
  this._uiRegistry.cancel();
756
758
  this._jobQueue.clear();
757
759
  this._searchDeviceTargets.clear();
760
+ await this._usbConnectTail;
758
761
  await this._resetUsbSessions();
759
762
  this._usbConnector?.off("device-disconnect", this._handleUsbDisconnect);
760
763
  this._usbConnector?.off("ui-event", this._handleUsbUiEvent);
@@ -766,14 +769,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
766
769
  // Device table
767
770
  // ---------------------------------------------------------------------------
768
771
  /**
769
- * Unscoped discovery includes QR-synced wallets already known to this
770
- * adapter. Explicit QR discovery returns a virtual connection target because
771
- * there is no physical descriptor to enumerate. When a USB connector is
772
- * configured, its raw scan results are appended as-is: a USB descriptor has
773
- * no mfp until `connectDevice()` actually opens+claims it (see
774
- * `KeystoneUsbConnectorBase.searchDevices`), so these entries carry an
775
- * empty `deviceId` and exist purely so a host can list "plugged in, click
776
- * to connect" candidates.
772
+ * Returns known wallets plus USB scan results; USB entries have no mfp until `connectDevice()`
773
+ * claims them. Explicit QR discovery returns one virtual target.
777
774
  */
778
775
  async searchDevices(options) {
779
776
  const searchGeneration = /* @__PURE__ */ Symbol("keystone-device-search");
@@ -786,7 +783,7 @@ var KeystoneAdapter = class _KeystoneAdapter {
786
783
  }
787
784
  const known = Array.from(this._devices.values()).map(toDeviceInfo);
788
785
  if (options?.transportType === "qr") {
789
- const searchTargetId = (0, import_hwk_adapter_core3.createHardwareSearchTargetId)("keystone");
786
+ const searchTargetId = (0, import_hwk_adapter_core4.createHardwareSearchTargetId)("keystone");
790
787
  this._searchDeviceTargets.set(searchTargetId, "qr");
791
788
  return [
792
789
  {
@@ -809,18 +806,15 @@ var KeystoneAdapter = class _KeystoneAdapter {
809
806
  if (this._activeSearchGeneration !== searchGeneration) return [];
810
807
  const placeholders = usbDevices.map((d) => ({
811
808
  vendor: "keystone",
812
- // Empty, not 'unknown': enumeration genuinely cannot tell the model
813
- // apart (every Keystone shares one vid/pid), and a literal placeholder
814
- // string would beat the host's own default-name fallback and surface as
815
- // a device called "unknown".
809
+ // Empty, not 'unknown': all models share one vid/pid, and a placeholder
810
+ // string would beat the host's default-name fallback.
816
811
  model: d.model ?? "",
817
812
  modelName: d.modelName,
818
- // Placeholders — neither is knowable until the device is opened.
813
+ // Unknown until the device is opened.
819
814
  firmwareVersion: "0.0.0",
820
815
  deviceId: d.deviceId,
821
816
  connectId: d.connectId,
822
817
  connectionType: "usb",
823
- // The USB product name, which IS readable at enumeration time.
824
818
  label: d.name,
825
819
  serialNumber: d.serialNumber,
826
820
  capabilities: d.capabilities ?? { persistentDeviceIdentity: false },
@@ -840,20 +834,25 @@ var KeystoneAdapter = class _KeystoneAdapter {
840
834
  Array.from(this._devices.values()).map((record) => record.usbSessionId).filter((sessionId) => Boolean(sessionId))
841
835
  );
842
836
  for (const sessionId of sessionIds) {
843
- try {
844
- await this._usbConnector.disconnect(sessionId);
845
- } catch {
846
- }
847
- this._handleUsbDisconnect({ connectId: sessionId });
837
+ await this._retireUsbSession(sessionId);
848
838
  }
849
839
  });
850
840
  }
841
+ /** Best-effort disconnect; the session is retired either way so it cannot be selected again. */
842
+ async _retireUsbSession(sessionId) {
843
+ if (this._usbConnector) {
844
+ try {
845
+ await this._usbConnector.disconnect(sessionId);
846
+ } catch {
847
+ }
848
+ }
849
+ this._handleUsbDisconnect({ connectId: sessionId });
850
+ }
851
851
  async searchDeviceTargets(options) {
852
852
  const devices = await this.searchDevices(options);
853
853
  return devices.map((device) => ({
854
854
  searchTargetId: device.connectId,
855
- // Keystone's wallet id is stable only after the USB/QR handshake. The
856
- // selectable handle remains owned by the current discovery snapshot.
855
+ // The wallet id is stable only after the USB/QR handshake.
857
856
  searchTargetReusePolicy: "current-discovery",
858
857
  vendor: device.vendor,
859
858
  connectionType: device.connectionType,
@@ -872,25 +871,35 @@ var KeystoneAdapter = class _KeystoneAdapter {
872
871
  }));
873
872
  }
874
873
  async connectDevice(searchTargetId) {
874
+ const scope = this._jobQueue.createCancelScope(searchTargetId);
875
875
  try {
876
876
  let connected;
877
877
  let selectedConnectionType;
878
878
  const discoveredConnectionType = this._searchDeviceTargets.get(searchTargetId);
879
879
  if (discoveredConnectionType === "qr") {
880
880
  selectedConnectionType = "qr";
881
- connected = await this.importFromQr();
881
+ const abortColdStart = () => this._jobQueue.cancelActiveAndPending(
882
+ KEYSTONE_COLD_START_JOB_LABEL,
883
+ scope.signal.reason
884
+ );
885
+ scope.signal.addEventListener("abort", abortColdStart, { once: true });
886
+ try {
887
+ connected = await this.importFromQr();
888
+ } finally {
889
+ scope.signal.removeEventListener("abort", abortColdStart);
890
+ }
882
891
  } else if (discoveredConnectionType === "usb") {
883
892
  selectedConnectionType = "usb";
884
893
  if (!this._usbConnector) {
885
- return (0, import_hwk_adapter_core3.failure)(
886
- import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
894
+ return (0, import_hwk_adapter_core4.failure)(
895
+ import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
887
896
  "No USB connector configured for this Keystone adapter"
888
897
  );
889
898
  }
890
- connected = await this._connectUsb({}, searchTargetId);
891
- } else if ((0, import_hwk_adapter_core3.hasHardwareRuntimeIdPrefix)(searchTargetId)) {
892
- return (0, import_hwk_adapter_core3.failure)(
893
- import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
899
+ connected = await this._connectUsb({}, searchTargetId, scope.signal);
900
+ } else if ((0, import_hwk_adapter_core4.hasHardwareRuntimeIdPrefix)(searchTargetId)) {
901
+ return (0, import_hwk_adapter_core4.failure)(
902
+ import_hwk_adapter_core4.HardwareErrorCode.DeviceNotFound,
894
903
  "Keystone search target has expired; search for devices again",
895
904
  void 0,
896
905
  void 0,
@@ -900,17 +909,18 @@ var KeystoneAdapter = class _KeystoneAdapter {
900
909
  const target = this._resolveTarget(searchTargetId);
901
910
  if (target.record) {
902
911
  selectedConnectionType = target.record.usbSessionId ? "usb" : "qr";
903
- connected = (0, import_hwk_adapter_core3.success)(toDeviceInfo(target.record));
912
+ connected = (0, import_hwk_adapter_core4.success)(toDeviceInfo(target.record));
904
913
  } else if (!this._usbConnector) {
905
- return (0, import_hwk_adapter_core3.failure)(
906
- import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
914
+ return (0, import_hwk_adapter_core4.failure)(
915
+ import_hwk_adapter_core4.HardwareErrorCode.DeviceNotFound,
907
916
  `Unknown Keystone device search target: ${searchTargetId}`
908
917
  );
909
918
  } else {
910
919
  selectedConnectionType = "usb";
911
- connected = await this._connectUsb(target);
920
+ connected = await this._connectUsb(target, void 0, scope.signal);
912
921
  }
913
922
  }
923
+ _KeystoneAdapter._throwIfAborted(scope.signal);
914
924
  if (!connected.success) return connected;
915
925
  const record = this._devices.get(connected.payload.deviceId);
916
926
  this._operations.endByConnectionKey(connected.payload.connectId, "explicit");
@@ -918,23 +928,23 @@ var KeystoneAdapter = class _KeystoneAdapter {
918
928
  searchTargetId,
919
929
  connectId: connected.payload.connectId,
920
930
  device: connected.payload,
931
+ connectionType: selectedConnectionType,
921
932
  connectionKeys: selectedConnectionType === "usb" && record?.usbSessionId ? [record.usbSessionId] : []
922
933
  });
923
934
  this._operationRoutes.set(connected.payload.connectId, {
924
935
  operationId: operation.operationId,
925
936
  connectionType: selectedConnectionType
926
937
  });
927
- return (0, import_hwk_adapter_core3.success)(operation.operationId);
938
+ return (0, import_hwk_adapter_core4.success)(operation.operationId);
928
939
  } catch (err) {
929
940
  return this._errorToFailure(err);
941
+ } finally {
942
+ scope.release();
930
943
  }
931
944
  }
932
945
  /**
933
- * QR has no persistent connection to tear down — the account cache
934
- * survives so a later call resumes without re-syncing. For a USB session,
935
- * this closes the connector session and either demotes the record back to
936
- * QR-only (if it was ever QR-synced) or removes it entirely (pure-USB
937
- * wallet that was never seen over QR) — see §4.2 of the design doc.
946
+ * QR has nothing to tear down. A USB session is closed, and the record is demoted to QR-only or
947
+ * removed if it was never QR-synced.
938
948
  */
939
949
  async releaseOperation(operationId) {
940
950
  const operation = this._operations.find(operationId);
@@ -958,53 +968,55 @@ var KeystoneAdapter = class _KeystoneAdapter {
958
968
  return;
959
969
  }
960
970
  const { usbSessionId } = record;
961
- await this._runUsbTeardown(async () => {
962
- if (this._usbConnector) {
963
- try {
964
- await this._usbConnector.disconnect(usbSessionId);
965
- } catch {
966
- }
967
- }
968
- this._handleUsbDisconnect({ connectId: usbSessionId });
969
- });
971
+ await this._runUsbTeardown(() => this._retireUsbSession(usbSessionId));
970
972
  }
971
973
  getDeviceInfo(connectIdOrOperationId, deviceId) {
972
974
  try {
973
- const connectId = (0, import_hwk_adapter_core3.isHardwareOperationId)(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
975
+ const connectId = (0, import_hwk_adapter_core4.isHardwareOperationId)(connectIdOrOperationId) ? this._operations.resolve(connectIdOrOperationId).connectId : connectIdOrOperationId;
974
976
  const { record } = this._resolveTarget(connectId, deviceId);
975
977
  if (!record) {
976
978
  return Promise.resolve(
977
- (0, import_hwk_adapter_core3.failure)(
978
- import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
979
+ (0, import_hwk_adapter_core4.failure)(
980
+ import_hwk_adapter_core4.HardwareErrorCode.DeviceNotFound,
979
981
  `Unknown Keystone device: ${connectId || deviceId}`
980
982
  )
981
983
  );
982
984
  }
983
- return Promise.resolve((0, import_hwk_adapter_core3.success)(toDeviceInfo(record)));
985
+ return Promise.resolve((0, import_hwk_adapter_core4.success)(toDeviceInfo(record)));
984
986
  } catch (err) {
985
987
  return Promise.resolve(this._errorToFailure(err));
986
988
  }
987
989
  }
988
990
  cancel(connectId) {
989
- const reason = (0, import_hwk_adapter_core3.createHwkError)({
990
- code: import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
991
+ let namedOperationConnectId;
992
+ if ((0, import_hwk_adapter_core4.isHardwareOperationId)(connectId)) {
993
+ try {
994
+ namedOperationConnectId = this._operations.resolve(connectId).connectId;
995
+ } catch {
996
+ return;
997
+ }
998
+ }
999
+ const reason = (0, import_hwk_adapter_core4.createHwkError)({
1000
+ code: import_hwk_adapter_core4.HardwareErrorCode.UserAborted,
991
1001
  message: "User aborted operation"
992
1002
  });
993
- const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
994
1003
  if (connectId) {
995
- if (!activeJobId || activeJobId !== connectId && activeJobId !== this._walletIdFromIdentifier(connectId)) {
996
- this._jobQueue.cancelActiveAndPending(connectId, reason);
997
- const walletId = this._walletIdFromIdentifier(connectId);
998
- if (walletId && walletId !== connectId) {
999
- this._jobQueue.cancelActiveAndPending(walletId, reason);
1000
- }
1001
- return;
1004
+ const queueKeys = keystoneCancelQueueKeys([
1005
+ connectId,
1006
+ this._operationRoutes.get(connectId)?.operationId,
1007
+ namedOperationConnectId
1008
+ ]);
1009
+ for (const key of queueKeys) {
1010
+ this._jobQueue.cancelActiveAndPending(key, reason);
1011
+ }
1012
+ const scopedOperationId = (0, import_hwk_adapter_core4.isHardwareOperationId)(connectId) ? connectId : this._operationRouteForIdentifier(connectId)?.operationId;
1013
+ if (scopedOperationId) {
1014
+ this._uiRegistry.cancel(void 0, void 0, scopedOperationId);
1002
1015
  }
1003
- this._jobQueue.cancelActiveAndPending(activeJobId, reason);
1004
1016
  } else {
1005
1017
  this._jobQueue.cancelActiveAndPending(void 0, reason);
1018
+ this._uiRegistry.cancel();
1006
1019
  }
1007
- this._uiRegistry.cancel();
1008
1020
  }
1009
1021
  getChainFingerprint(connectId, deviceId, chain) {
1010
1022
  try {
@@ -1012,16 +1024,16 @@ var KeystoneAdapter = class _KeystoneAdapter {
1012
1024
  const walletId = target.record?.walletId ?? target.expectedWalletId;
1013
1025
  if (!walletId) {
1014
1026
  return Promise.resolve(
1015
- (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound, "Unknown Keystone wallet identity")
1027
+ (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DeviceNotFound, "Unknown Keystone wallet identity")
1016
1028
  );
1017
1029
  }
1018
- return Promise.resolve((0, import_hwk_adapter_core3.success)((0, import_hwk_adapter_core3.deriveDeviceFingerprint)(`keystone:${chain}:${walletId}`)));
1030
+ return Promise.resolve((0, import_hwk_adapter_core4.success)((0, import_hwk_adapter_core4.deriveDeviceFingerprint)(`keystone:${chain}:${walletId}`)));
1019
1031
  } catch (err) {
1020
1032
  return Promise.resolve(this._errorToFailure(err));
1021
1033
  }
1022
1034
  }
1023
1035
  uiResponse(response) {
1024
- if (response.type === import_hwk_adapter_core3.UI_RESPONSE.CANCEL) {
1036
+ if (response.type === import_hwk_adapter_core4.UI_RESPONSE.CANCEL) {
1025
1037
  this.cancel();
1026
1038
  return;
1027
1039
  }
@@ -1034,30 +1046,30 @@ var KeystoneAdapter = class _KeystoneAdapter {
1034
1046
  this.emitter.off(event, listener);
1035
1047
  }
1036
1048
  // ---------------------------------------------------------------------------
1037
- // Explicit account import — the recommended entry point before signing, so
1038
- // signing calls don't each pay for their own cold-sync round trip.
1049
+ // Explicit account import: call before signing to avoid a cold sync per call.
1039
1050
  // ---------------------------------------------------------------------------
1040
1051
  async importFromQr(options = {}) {
1041
1052
  try {
1042
- return await this._jobQueue.enqueue(COLD_START_JOB_LABEL, async (signal) => {
1053
+ return await this._jobQueue.enqueue(KEYSTONE_COLD_START_JOB_LABEL, async (signal) => {
1043
1054
  const displayDevice = placeholderDeviceInfo();
1044
1055
  let responseUr;
1045
1056
  if (options.mode === "scan") {
1046
- responseUr = await this._requestQrScanAndAwaitResponse(displayDevice);
1057
+ responseUr = await this._requestQrScanAndAwaitResponse(displayDevice, signal);
1047
1058
  } else {
1048
1059
  const requestUr = this.urEngine.buildKeyDerivationRequest({
1049
- schemas: [{ path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" }],
1060
+ schemas: [keySchema("evm", KEYSTONE_WALLET_ID_PATH)],
1050
1061
  origin: this._origin
1051
1062
  });
1052
- responseUr = await this._requestQrDisplayAndAwaitResponse(displayDevice, {
1053
- ...requestUr,
1054
- animated: false
1055
- });
1063
+ responseUr = await this._requestQrDisplayAndAwaitResponse(
1064
+ displayDevice,
1065
+ { ...requestUr, animated: false },
1066
+ signal
1067
+ );
1056
1068
  }
1057
1069
  _KeystoneAdapter._throwIfAborted(signal);
1058
1070
  const parsed = this.urEngine.parseAccountResponse(responseUr);
1059
1071
  const record = this._upsertDeviceRecord(parsed);
1060
- return (0, import_hwk_adapter_core3.success)(toDeviceInfo(record));
1072
+ return (0, import_hwk_adapter_core4.success)(toDeviceInfo(record));
1061
1073
  });
1062
1074
  } catch (err) {
1063
1075
  return this._errorToFailure(err);
@@ -1066,162 +1078,120 @@ var KeystoneAdapter = class _KeystoneAdapter {
1066
1078
  // ---------------------------------------------------------------------------
1067
1079
  // EVM
1068
1080
  // ---------------------------------------------------------------------------
1069
- async evmGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
1070
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1081
+ async evmGetAddress(connectIdArg, deviceIdArg, params, book) {
1082
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1071
1083
  if (!operationTarget.success) return operationTarget;
1072
1084
  const connectId = operationTarget.payload.targetId;
1073
1085
  const deviceId = deviceIdArg ?? void 0;
1074
- const params = paramsArg;
1075
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmGetAddress requires params");
1086
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmGetAddress requires params");
1076
1087
  if (!params.path)
1077
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmGetAddress requires params.path");
1088
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmGetAddress requires params.path");
1078
1089
  const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
1079
1090
  if (!relativeDerivePath) {
1080
- return (0, import_hwk_adapter_core3.failure)(
1081
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1091
+ return (0, import_hwk_adapter_core4.failure)(
1092
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1082
1093
  "evmGetAddress requires a full leaf path, e.g. m/44'/60'/0'/0/0"
1083
1094
  );
1084
1095
  }
1085
- try {
1086
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1087
- const { account } = await this._fetchAccount(
1088
- connectId,
1089
- deviceId,
1090
- "evm",
1091
- accountPath,
1092
- signal,
1093
- { book }
1094
- );
1095
- _KeystoneAdapter._throwIfAborted(signal);
1096
- if (!account.extendedPublicKey) {
1097
- throw (0, import_hwk_adapter_core3.createHwkError)({
1098
- code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1099
- message: "Keystone did not return an extended public key for this account path"
1100
- });
1101
- }
1102
- const address = this.urEngine.deriveEvmAddressFromXpub(
1103
- account.extendedPublicKey,
1104
- relativeDerivePath
1105
- );
1106
- return (0, import_hwk_adapter_core3.success)({ address, path: normalizePath(params.path) });
1107
- });
1108
- } catch (err) {
1109
- return this._errorToFailure(err);
1110
- }
1096
+ return this._runJob(connectId, deviceId, async (signal) => {
1097
+ const xpub = await this._fetchAccountXpub(
1098
+ connectId,
1099
+ deviceId,
1100
+ "evm",
1101
+ accountPath,
1102
+ signal,
1103
+ book
1104
+ );
1105
+ const address = this.urEngine.deriveEvmAddressFromXpub(xpub, relativeDerivePath);
1106
+ return (0, import_hwk_adapter_core4.success)({ address, path: normalizePath(params.path) });
1107
+ });
1111
1108
  }
1112
- async evmSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
1113
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1109
+ async evmSignTransaction(connectIdArg, deviceIdArg, params) {
1110
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1114
1111
  if (!operationTarget.success) return operationTarget;
1115
1112
  const connectId = operationTarget.payload.targetId;
1116
1113
  const deviceId = deviceIdArg ?? void 0;
1117
- const params = paramsArg;
1118
1114
  if (!params)
1119
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params");
1115
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params");
1120
1116
  if (!params.path) {
1121
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params.path");
1117
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignTransaction requires params.path");
1122
1118
  }
1123
1119
  if (!("serializedTx" in params) || typeof params.serializedTx !== "string" || !params.serializedTx) {
1124
- return (0, import_hwk_adapter_core3.failure)(
1125
- import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1120
+ return (0, import_hwk_adapter_core4.failure)(
1121
+ import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
1126
1122
  "Keystone only signs a fully RLP-serialized transaction (params.serializedTx) \u2014 structured-field signing is not supported"
1127
1123
  );
1128
1124
  }
1129
- const rawTxHex = (0, import_hwk_adapter_core3.stripHex)(params.serializedTx);
1125
+ const rawTxHex = (0, import_hwk_adapter_core4.stripHex)(params.serializedTx);
1130
1126
  const path = normalizePath(params.path);
1131
- try {
1132
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1133
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "evm", signal);
1134
- _KeystoneAdapter._throwIfAborted(signal);
1135
- const requestId = (0, import_uuid2.v4)();
1136
- const dataType = parseInt(rawTxHex.slice(0, 2), 16) < 192 ? "typedTransaction" : "transaction";
1137
- const requestUr = this.urEngine.buildEthSignRequest({
1138
- requestId,
1139
- unsignedTxHex: rawTxHex,
1140
- dataType,
1141
- path,
1142
- xfp: record.masterFingerprint,
1143
- chainId: params.chainId
1144
- });
1145
- const responseUr = await this._resolveUr(
1146
- record,
1147
- requestUr,
1148
- true,
1149
- connectId,
1150
- signal,
1151
- "evmSignTransaction"
1152
- );
1153
- _KeystoneAdapter._throwIfAborted(signal);
1154
- const sig = this.urEngine.parseEthSignature(responseUr);
1155
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1156
- return (0, import_hwk_adapter_core3.success)({
1157
- v: (0, import_hwk_adapter_core3.ensure0x)(sig.v),
1158
- r: (0, import_hwk_adapter_core3.ensure0x)(sig.r),
1159
- s: (0, import_hwk_adapter_core3.ensure0x)(sig.s)
1160
- });
1161
- });
1162
- } catch (err) {
1163
- return this._errorToFailure(err);
1164
- }
1127
+ const dataType = parseInt(rawTxHex.slice(0, 2), 16) < 192 ? "typedTransaction" : "transaction";
1128
+ return this._signWithWallet({
1129
+ connectId,
1130
+ deviceId,
1131
+ chain: "evm",
1132
+ operationName: "evmSignTransaction",
1133
+ buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
1134
+ requestId,
1135
+ unsignedTxHex: rawTxHex,
1136
+ dataType,
1137
+ path,
1138
+ xfp,
1139
+ chainId: params.chainId,
1140
+ origin: this._origin
1141
+ }),
1142
+ parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
1143
+ toPayload: (sig) => ({
1144
+ v: (0, import_hwk_adapter_core4.ensure0x)(sig.v),
1145
+ r: (0, import_hwk_adapter_core4.ensure0x)(sig.r),
1146
+ s: (0, import_hwk_adapter_core4.ensure0x)(sig.s)
1147
+ })
1148
+ });
1165
1149
  }
1166
- async evmSignMessage(connectIdArg, deviceIdArg, paramsArg) {
1167
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1150
+ async evmSignMessage(connectIdArg, deviceIdArg, params) {
1151
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1168
1152
  if (!operationTarget.success) return operationTarget;
1169
1153
  const connectId = operationTarget.payload.targetId;
1170
1154
  const deviceId = deviceIdArg ?? void 0;
1171
- const params = paramsArg;
1172
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignMessage requires params");
1155
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignMessage requires params");
1173
1156
  if (!params.path || params.message === void 0) {
1174
- return (0, import_hwk_adapter_core3.failure)(
1175
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1157
+ return (0, import_hwk_adapter_core4.failure)(
1158
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1176
1159
  "evmSignMessage requires params.path and params.message"
1177
1160
  );
1178
1161
  }
1179
- const messageHex = params.hex ? (0, import_hwk_adapter_core3.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
1162
+ const messageHex = params.hex ? (0, import_hwk_adapter_core4.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
1180
1163
  const path = normalizePath(params.path);
1181
- try {
1182
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1183
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "evm", signal);
1184
- _KeystoneAdapter._throwIfAborted(signal);
1185
- const requestId = (0, import_uuid2.v4)();
1186
- const requestUr = this.urEngine.buildEthSignRequest({
1187
- requestId,
1188
- unsignedTxHex: messageHex,
1189
- dataType: "personalMessage",
1190
- path,
1191
- xfp: record.masterFingerprint,
1192
- chainId: params.chainId
1193
- });
1194
- const responseUr = await this._resolveUr(
1195
- record,
1196
- requestUr,
1197
- true,
1198
- connectId,
1199
- signal,
1200
- "evmSignMessage"
1201
- );
1202
- _KeystoneAdapter._throwIfAborted(signal);
1203
- const sig = this.urEngine.parseEthSignature(responseUr);
1204
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1205
- return (0, import_hwk_adapter_core3.success)({ signature: (0, import_hwk_adapter_core3.ensure0x)(sig.r + sig.s + sig.v) });
1206
- });
1207
- } catch (err) {
1208
- return this._errorToFailure(err);
1209
- }
1164
+ return this._signWithWallet({
1165
+ connectId,
1166
+ deviceId,
1167
+ chain: "evm",
1168
+ operationName: "evmSignMessage",
1169
+ buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
1170
+ requestId,
1171
+ unsignedTxHex: messageHex,
1172
+ dataType: "personalMessage",
1173
+ path,
1174
+ xfp,
1175
+ chainId: params.chainId,
1176
+ origin: this._origin
1177
+ }),
1178
+ parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
1179
+ toPayload: (sig) => ({ signature: (0, import_hwk_adapter_core4.ensure0x)(sig.r + sig.s + sig.v) })
1180
+ });
1210
1181
  }
1211
- async evmSignTypedData(connectIdArg, deviceIdArg, paramsArg) {
1212
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1182
+ async evmSignTypedData(connectIdArg, deviceIdArg, params) {
1183
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1213
1184
  if (!operationTarget.success) return operationTarget;
1214
1185
  const connectId = operationTarget.payload.targetId;
1215
1186
  const deviceId = deviceIdArg ?? void 0;
1216
- const params = paramsArg;
1217
1187
  if (!params)
1218
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params");
1188
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params");
1219
1189
  if (!params.path) {
1220
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params.path");
1190
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "evmSignTypedData requires params.path");
1221
1191
  }
1222
1192
  if (params.mode === "hash") {
1223
- return (0, import_hwk_adapter_core3.failure)(
1224
- import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1193
+ return (0, import_hwk_adapter_core4.failure)(
1194
+ import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
1225
1195
  "Keystone always displays the full EIP-712 payload for on-device review \u2014 pre-hashed signing is not supported"
1226
1196
  );
1227
1197
  }
@@ -1230,55 +1200,40 @@ var KeystoneAdapter = class _KeystoneAdapter {
1230
1200
  "utf8"
1231
1201
  ).toString("hex");
1232
1202
  const path = normalizePath(params.path);
1233
- try {
1234
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1235
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "evm", signal);
1236
- _KeystoneAdapter._throwIfAborted(signal);
1237
- const requestId = (0, import_uuid2.v4)();
1238
- const requestUr = this.urEngine.buildEthSignRequest({
1239
- requestId,
1240
- unsignedTxHex: signDataHex,
1241
- dataType: "typedData",
1242
- path,
1243
- xfp: record.masterFingerprint,
1244
- chainId: params.chainId
1245
- });
1246
- const responseUr = await this._resolveUr(
1247
- record,
1248
- requestUr,
1249
- true,
1250
- connectId,
1251
- signal,
1252
- "evmSignTypedData"
1253
- );
1254
- _KeystoneAdapter._throwIfAborted(signal);
1255
- const sig = this.urEngine.parseEthSignature(responseUr);
1256
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1257
- return (0, import_hwk_adapter_core3.success)({ signature: (0, import_hwk_adapter_core3.ensure0x)(sig.r + sig.s + sig.v) });
1258
- });
1259
- } catch (err) {
1260
- return this._errorToFailure(err);
1261
- }
1203
+ return this._signWithWallet({
1204
+ connectId,
1205
+ deviceId,
1206
+ chain: "evm",
1207
+ operationName: "evmSignTypedData",
1208
+ buildRequest: (requestId, xfp) => this.urEngine.buildEthSignRequest({
1209
+ requestId,
1210
+ unsignedTxHex: signDataHex,
1211
+ dataType: "typedData",
1212
+ path,
1213
+ xfp,
1214
+ chainId: params.chainId,
1215
+ origin: this._origin
1216
+ }),
1217
+ parseResponse: (ur) => this.urEngine.parseEthSignature(ur),
1218
+ toPayload: (sig) => ({ signature: (0, import_hwk_adapter_core4.ensure0x)(sig.r + sig.s + sig.v) })
1219
+ });
1262
1220
  }
1263
1221
  // ---------------------------------------------------------------------------
1264
- // BTC — PSBT signing and message signing only for now. Address/pubkey
1265
- // derivation needs script-type-aware xpub decoding (P2WPKH/P2TR/…) this
1266
- // phase doesn't wire in yet; structured-field tx signing needs host-side
1267
- // PSBT construction. Both are real, bounded follow-ups, not silent gaps.
1222
+ // BTC: 44'/49'/84' addresses derive offline from the account xpub; P2TR addresses
1223
+ // and structured-field tx signing are unsupported (the host builds a PSBT instead).
1268
1224
  // ---------------------------------------------------------------------------
1269
- async btcGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
1270
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1225
+ async btcGetAddress(connectIdArg, deviceIdArg, params, book) {
1226
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1271
1227
  if (!operationTarget.success) return operationTarget;
1272
1228
  const connectId = operationTarget.payload.targetId;
1273
1229
  const deviceId = deviceIdArg ?? void 0;
1274
- const params = paramsArg;
1275
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetAddress requires params");
1230
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetAddress requires params");
1276
1231
  if (!params.path)
1277
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetAddress requires params.path");
1232
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetAddress requires params.path");
1278
1233
  const scriptType = btcScriptTypeFromPath(params.path);
1279
1234
  if (!scriptType) {
1280
- return (0, import_hwk_adapter_core3.failure)(
1281
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1235
+ return (0, import_hwk_adapter_core4.failure)(
1236
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1282
1237
  "btcGetAddress requires a path whose purpose is 44'/49'/84'/86' (P2PKH/P2SH-P2WPKH/P2WPKH/P2TR)"
1283
1238
  );
1284
1239
  }
@@ -1291,100 +1246,68 @@ var KeystoneAdapter = class _KeystoneAdapter {
1291
1246
  const normalizedPath = normalizePath(params.path);
1292
1247
  const { accountPath, relativeDerivePath } = splitAccountPath(normalizedPath);
1293
1248
  if (!relativeDerivePath) {
1294
- return (0, import_hwk_adapter_core3.failure)(
1295
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1249
+ return (0, import_hwk_adapter_core4.failure)(
1250
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1296
1251
  "btcGetAddress requires a full leaf path, e.g. m/84'/0'/0'/0/0"
1297
1252
  );
1298
1253
  }
1299
1254
  if (!isKeystoneSignableBtcAccountPath(accountPath)) {
1300
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1301
- }
1302
- try {
1303
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1304
- const { account } = await this._fetchAccount(
1305
- connectId,
1306
- deviceId,
1307
- "btc",
1308
- accountPath,
1309
- signal,
1310
- { book }
1311
- );
1312
- _KeystoneAdapter._throwIfAborted(signal);
1313
- if (!account.extendedPublicKey) {
1314
- throw (0, import_hwk_adapter_core3.createHwkError)({
1315
- code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1316
- message: "Keystone did not return an extended public key for this account path"
1317
- });
1318
- }
1319
- const address = this.urEngine.deriveBtcAddressFromXpub(
1320
- account.extendedPublicKey,
1321
- relativeDerivePath,
1322
- scriptType
1323
- );
1324
- return (0, import_hwk_adapter_core3.success)({ address, path: normalizedPath });
1325
- });
1326
- } catch (err) {
1327
- return this._errorToFailure(err);
1328
- }
1255
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1256
+ }
1257
+ return this._runJob(connectId, deviceId, async (signal) => {
1258
+ const xpub = await this._fetchAccountXpub(
1259
+ connectId,
1260
+ deviceId,
1261
+ "btc",
1262
+ accountPath,
1263
+ signal,
1264
+ book
1265
+ );
1266
+ const address = this.urEngine.deriveBtcAddressFromXpub(xpub, relativeDerivePath, scriptType);
1267
+ return (0, import_hwk_adapter_core4.success)({ address, path: normalizedPath });
1268
+ });
1329
1269
  }
1330
1270
  /**
1331
- * Returns the account-level extended public key. `params.path` must be an
1332
- * ACCOUNT path (`m/84'/0'/0'`), not a leaf — that is the level Keystone
1333
- * actually syncs, and it is what a host needs to derive a whole account's
1334
- * addresses offline. Unlike `btcGetAddress` this has no script-type
1335
- * restriction: an xpub is script-type agnostic, so `86'` (taproot) works
1336
- * here even though deriving a taproot ADDRESS still needs an EC library
1337
- * this package doesn't wire in.
1271
+ * `params.path` must be an account path (`m/84'/0'/0'`), the level Keystone syncs. An xpub is
1272
+ * script-type agnostic, so `86'` works here even though `btcGetAddress` rejects it.
1338
1273
  */
1339
- async btcGetPublicKey(connectIdArg, deviceIdArg, paramsArg, book) {
1340
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1274
+ async btcGetPublicKey(connectIdArg, deviceIdArg, params, book) {
1275
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1341
1276
  if (!operationTarget.success) return operationTarget;
1342
1277
  const connectId = operationTarget.payload.targetId;
1343
1278
  const deviceId = deviceIdArg ?? void 0;
1344
- const params = paramsArg;
1345
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params");
1279
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params");
1346
1280
  if (!params.path)
1347
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params.path");
1281
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcGetPublicKey requires params.path");
1348
1282
  const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
1349
1283
  if (relativeDerivePath) {
1350
- return (0, import_hwk_adapter_core3.failure)(
1351
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1284
+ return (0, import_hwk_adapter_core4.failure)(
1285
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1352
1286
  "btcGetPublicKey requires an account path, e.g. m/84'/0'/0' (not a leaf path)"
1353
1287
  );
1354
1288
  }
1355
1289
  if (!isKeystoneSignableBtcAccountPath(accountPath)) {
1356
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1357
- }
1358
- try {
1359
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1360
- const { account } = await this._fetchAccount(
1361
- connectId,
1362
- deviceId,
1363
- "btc",
1364
- accountPath,
1365
- signal,
1366
- { book }
1367
- );
1368
- _KeystoneAdapter._throwIfAborted(signal);
1369
- if (!account.extendedPublicKey) {
1370
- throw (0, import_hwk_adapter_core3.createHwkError)({
1371
- code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1372
- message: "Keystone did not return an extended public key for this account path"
1373
- });
1374
- }
1375
- const meta = this.urEngine.parseXpubMeta(account.extendedPublicKey);
1376
- return (0, import_hwk_adapter_core3.success)({
1377
- xpub: account.extendedPublicKey,
1378
- publicKey: meta.publicKey,
1379
- chainCode: meta.chainCode,
1380
- depth: meta.depth,
1381
- fingerprint: meta.parentFingerprint,
1382
- path: normalizePath(accountPath)
1383
- });
1290
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1291
+ }
1292
+ return this._runJob(connectId, deviceId, async (signal) => {
1293
+ const xpub = await this._fetchAccountXpub(
1294
+ connectId,
1295
+ deviceId,
1296
+ "btc",
1297
+ accountPath,
1298
+ signal,
1299
+ book
1300
+ );
1301
+ const meta = this.urEngine.parseXpubMeta(xpub);
1302
+ return (0, import_hwk_adapter_core4.success)({
1303
+ xpub,
1304
+ publicKey: meta.publicKey,
1305
+ chainCode: meta.chainCode,
1306
+ depth: meta.depth,
1307
+ fingerprint: meta.parentFingerprint,
1308
+ path: normalizePath(accountPath)
1384
1309
  });
1385
- } catch (err) {
1386
- return this._errorToFailure(err);
1387
- }
1310
+ });
1388
1311
  }
1389
1312
  async btcSignTransaction(_connectId, _deviceId, _params) {
1390
1313
  return this._unsupported(
@@ -1392,372 +1315,319 @@ var KeystoneAdapter = class _KeystoneAdapter {
1392
1315
  "use btcSignPsbt \u2014 structured input/output signing needs host-side PSBT construction, not yet wired in"
1393
1316
  );
1394
1317
  }
1395
- async btcSignPsbt(connectIdArg, deviceIdArg, paramsArg) {
1396
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1318
+ async btcSignPsbt(connectIdArg, deviceIdArg, params) {
1319
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1397
1320
  if (!operationTarget.success) return operationTarget;
1398
1321
  const connectId = operationTarget.payload.targetId;
1399
1322
  const deviceId = deviceIdArg ?? void 0;
1400
- const params = paramsArg;
1401
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params");
1323
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params");
1402
1324
  if (!params.psbt)
1403
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.psbt");
1325
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.psbt");
1404
1326
  if (!params.path)
1405
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.path");
1327
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcSignPsbt requires params.path");
1406
1328
  if (!isKeystoneSignableBtcAccountPath(splitAccountPath(params.path).accountPath)) {
1407
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1408
- }
1409
- try {
1410
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1411
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
1412
- _KeystoneAdapter._throwIfAborted(signal);
1413
- const requestUr = this.urEngine.buildBtcPsbtRequest((0, import_hwk_adapter_core3.stripHex)(params.psbt));
1414
- const responseUr = await this._resolveUr(
1415
- record,
1416
- requestUr,
1417
- true,
1418
- connectId,
1419
- signal,
1420
- "btcSignPsbt"
1421
- );
1422
- _KeystoneAdapter._throwIfAborted(signal);
1423
- const signedPsbt = this.urEngine.parseBtcPsbt(responseUr);
1424
- return (0, import_hwk_adapter_core3.success)({ signedPsbt });
1425
- });
1426
- } catch (err) {
1427
- return this._errorToFailure(err);
1329
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1428
1330
  }
1331
+ return this._runJob(connectId, deviceId, async (signal) => {
1332
+ const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
1333
+ _KeystoneAdapter._throwIfAborted(signal);
1334
+ const requestUr = this.urEngine.buildBtcPsbtRequest((0, import_hwk_adapter_core4.stripHex)(params.psbt));
1335
+ const responseUr = await this._resolveUr(
1336
+ record,
1337
+ requestUr,
1338
+ true,
1339
+ connectId,
1340
+ signal,
1341
+ "btcSignPsbt"
1342
+ );
1343
+ _KeystoneAdapter._throwIfAborted(signal);
1344
+ const signedPsbt = this.urEngine.parseBtcPsbt(responseUr);
1345
+ return (0, import_hwk_adapter_core4.success)({ signedPsbt });
1346
+ });
1429
1347
  }
1430
- async btcSignMessage(connectIdArg, deviceIdArg, paramsArg) {
1431
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1348
+ async btcSignMessage(connectIdArg, deviceIdArg, params) {
1349
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1432
1350
  if (!operationTarget.success) return operationTarget;
1433
1351
  const connectId = operationTarget.payload.targetId;
1434
1352
  const deviceId = deviceIdArg ?? void 0;
1435
- const params = paramsArg;
1436
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "btcSignMessage requires params");
1353
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "btcSignMessage requires params");
1437
1354
  if (!params.path || params.message === void 0) {
1438
- return (0, import_hwk_adapter_core3.failure)(
1439
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1355
+ return (0, import_hwk_adapter_core4.failure)(
1356
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1440
1357
  "btcSignMessage requires params.path and params.message"
1441
1358
  );
1442
1359
  }
1443
- const messageHex = params.hex ? (0, import_hwk_adapter_core3.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
1360
+ const messageHex = params.hex ? (0, import_hwk_adapter_core4.stripHex)(params.message) : Buffer.from(params.message, "utf8").toString("hex");
1444
1361
  const path = normalizePath(params.path);
1445
1362
  if (!isKeystoneSignableBtcAccountPath(splitAccountPath(path).accountPath)) {
1446
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1447
- }
1448
- try {
1449
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1450
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
1451
- _KeystoneAdapter._throwIfAborted(signal);
1452
- const requestId = (0, import_uuid2.v4)();
1453
- const requestUr = this.urEngine.buildBtcMessageSignRequest({
1454
- requestId,
1455
- messageHex,
1456
- accounts: [{ path, xfp: record.masterFingerprint }]
1457
- });
1458
- const responseUr = await this._resolveUr(
1459
- record,
1460
- requestUr,
1461
- true,
1462
- connectId,
1463
- signal,
1464
- "btcSignMessage"
1465
- );
1466
- _KeystoneAdapter._throwIfAborted(signal);
1467
- const sig = this.urEngine.parseBtcSignature(responseUr);
1468
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1469
- return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
1470
- });
1471
- } catch (err) {
1472
- return this._errorToFailure(err);
1473
- }
1363
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.DevicePathForbidden, KEYSTONE_BTC_ACCOUNT_FORBIDDEN_MESSAGE);
1364
+ }
1365
+ return this._signWithWallet({
1366
+ connectId,
1367
+ deviceId,
1368
+ chain: "btc",
1369
+ operationName: "btcSignMessage",
1370
+ buildRequest: (requestId, xfp) => this.urEngine.buildBtcMessageSignRequest({
1371
+ requestId,
1372
+ messageHex,
1373
+ accounts: [{ path, xfp }],
1374
+ origin: this._origin
1375
+ }),
1376
+ parseResponse: (ur) => this.urEngine.parseBtcSignature(ur),
1377
+ toPayload: (sig) => ({ signature: sig.signature })
1378
+ });
1474
1379
  }
1475
1380
  async btcGetMasterFingerprint(connectIdArg, deviceIdArg, paramsArg) {
1476
1381
  const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1477
1382
  if (!operationTarget.success) return operationTarget;
1478
1383
  const connectId = operationTarget.payload.targetId;
1479
1384
  const deviceId = deviceIdArg ?? void 0;
1480
- try {
1481
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1482
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
1483
- return (0, import_hwk_adapter_core3.success)({ masterFingerprint: record.masterFingerprint });
1484
- });
1485
- } catch (err) {
1486
- return this._errorToFailure(err);
1487
- }
1385
+ return this._runJob(connectId, deviceId, async (signal) => {
1386
+ const { record } = await this._ensureWalletKnown(connectId, deviceId, "btc", signal);
1387
+ return (0, import_hwk_adapter_core4.success)({ masterFingerprint: record.masterFingerprint });
1388
+ });
1488
1389
  }
1489
1390
  // ---------------------------------------------------------------------------
1490
1391
  // SOL
1491
1392
  // ---------------------------------------------------------------------------
1492
- async solGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
1493
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1393
+ async solGetAddress(connectIdArg, deviceIdArg, params, book) {
1394
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1494
1395
  if (!operationTarget.success) return operationTarget;
1495
1396
  const connectId = operationTarget.payload.targetId;
1496
1397
  const deviceId = deviceIdArg ?? void 0;
1497
- const params = paramsArg;
1498
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solGetAddress requires params");
1398
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solGetAddress requires params");
1499
1399
  if (!params.path)
1500
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solGetAddress requires params.path");
1400
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solGetAddress requires params.path");
1501
1401
  const path = normalizePath(params.path);
1502
- try {
1503
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1504
- const { account } = await this._fetchAccount(connectId, deviceId, "sol", path, signal, {
1505
- book
1506
- });
1507
- _KeystoneAdapter._throwIfAborted(signal);
1508
- const address = import_bs58.default.encode(Buffer.from(account.publicKey, "hex"));
1509
- return (0, import_hwk_adapter_core3.success)({ address, path });
1510
- });
1511
- } catch (err) {
1512
- return this._errorToFailure(err);
1513
- }
1402
+ return this._runJob(connectId, deviceId, async (signal) => {
1403
+ const { account } = await this._fetchAccount(connectId, deviceId, "sol", path, signal, book);
1404
+ _KeystoneAdapter._throwIfAborted(signal);
1405
+ const address = import_bs58.default.encode(Buffer.from(account.publicKey, "hex"));
1406
+ return (0, import_hwk_adapter_core4.success)({ address, path });
1407
+ });
1514
1408
  }
1515
- async solSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
1516
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1409
+ async solSignTransaction(connectIdArg, deviceIdArg, params) {
1410
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1517
1411
  if (!operationTarget.success) return operationTarget;
1518
1412
  const connectId = operationTarget.payload.targetId;
1519
1413
  const deviceId = deviceIdArg ?? void 0;
1520
- const params = paramsArg;
1521
1414
  if (!params)
1522
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solSignTransaction requires params");
1415
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solSignTransaction requires params");
1523
1416
  if (!params.path || !params.serializedTx) {
1524
- return (0, import_hwk_adapter_core3.failure)(
1525
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1417
+ return (0, import_hwk_adapter_core4.failure)(
1418
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1526
1419
  "solSignTransaction requires params.path and params.serializedTx"
1527
1420
  );
1528
1421
  }
1529
1422
  const path = normalizePath(params.path);
1530
- try {
1531
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1532
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "sol", signal);
1533
- _KeystoneAdapter._throwIfAborted(signal);
1534
- const requestId = (0, import_uuid2.v4)();
1535
- const requestUr = this.urEngine.buildSolSignRequest({
1536
- requestId,
1537
- unsignedPayloadHex: (0, import_hwk_adapter_core3.stripHex)(params.serializedTx),
1538
- dataType: "transaction",
1539
- path,
1540
- xfp: record.masterFingerprint
1541
- });
1542
- const responseUr = await this._resolveUr(
1543
- record,
1544
- requestUr,
1545
- true,
1546
- connectId,
1547
- signal,
1548
- "solSignTransaction"
1549
- );
1550
- _KeystoneAdapter._throwIfAborted(signal);
1551
- const sig = this.urEngine.parseSolSignature(responseUr);
1552
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1553
- return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
1554
- });
1555
- } catch (err) {
1556
- return this._errorToFailure(err);
1557
- }
1423
+ return this._signWithWallet({
1424
+ connectId,
1425
+ deviceId,
1426
+ chain: "sol",
1427
+ operationName: "solSignTransaction",
1428
+ buildRequest: (requestId, xfp) => this.urEngine.buildSolSignRequest({
1429
+ requestId,
1430
+ unsignedPayloadHex: (0, import_hwk_adapter_core4.stripHex)(params.serializedTx),
1431
+ dataType: "transaction",
1432
+ path,
1433
+ xfp,
1434
+ origin: this._origin
1435
+ }),
1436
+ parseResponse: (ur) => this.urEngine.parseSolSignature(ur),
1437
+ toPayload: (sig) => ({ signature: sig.signature })
1438
+ });
1558
1439
  }
1559
- async solSignMessage(connectIdArg, deviceIdArg, paramsArg) {
1560
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1440
+ async solSignMessage(connectIdArg, deviceIdArg, params) {
1441
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1561
1442
  if (!operationTarget.success) return operationTarget;
1562
1443
  const connectId = operationTarget.payload.targetId;
1563
1444
  const deviceId = deviceIdArg ?? void 0;
1564
- const params = paramsArg;
1565
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "solSignMessage requires params");
1445
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "solSignMessage requires params");
1566
1446
  if (!params.path || !params.message) {
1567
- return (0, import_hwk_adapter_core3.failure)(
1568
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1447
+ return (0, import_hwk_adapter_core4.failure)(
1448
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1569
1449
  "solSignMessage requires params.path and params.message"
1570
1450
  );
1571
1451
  }
1572
1452
  const path = normalizePath(params.path);
1573
- try {
1574
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1575
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "sol", signal);
1576
- _KeystoneAdapter._throwIfAborted(signal);
1577
- const requestId = (0, import_uuid2.v4)();
1578
- const requestUr = this.urEngine.buildSolSignRequest({
1579
- requestId,
1580
- unsignedPayloadHex: (0, import_hwk_adapter_core3.stripHex)(params.message),
1581
- dataType: "message",
1582
- path,
1583
- xfp: record.masterFingerprint
1584
- });
1585
- const responseUr = await this._resolveUr(
1586
- record,
1587
- requestUr,
1588
- true,
1589
- connectId,
1590
- signal,
1591
- "solSignMessage"
1592
- );
1593
- _KeystoneAdapter._throwIfAborted(signal);
1594
- const sig = this.urEngine.parseSolSignature(responseUr);
1595
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1596
- return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
1597
- });
1598
- } catch (err) {
1599
- return this._errorToFailure(err);
1600
- }
1453
+ return this._signWithWallet({
1454
+ connectId,
1455
+ deviceId,
1456
+ chain: "sol",
1457
+ operationName: "solSignMessage",
1458
+ buildRequest: (requestId, xfp) => this.urEngine.buildSolSignRequest({
1459
+ requestId,
1460
+ unsignedPayloadHex: (0, import_hwk_adapter_core4.stripHex)(params.message),
1461
+ dataType: "message",
1462
+ path,
1463
+ xfp,
1464
+ origin: this._origin
1465
+ }),
1466
+ parseResponse: (ur) => this.urEngine.parseSolSignature(ur),
1467
+ toPayload: (sig) => ({ signature: sig.signature })
1468
+ });
1601
1469
  }
1602
1470
  // ---------------------------------------------------------------------------
1603
- // TRON — routed through `TronSignRequest`/`TronSignature` (see
1604
- // urEngine/TronSignRequest.ts), whose key layout follows keystone-sdk-rust
1605
- // (tags 5201/5202, firmware >= 2.5.0). The OneKey Pro air-gap encoding under
1606
- // the same tag is a different layout the firmware rejects. Not keystone-sdk's
1607
- // bundled `sdk.tron` module either (protobuf-based, unverified response
1608
- // semantics).
1471
+ // TRON: keystone-sdk-rust layout (tags 5201/5202, firmware >= 2.5.0), not sdk.tron's
1472
+ // protobuf module; OneKey Pro's encoding under the same tag is rejected by the firmware.
1609
1473
  // ---------------------------------------------------------------------------
1610
- async tronGetAddress(connectIdArg, deviceIdArg, paramsArg, book) {
1611
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1474
+ async tronGetAddress(connectIdArg, deviceIdArg, params, book) {
1475
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1612
1476
  if (!operationTarget.success) return operationTarget;
1613
1477
  const connectId = operationTarget.payload.targetId;
1614
1478
  const deviceId = deviceIdArg ?? void 0;
1615
- const params = paramsArg;
1616
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronGetAddress requires params");
1479
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronGetAddress requires params");
1617
1480
  if (!params.path)
1618
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronGetAddress requires params.path");
1481
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronGetAddress requires params.path");
1619
1482
  const { accountPath, relativeDerivePath } = splitAccountPath(params.path);
1620
1483
  if (!relativeDerivePath) {
1621
- return (0, import_hwk_adapter_core3.failure)(
1622
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1484
+ return (0, import_hwk_adapter_core4.failure)(
1485
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1623
1486
  "tronGetAddress requires a full leaf path, e.g. m/44'/195'/0'/0/0"
1624
1487
  );
1625
1488
  }
1626
- try {
1627
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1628
- const { account } = await this._fetchAccount(
1629
- connectId,
1630
- deviceId,
1631
- "tron",
1632
- accountPath,
1633
- signal,
1634
- { book }
1635
- );
1636
- _KeystoneAdapter._throwIfAborted(signal);
1637
- if (!account.extendedPublicKey) {
1638
- throw (0, import_hwk_adapter_core3.createHwkError)({
1639
- code: import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1640
- message: "Keystone did not return an extended public key for this account path"
1641
- });
1642
- }
1643
- const address = this.urEngine.deriveTronAddressFromXpub(
1644
- account.extendedPublicKey,
1645
- relativeDerivePath
1646
- );
1647
- return (0, import_hwk_adapter_core3.success)({ address, path: normalizePath(params.path) });
1648
- });
1649
- } catch (err) {
1650
- return this._errorToFailure(err);
1651
- }
1489
+ return this._runJob(connectId, deviceId, async (signal) => {
1490
+ const xpub = await this._fetchAccountXpub(
1491
+ connectId,
1492
+ deviceId,
1493
+ "tron",
1494
+ accountPath,
1495
+ signal,
1496
+ book
1497
+ );
1498
+ const address = this.urEngine.deriveTronAddressFromXpub(xpub, relativeDerivePath);
1499
+ return (0, import_hwk_adapter_core4.success)({ address, path: normalizePath(params.path) });
1500
+ });
1652
1501
  }
1653
- async tronSignTransaction(connectIdArg, deviceIdArg, paramsArg) {
1654
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1502
+ async tronSignTransaction(connectIdArg, deviceIdArg, params) {
1503
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1655
1504
  if (!operationTarget.success) return operationTarget;
1656
1505
  const connectId = operationTarget.payload.targetId;
1657
1506
  const deviceId = deviceIdArg ?? void 0;
1658
- const params = paramsArg;
1659
1507
  if (!params)
1660
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params");
1508
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params");
1661
1509
  if (!params.path) {
1662
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params.path");
1510
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignTransaction requires params.path");
1663
1511
  }
1664
1512
  if (!params.rawTxHex) {
1665
- return (0, import_hwk_adapter_core3.failure)(
1666
- import_hwk_adapter_core3.HardwareErrorCode.InvalidParams,
1513
+ return (0, import_hwk_adapter_core4.failure)(
1514
+ import_hwk_adapter_core4.HardwareErrorCode.InvalidParams,
1667
1515
  "Keystone only signs a fully protobuf-serialized TRON transaction (params.rawTxHex) \u2014 the Trezor-style structured contract fields have no equivalent here"
1668
1516
  );
1669
1517
  }
1670
- const rawTxHex = (0, import_hwk_adapter_core3.stripHex)(params.rawTxHex);
1518
+ const rawTxHex = (0, import_hwk_adapter_core4.stripHex)(params.rawTxHex);
1671
1519
  const path = normalizePath(params.path);
1672
- try {
1673
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1674
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "tron", signal);
1675
- _KeystoneAdapter._throwIfAborted(signal);
1676
- const requestId = (0, import_uuid2.v4)();
1677
- const requestUr = this.urEngine.buildTronSignRequest({
1678
- requestId,
1679
- rawTxHex,
1680
- path,
1681
- xfp: record.masterFingerprint
1682
- });
1683
- const responseUr = await this._resolveUr(
1684
- record,
1685
- requestUr,
1686
- true,
1687
- connectId,
1688
- signal,
1689
- "tronSignTransaction"
1690
- );
1691
- _KeystoneAdapter._throwIfAborted(signal);
1692
- const sig = this.urEngine.parseTronSignature(responseUr);
1693
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1694
- return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
1695
- });
1696
- } catch (err) {
1697
- return this._errorToFailure(err);
1698
- }
1520
+ return this._signWithWallet({
1521
+ connectId,
1522
+ deviceId,
1523
+ chain: "tron",
1524
+ operationName: "tronSignTransaction",
1525
+ buildRequest: (requestId, xfp) => this.urEngine.buildTronSignRequest({
1526
+ requestId,
1527
+ rawTxHex,
1528
+ path,
1529
+ xfp,
1530
+ origin: this._origin
1531
+ }),
1532
+ parseResponse: (ur) => this.urEngine.parseTronSignature(ur),
1533
+ // Keystone signs rawTxHex as given and returns only the signature, so no serializedTx.
1534
+ toPayload: (sig) => ({ signature: sig.signature })
1535
+ });
1699
1536
  }
1700
- async tronSignMessage(connectIdArg, deviceIdArg, paramsArg) {
1701
- const operationTarget = resolveHardwareOperationTarget(connectIdArg, paramsArg?.operationId);
1537
+ async tronSignMessage(connectIdArg, deviceIdArg, params) {
1538
+ const operationTarget = resolveHardwareOperationTarget(connectIdArg, params?.operationId);
1702
1539
  if (!operationTarget.success) return operationTarget;
1703
1540
  const connectId = operationTarget.payload.targetId;
1704
1541
  const deviceId = deviceIdArg ?? void 0;
1705
- const params = paramsArg;
1706
- if (!params) return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignMessage requires params");
1542
+ if (!params) return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignMessage requires params");
1707
1543
  if (!params.path) {
1708
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignMessage requires params.path");
1544
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignMessage requires params.path");
1709
1545
  }
1710
1546
  if (!params.messageHex) {
1711
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.InvalidParams, "tronSignMessage requires params.messageHex");
1547
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.InvalidParams, "tronSignMessage requires params.messageHex");
1712
1548
  }
1713
1549
  if (params.messageType !== "V2") {
1714
- return (0, import_hwk_adapter_core3.failure)(
1715
- import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
1550
+ return (0, import_hwk_adapter_core4.failure)(
1551
+ import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
1716
1552
  'Keystone signs TRON messages as TIP-191 signMessageV2 only (messageType: "V2")'
1717
1553
  );
1718
1554
  }
1719
- const messageHex = (0, import_hwk_adapter_core3.stripHex)(params.messageHex);
1555
+ const messageHex = (0, import_hwk_adapter_core4.stripHex)(params.messageHex);
1720
1556
  const path = normalizePath(params.path);
1557
+ return this._signWithWallet({
1558
+ connectId,
1559
+ deviceId,
1560
+ chain: "tron",
1561
+ operationName: "tronSignMessage",
1562
+ buildRequest: (requestId, xfp) => this.urEngine.buildTronSignRequest({
1563
+ requestId,
1564
+ rawTxHex: messageHex,
1565
+ path,
1566
+ xfp,
1567
+ signType: 2 /* PersonalMessage */,
1568
+ origin: this._origin
1569
+ }),
1570
+ parseResponse: (ur) => this.urEngine.parseTronSignature(ur),
1571
+ toPayload: (sig) => ({ signature: sig.signature })
1572
+ });
1573
+ }
1574
+ // ---------------------------------------------------------------------------
1575
+ // Internals
1576
+ // ---------------------------------------------------------------------------
1577
+ async _runJob(connectId, deviceId, job) {
1721
1578
  try {
1722
- return await this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1723
- const { record } = await this._ensureWalletKnown(connectId, deviceId, "tron", signal);
1724
- _KeystoneAdapter._throwIfAborted(signal);
1725
- const requestId = (0, import_uuid2.v4)();
1726
- const requestUr = this.urEngine.buildTronSignRequest({
1727
- requestId,
1728
- rawTxHex: messageHex,
1729
- path,
1730
- xfp: record.masterFingerprint,
1731
- signType: 2 /* PersonalMessage */
1732
- });
1733
- const responseUr = await this._resolveUr(
1734
- record,
1735
- requestUr,
1736
- true,
1737
- connectId,
1738
- signal,
1739
- "tronSignMessage"
1740
- );
1741
- _KeystoneAdapter._throwIfAborted(signal);
1742
- const sig = this.urEngine.parseTronSignature(responseUr);
1743
- _KeystoneAdapter._assertRequestIdMatches(requestId, sig.requestId);
1744
- return (0, import_hwk_adapter_core3.success)({ signature: sig.signature });
1745
- });
1579
+ return await this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId), job);
1746
1580
  } catch (err) {
1747
1581
  return this._errorToFailure(err);
1748
1582
  }
1749
1583
  }
1750
- // ---------------------------------------------------------------------------
1751
- // Internals
1752
- // ---------------------------------------------------------------------------
1753
- _walletIdFromIdentifier(identifier) {
1754
- const value = identifier.startsWith(KEYSTONE_WALLET_CONNECT_ID_PREFIX) ? identifier.slice(KEYSTONE_WALLET_CONNECT_ID_PREFIX.length) : identifier;
1755
- return /^[0-9a-f]{64}$/i.test(value) ? value.toLowerCase() : void 0;
1584
+ async _fetchAccountXpub(connectId, deviceId, hwkChain, accountPath, signal, book) {
1585
+ const { account } = await this._fetchAccount(
1586
+ connectId,
1587
+ deviceId,
1588
+ hwkChain,
1589
+ accountPath,
1590
+ signal,
1591
+ book
1592
+ );
1593
+ _KeystoneAdapter._throwIfAborted(signal);
1594
+ if (!account.extendedPublicKey) {
1595
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1596
+ code: import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
1597
+ message: "Keystone did not return an extended public key for this account path"
1598
+ });
1599
+ }
1600
+ return account.extendedPublicKey;
1601
+ }
1602
+ /**
1603
+ * Signing needs only the wallet's mfp: the device re-derives the key from path+xfp, so no
1604
+ * per-path account lookup happens here.
1605
+ */
1606
+ _signWithWallet(args) {
1607
+ const { connectId, deviceId } = args;
1608
+ return this._runJob(connectId, deviceId, async (signal) => {
1609
+ const { record } = await this._ensureWalletKnown(connectId, deviceId, args.chain, signal);
1610
+ _KeystoneAdapter._throwIfAborted(signal);
1611
+ const requestId = (0, import_uuid2.v4)();
1612
+ const requestUr = args.buildRequest(requestId, record.masterFingerprint);
1613
+ const responseUr = await this._resolveUr(
1614
+ record,
1615
+ requestUr,
1616
+ true,
1617
+ connectId,
1618
+ signal,
1619
+ args.operationName
1620
+ );
1621
+ _KeystoneAdapter._throwIfAborted(signal);
1622
+ const signature = args.parseResponse(responseUr);
1623
+ _KeystoneAdapter._assertRequestIdMatches(requestId, signature.requestId);
1624
+ return (0, import_hwk_adapter_core4.success)(args.toPayload(signature));
1625
+ });
1756
1626
  }
1757
1627
  _allNetworkSyncSchema(item) {
1758
- if (!(0, import_hwk_adapter_core3.isAllNetworkMethodName)(String(item.methodName)) || !item.path) return void 0;
1628
+ if (!(0, import_hwk_adapter_core4.isAllNetworkMethodName)(String(item.methodName)) || !item.path) return void 0;
1759
1629
  const method = item.methodName;
1760
- const hwkChain = (0, import_hwk_adapter_core3.getAllNetworkMethodChain)(method);
1630
+ const hwkChain = (0, import_hwk_adapter_core4.getAllNetworkMethodChain)(method);
1761
1631
  const path = normalizePath(item.path);
1762
1632
  if (hwkChain === "btc" && !isKeystoneSignableBtcAccountPath(splitAccountPath(path).accountPath)) {
1763
1633
  return void 0;
@@ -1775,16 +1645,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
1775
1645
  }
1776
1646
  }
1777
1647
  /**
1778
- * QR is an interactive batch transport: one account-creation action must
1779
- * produce one request QR and one response scan, regardless of how many
1780
- * chains or derivation paths the host bundled. USB keeps its proven
1781
- * one-path-at-a-time export flow because the device limits that channel.
1648
+ * QR answers the whole bundle with one request QR and one scan. USB exports one path per
1649
+ * request because the device limits that channel.
1782
1650
  */
1783
1651
  async _prefetchAllNetworkAccounts(connectId, deviceId, params) {
1784
- return this._jobQueue.enqueue(getKeystoneJobId(connectId, deviceId), async (signal) => {
1652
+ return this._jobQueue.enqueue(keystoneQueueKey(connectId, deviceId), async (signal) => {
1785
1653
  const target = this._resolveTarget(connectId, deviceId);
1786
- const { record: targetRecord } = target;
1787
- let record = targetRecord;
1654
+ let { record } = target;
1788
1655
  const requestedByKey = /* @__PURE__ */ new Map();
1789
1656
  for (const item of params.bundle) {
1790
1657
  const schema = this._allNetworkSyncSchema(item);
@@ -1794,11 +1661,23 @@ var KeystoneAdapter = class _KeystoneAdapter {
1794
1661
  if (!requestedByKey.size) {
1795
1662
  return { book };
1796
1663
  }
1797
- const operation = (0, import_hwk_adapter_core3.isHardwareOperationId)(params.operationId) ? this._operations.find(params.operationId) : void 0;
1664
+ const requestedSchemas = Array.from(requestedByKey.values());
1665
+ const assertAllBooked = () => {
1666
+ for (const schema of requestedSchemas) {
1667
+ if (!book.has(accountKey(schema.hwkChain, schema.path))) {
1668
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1669
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
1670
+ message: `Keystone did not return the requested derivation path (${schema.path})`
1671
+ });
1672
+ }
1673
+ }
1674
+ };
1675
+ const operation = (0, import_hwk_adapter_core4.isHardwareOperationId)(params.operationId) ? this._operations.find(params.operationId) : void 0;
1798
1676
  const operationRoute = operation ? this._operationRoutes.get(operation.connectId) : void 0;
1799
1677
  const routedToQr = operationRoute?.operationId === operation?.operationId && operationRoute?.connectionType === "qr";
1800
1678
  if (this._forcedTransport !== "qr" && !routedToQr && !record?.usbSessionId && this._usbConnector && target.expectedWalletId) {
1801
1679
  record = await this._tryUsbAttach(
1680
+ signal,
1802
1681
  target.expectedWalletId,
1803
1682
  record?.masterFingerprint ?? target.expectedMasterFingerprint
1804
1683
  ) ?? record;
@@ -1808,14 +1687,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
1808
1687
  const bundleSessionId = record.usbSessionId;
1809
1688
  const assertBundleSession = () => {
1810
1689
  if (record?.usbSessionId !== bundleSessionId) {
1811
- throw (0, import_hwk_adapter_core3.createHwkError)({
1812
- code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
1690
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1691
+ code: import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
1813
1692
  message: "Keystone USB connection was lost during account export"
1814
1693
  });
1815
1694
  }
1816
1695
  };
1817
- const missingSchemas2 = Array.from(requestedByKey.values());
1818
- for (const schema of missingSchemas2) {
1696
+ for (const schema of requestedSchemas) {
1819
1697
  if (!book.has(accountKey(schema.hwkChain, schema.path))) {
1820
1698
  assertBundleSession();
1821
1699
  const synced = await this._fetchAccount(
@@ -1824,43 +1702,35 @@ var KeystoneAdapter = class _KeystoneAdapter {
1824
1702
  schema.hwkChain,
1825
1703
  schema.path,
1826
1704
  signal,
1827
- { book }
1705
+ book
1828
1706
  );
1829
1707
  record = synced.record;
1830
1708
  assertBundleSession();
1831
1709
  }
1832
1710
  }
1833
- for (const schema of missingSchemas2) {
1834
- if (!book.has(accountKey(schema.hwkChain, schema.path))) {
1835
- throw (0, import_hwk_adapter_core3.createHwkError)({
1836
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1837
- message: `Keystone did not return the requested derivation path (${schema.path})`
1838
- });
1839
- }
1840
- }
1711
+ assertAllBooked();
1841
1712
  return { book, walletId: record.walletId };
1842
1713
  }
1843
1714
  if (this._forcedTransport === "usb") {
1844
- throw (0, import_hwk_adapter_core3.createHwkError)({
1845
- code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
1715
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1716
+ code: import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
1846
1717
  message: "USB channel is not connected for this Keystone wallet"
1847
1718
  });
1848
1719
  }
1849
- const missingSchemas = Array.from(requestedByKey.values());
1850
1720
  const schemas = [
1851
1721
  { hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH },
1852
- ...missingSchemas.filter((schema) => normalizePath(schema.path) !== KEYSTONE_WALLET_ID_PATH)
1722
+ ...requestedSchemas.filter(
1723
+ (schema) => normalizePath(schema.path) !== KEYSTONE_WALLET_ID_PATH
1724
+ )
1853
1725
  ];
1854
1726
  const requestUr = this.urEngine.buildKeyDerivationRequest({
1855
- schemas: schemas.map((schema) => ({
1856
- path: schema.path,
1857
- curve: schema.hwkChain === "sol" ? "ed25519" : "secp256k1"
1858
- })),
1727
+ schemas: schemas.map((schema) => keySchema(schema.hwkChain, schema.path)),
1859
1728
  origin: this._origin
1860
1729
  });
1861
1730
  const responseUr = await this._requestQrDisplayAndAwaitResponse(
1862
1731
  record ? toDeviceInfo(record) : placeholderDeviceInfo(),
1863
- { ...requestUr, animated: false }
1732
+ { ...requestUr, animated: false },
1733
+ signal
1864
1734
  );
1865
1735
  _KeystoneAdapter._throwIfAborted(signal);
1866
1736
  const parsed = this.urEngine.parseAccountResponse(responseUr);
@@ -1873,20 +1743,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
1873
1743
  const normalizedPath = normalizePath(account.path);
1874
1744
  const hwkChain = requestedChainByPath.get(normalizedPath) ?? inferHwkChainFromPath(normalizedPath);
1875
1745
  if (hwkChain) {
1876
- book.set(accountKey(hwkChain, normalizedPath), {
1877
- ...account,
1878
- hwkChain
1879
- });
1880
- }
1881
- }
1882
- for (const schema of missingSchemas) {
1883
- if (!book.has(accountKey(schema.hwkChain, schema.path))) {
1884
- throw (0, import_hwk_adapter_core3.createHwkError)({
1885
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1886
- message: `Keystone did not return the requested derivation path (${schema.path})`
1887
- });
1746
+ book.set(accountKey(hwkChain, normalizedPath), { ...account, hwkChain });
1888
1747
  }
1889
1748
  }
1749
+ assertAllBooked();
1890
1750
  return { book, walletId: record.walletId };
1891
1751
  });
1892
1752
  }
@@ -1895,31 +1755,28 @@ var KeystoneAdapter = class _KeystoneAdapter {
1895
1755
  * fingerprint is protocol metadata and is never accepted as a lookup key.
1896
1756
  */
1897
1757
  _resolveTarget(connectId, deviceId) {
1898
- let resolvedConnectId = connectId;
1899
- if ((0, import_hwk_adapter_core3.isHardwareOperationId)(connectId)) {
1900
- resolvedConnectId = this._operations.resolve(connectId).connectId;
1901
- }
1758
+ const resolvedConnectId = (0, import_hwk_adapter_core4.isHardwareOperationId)(connectId) ? this._operations.resolve(connectId).connectId : connectId;
1902
1759
  const identifiers = [deviceId, resolvedConnectId].filter(
1903
1760
  (identifier) => Boolean(identifier)
1904
1761
  );
1905
1762
  if (!identifiers.length) return {};
1906
- const walletIds = identifiers.map((identifier) => this._walletIdFromIdentifier(identifier)).filter((walletId) => Boolean(walletId));
1763
+ const walletIds = identifiers.map((identifier) => keystoneWalletIdFromIdentifier(identifier)).filter((walletId) => Boolean(walletId));
1907
1764
  if (!walletIds.length) {
1908
- throw (0, import_hwk_adapter_core3.createHwkError)({
1909
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceNotFound,
1765
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1766
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceNotFound,
1910
1767
  message: "Keystone device lookup requires a 64-hex wallet identity"
1911
1768
  });
1912
1769
  }
1913
1770
  const expectedWalletId = walletIds[0];
1914
1771
  if (walletIds.some((walletId) => walletId !== expectedWalletId)) {
1915
- throw (0, import_hwk_adapter_core3.createHwkError)({
1916
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1772
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1773
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
1917
1774
  message: "Keystone connectId and deviceId identify different wallets"
1918
1775
  });
1919
1776
  }
1920
- if (identifiers.some((identifier) => !this._walletIdFromIdentifier(identifier))) {
1921
- throw (0, import_hwk_adapter_core3.createHwkError)({
1922
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1777
+ if (identifiers.some((identifier) => !keystoneWalletIdFromIdentifier(identifier))) {
1778
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1779
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
1923
1780
  message: "Keystone connectId and deviceId are inconsistent"
1924
1781
  });
1925
1782
  }
@@ -1933,27 +1790,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
1933
1790
  _assertParsedIdentity(parsed, expected) {
1934
1791
  const walletId = deriveKeystoneWalletId(parsed.accounts);
1935
1792
  if (expected.expectedMasterFingerprint && parsed.masterFingerprint !== expected.expectedMasterFingerprint) {
1936
- throw (0, import_hwk_adapter_core3.createHwkError)({
1937
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1793
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1794
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
1938
1795
  message: `Connected Keystone wallet (mfp ${parsed.masterFingerprint}) does not match the requested wallet fingerprint (${expected.expectedMasterFingerprint})`
1939
1796
  });
1940
1797
  }
1941
1798
  if (expected.expectedWalletId && walletId !== expected.expectedWalletId) {
1942
- throw (0, import_hwk_adapter_core3.createHwkError)({
1943
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1799
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1800
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
1944
1801
  message: "Connected Keystone wallet does not match the requested wallet identity"
1945
1802
  });
1946
1803
  }
1947
1804
  return walletId;
1948
1805
  }
1949
1806
  /**
1950
- * Folds a parsed account-response UR into the device table. `viaUsb`
1951
- * (defaults false) says which channel actually carried this round trip —
1952
- * `_resolveUr` routes a KeyDerivation sync over USB when the target record
1953
- * already has a live session, so this must NOT unconditionally mark
1954
- * `qrSynced`, or a USB-only wallet would wrongly survive a later USB
1955
- * disconnect as a "QR-synced, demote to QR-only" entry instead of being
1956
- * dropped outright (see `releaseOperation`).
1807
+ * Folds a parsed account response into the device table. Only non-USB round trips mark
1808
+ * `qrSynced`, so a USB-only wallet is dropped on disconnect.
1957
1809
  */
1958
1810
  _upsertDeviceRecord(parsed, options) {
1959
1811
  const walletId = deriveKeystoneWalletId(parsed.accounts);
@@ -1963,8 +1815,8 @@ var KeystoneAdapter = class _KeystoneAdapter {
1963
1815
  record = createDeviceRecord(walletId, parsed.masterFingerprint);
1964
1816
  this._devices.set(walletId, record);
1965
1817
  } else if (record.masterFingerprint !== parsed.masterFingerprint) {
1966
- throw (0, import_hwk_adapter_core3.createHwkError)({
1967
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1818
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1819
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
1968
1820
  message: "Keystone wallet identity returned an inconsistent master fingerprint"
1969
1821
  });
1970
1822
  }
@@ -1974,42 +1826,46 @@ var KeystoneAdapter = class _KeystoneAdapter {
1974
1826
  record.usbSessionId = options?.usbSessionId ?? record.usbSessionId;
1975
1827
  if (!options?.viaUsb) record.qrSynced = true;
1976
1828
  const info = toDeviceInfo(record);
1977
- const eventType = isNew ? import_hwk_adapter_core3.DEVICE.CONNECT : import_hwk_adapter_core3.DEVICE.CHANGED;
1829
+ const eventType = isNew ? import_hwk_adapter_core4.DEVICE.CONNECT : import_hwk_adapter_core4.DEVICE.CHANGED;
1978
1830
  this.emitter.emit(eventType, { type: eventType, payload: info });
1979
1831
  return record;
1980
1832
  }
1981
1833
  /**
1982
- * Opens+claims whatever Keystone the USB connector currently has
1983
- * permission for, learns its protocol mfp via `getAppConfig`, then requests
1984
- * one fixed account-level xpub and derives the stable wallet id. A
1985
- * QR-synced entry becomes
1986
- * `{qr, usb}`-capable in place (one `device-changed`, not a second
1987
- * `device-connect`); a wallet never seen before becomes a new USB-only
1988
- * entry. See §4.2 of the design doc.
1834
+ * Opens a permitted USB Keystone, reads its mfp, and derives the wallet id from the identity
1835
+ * xpub. A known wallet gains USB in place (`device-changed`); an unseen one becomes USB-only.
1989
1836
  */
1990
- async _connectUsb(expected, searchTargetId) {
1991
- if (this._pendingUsbTeardowns > 0) {
1992
- return (0, import_hwk_adapter_core3.failure)(
1993
- import_hwk_adapter_core3.HardwareErrorCode.DeviceBusyInternal,
1837
+ async _connectUsb(expected, searchTargetId, signal) {
1838
+ if (this._pendingUsbTeardowns > 0 || this._abandonedUsbConnects > 0) {
1839
+ return (0, import_hwk_adapter_core4.failure)(
1840
+ import_hwk_adapter_core4.HardwareErrorCode.DeviceBusyInternal,
1994
1841
  "Keystone USB session is being released"
1995
1842
  );
1996
1843
  }
1997
- const previous = this._usbConnectTail;
1998
- let release;
1999
- this._usbConnectTail = new Promise((resolve) => {
2000
- release = resolve;
1844
+ _KeystoneAdapter._throwIfAborted(signal);
1845
+ let abandoned = false;
1846
+ const onAbort = () => {
1847
+ abandoned = true;
1848
+ this._abandonedUsbConnects += 1;
1849
+ };
1850
+ signal?.addEventListener("abort", onAbort, { once: true });
1851
+ const pending = this._usbConnectTail.then(async () => {
1852
+ _KeystoneAdapter._throwIfAborted(signal);
1853
+ return this._connectUsbExclusive(expected, searchTargetId, signal);
2001
1854
  });
2002
- await previous;
2003
- try {
2004
- return await this._connectUsbExclusive(expected, searchTargetId);
2005
- } finally {
2006
- release?.();
2007
- }
1855
+ const settled = pending.finally(() => {
1856
+ signal?.removeEventListener("abort", onAbort);
1857
+ if (abandoned) this._abandonedUsbConnects -= 1;
1858
+ });
1859
+ this._usbConnectTail = settled.then(
1860
+ () => void 0,
1861
+ () => void 0
1862
+ );
1863
+ return signal ? _KeystoneAdapter._abortable(signal, settled) : settled;
2008
1864
  }
2009
- async _connectUsbExclusive(expected, searchTargetId) {
1865
+ async _connectUsbExclusive(expected, searchTargetId, signal) {
2010
1866
  if (!this._usbConnector) {
2011
- return (0, import_hwk_adapter_core3.failure)(
2012
- import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
1867
+ return (0, import_hwk_adapter_core4.failure)(
1868
+ import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
2013
1869
  "No USB connector configured for this Keystone adapter"
2014
1870
  );
2015
1871
  }
@@ -2027,23 +1883,24 @@ var KeystoneAdapter = class _KeystoneAdapter {
2027
1883
  const connector = this._usbConnector;
2028
1884
  const session = await (connector.connectTarget ? connector.connectTarget(connectTarget) : connector.connect(searchTargetId ?? expected.expectedMasterFingerprint));
2029
1885
  sessionId = session.sessionId;
1886
+ _KeystoneAdapter._throwIfAborted(signal);
2030
1887
  const raw = session.deviceInfo.raw;
2031
- const mfpValue = (0, import_hwk_adapter_core3.parseBip32MasterFingerprint)(raw?.masterFingerprint);
1888
+ const mfpValue = (0, import_hwk_adapter_core4.parseBip32MasterFingerprint)(raw?.masterFingerprint);
2032
1889
  if (!mfpValue) {
2033
- throw (0, import_hwk_adapter_core3.createHwkError)({
2034
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1890
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1891
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
2035
1892
  message: "Keystone USB did not report a master fingerprint"
2036
1893
  });
2037
1894
  }
2038
1895
  const masterFingerprint = mfpValue.toLowerCase();
2039
1896
  if (expected.expectedMasterFingerprint && masterFingerprint !== expected.expectedMasterFingerprint) {
2040
- throw (0, import_hwk_adapter_core3.createHwkError)({
2041
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
1897
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1898
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
2042
1899
  message: `Connected Keystone wallet (mfp ${masterFingerprint}) does not match the requested wallet fingerprint (${expected.expectedMasterFingerprint})`
2043
1900
  });
2044
1901
  }
2045
1902
  const identityRequest = this.urEngine.buildKeyDerivationRequest({
2046
- schemas: [{ path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" }],
1903
+ schemas: [keySchema("evm", KEYSTONE_WALLET_ID_PATH)],
2047
1904
  origin: this._origin
2048
1905
  });
2049
1906
  const identityResult = await this._callUsbConnector(
@@ -2051,8 +1908,9 @@ var KeystoneAdapter = class _KeystoneAdapter {
2051
1908
  "resolveUr",
2052
1909
  identityRequest
2053
1910
  );
1911
+ _KeystoneAdapter._throwIfAborted(signal);
2054
1912
  if (!identityResult.success) {
2055
- throw (0, import_hwk_adapter_core3.rehydrateConnectorError)(identityResult.error);
1913
+ throw (0, import_hwk_adapter_core4.rehydrateConnectorError)(identityResult.error);
2056
1914
  }
2057
1915
  const parsed = this.urEngine.parseAccountResponse(identityResult.payload);
2058
1916
  this._assertParsedIdentity(parsed, {
@@ -2061,21 +1919,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
2061
1919
  });
2062
1920
  const walletId = deriveKeystoneWalletId(parsed.accounts);
2063
1921
  const previousUsbSessionId = this._devices.get(walletId)?.usbSessionId;
2064
- const record = this._upsertDeviceRecord(parsed, {
2065
- viaUsb: true,
2066
- usbSessionId: session.sessionId
2067
- });
2068
1922
  if (previousUsbSessionId && previousUsbSessionId !== session.sessionId) {
2069
1923
  try {
2070
1924
  await this._usbConnector.disconnect(previousUsbSessionId);
2071
1925
  } catch {
2072
1926
  }
2073
1927
  }
1928
+ _KeystoneAdapter._throwIfAborted(signal);
1929
+ const record = this._upsertDeviceRecord(parsed, {
1930
+ viaUsb: true,
1931
+ usbSessionId: session.sessionId
1932
+ });
1933
+ _KeystoneAdapter._throwIfAborted(signal);
2074
1934
  record.hadUsbSession = true;
2075
1935
  record.model = session.deviceInfo.modelName ?? session.deviceInfo.model ?? record.model;
2076
1936
  record.deviceVersion = session.deviceInfo.firmwareVersion ?? record.deviceVersion;
2077
- const info = toDeviceInfo(record);
2078
- return (0, import_hwk_adapter_core3.success)(info);
1937
+ return (0, import_hwk_adapter_core4.success)(toDeviceInfo(record));
2079
1938
  } catch (err) {
2080
1939
  if (sessionId) {
2081
1940
  try {
@@ -2087,13 +1946,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
2087
1946
  }
2088
1947
  }
2089
1948
  /**
2090
- * Probe already-authorized USB devices without opening a permission picker,
2091
- * then attach and verify the requested wallet when one is present. This is
2092
- * used both after an adapter restart and after a QR-only period, so plugging
2093
- * USB back in affects the next business call without changing an in-flight
2094
- * QR operation.
1949
+ * Probes already-authorized USB devices without a permission picker, then attaches and verifies
1950
+ * the requested wallet if present.
2095
1951
  */
2096
- async _tryUsbAttach(expectedWalletId, expectedMasterFingerprint, options) {
1952
+ async _tryUsbAttach(signal, expectedWalletId, expectedMasterFingerprint, options) {
1953
+ _KeystoneAdapter._throwIfAborted(signal);
2097
1954
  if (!expectedWalletId || this._forcedTransport === "qr" || !this._usbConnector) {
2098
1955
  return void 0;
2099
1956
  }
@@ -2101,17 +1958,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
2101
1958
  let availableDevices = [];
2102
1959
  let lastSearchError;
2103
1960
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1961
+ _KeystoneAdapter._throwIfAborted(signal);
2104
1962
  try {
2105
- availableDevices = await this._usbConnector.searchDevices({ purpose: "availability" });
1963
+ availableDevices = await _KeystoneAdapter._abortable(
1964
+ signal,
1965
+ this._usbConnector.searchDevices({ purpose: "availability" })
1966
+ );
2106
1967
  lastSearchError = void 0;
2107
1968
  } catch (error) {
2108
1969
  lastSearchError = error;
2109
1970
  }
1971
+ _KeystoneAdapter._throwIfAborted(signal);
2110
1972
  if (availableDevices.length) {
2111
1973
  break;
2112
1974
  }
2113
1975
  if (attempt < maxAttempts) {
2114
- await waitForKeystoneUsbReattachProbe();
1976
+ await _KeystoneAdapter._abortable(signal, waitForKeystoneUsbReattachProbe());
2115
1977
  }
2116
1978
  }
2117
1979
  if (!availableDevices.length) {
@@ -2126,8 +1988,16 @@ var KeystoneAdapter = class _KeystoneAdapter {
2126
1988
  for (const device of candidates) {
2127
1989
  attached = await this._connectUsb(
2128
1990
  { expectedWalletId, expectedMasterFingerprint },
2129
- device?.connectId
1991
+ device?.connectId,
1992
+ signal
2130
1993
  );
1994
+ _KeystoneAdapter._throwIfAborted(signal);
1995
+ if (!attached.success && attached.payload.code === import_hwk_adapter_core4.HardwareErrorCode.DeviceBusyInternal) {
1996
+ throw (0, import_hwk_adapter_core4.createHwkError)({
1997
+ ...attached.payload,
1998
+ message: attached.payload.error
1999
+ });
2000
+ }
2131
2001
  if (attached.success) {
2132
2002
  record = this._devices.get(expectedWalletId);
2133
2003
  if (record) break;
@@ -2136,17 +2006,11 @@ var KeystoneAdapter = class _KeystoneAdapter {
2136
2006
  return record;
2137
2007
  }
2138
2008
  /**
2139
- * The one place that decides QR vs. USB for a UR round trip and carries it
2140
- * out. `record` is the (possibly not-yet-existing, for a true cold start)
2141
- * device row for the target wallet. USB is the preferred channel: a known
2142
- * wallet record with no live session gets ONE best-effort re-attach here,
2143
- * and anything that goes wrong just leaves the call on QR. A
2144
- * `switchTransport('qr')` pin forces QR even for a USB-attached wallet;
2145
- * `switchTransport('usb')` on a wallet with no live USB session fails
2146
- * closed rather than silently falling back to QR.
2009
+ * Decides QR vs USB for one UR round trip. A pinned 'usb' with no live session fails closed;
2010
+ * otherwise a failed USB reattach leaves the call on QR.
2147
2011
  */
2148
2012
  async _resolveUr(record, requestUr, animated, operationId, signal, operationName) {
2149
- const releaseOperationRetention = (0, import_hwk_adapter_core3.isHardwareOperationId)(operationId) ? this._operations.retain(operationId) : void 0;
2013
+ const releaseOperationRetention = (0, import_hwk_adapter_core4.isHardwareOperationId)(operationId) ? this._operations.retain(operationId) : void 0;
2150
2014
  try {
2151
2015
  return await this._resolveUrWithRoute(
2152
2016
  record,
@@ -2162,21 +2026,19 @@ var KeystoneAdapter = class _KeystoneAdapter {
2162
2026
  }
2163
2027
  async _resolveUrWithRoute(record, requestUr, animated, operationId, signal, operationName) {
2164
2028
  let interactionRoute;
2165
- if ((0, import_hwk_adapter_core3.isHardwareOperationId)(operationId)) {
2029
+ if ((0, import_hwk_adapter_core4.isHardwareOperationId)(operationId)) {
2166
2030
  const operation = this._operations.resolve(operationId);
2167
2031
  const route = this._operationRoutes.get(operation.connectId);
2168
2032
  if (!route || route.operationId !== operationId) {
2169
- this._operations.end(operationId, "disconnect");
2170
- throw (0, import_hwk_adapter_core3.createHwkError)({
2171
- code: import_hwk_adapter_core3.HardwareErrorCode.OperationEnded,
2172
- message: "Keystone hardware operation is no longer connected",
2173
- params: { operationId, reason: "disconnect" }
2174
- });
2033
+ throw this._endDisconnectedOperation(
2034
+ operationId,
2035
+ "Keystone hardware operation is no longer connected"
2036
+ );
2175
2037
  }
2176
2038
  interactionRoute = route;
2177
2039
  }
2178
2040
  if (!interactionRoute && !this._forcedTransport && record && !record.usbSessionId && this._usbConnector) {
2179
- await this._tryUsbAttach(record.walletId, record.masterFingerprint, {
2041
+ await this._tryUsbAttach(signal, record.walletId, record.masterFingerprint, {
2180
2042
  waitForReenumeration: record.hadUsbSession
2181
2043
  });
2182
2044
  }
@@ -2184,18 +2046,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
2184
2046
  if (wantUsb) {
2185
2047
  if (!record?.usbSessionId || !this._usbConnector) {
2186
2048
  if (interactionRoute) {
2187
- this._operations.end(interactionRoute.operationId, "disconnect");
2188
- throw (0, import_hwk_adapter_core3.createHwkError)({
2189
- code: import_hwk_adapter_core3.HardwareErrorCode.OperationEnded,
2190
- message: "Keystone operation USB connection was lost",
2191
- params: {
2192
- operationId: interactionRoute.operationId,
2193
- reason: "disconnect"
2194
- }
2195
- });
2049
+ throw this._endDisconnectedOperation(
2050
+ interactionRoute.operationId,
2051
+ "Keystone operation USB connection was lost"
2052
+ );
2196
2053
  }
2197
- throw (0, import_hwk_adapter_core3.createHwkError)({
2198
- code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
2054
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2055
+ code: import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
2199
2056
  message: "USB channel is not connected for this Keystone wallet"
2200
2057
  });
2201
2058
  }
@@ -2206,17 +2063,20 @@ var KeystoneAdapter = class _KeystoneAdapter {
2206
2063
  signal
2207
2064
  );
2208
2065
  if (result.success) return result.payload;
2209
- const usbError = (0, import_hwk_adapter_core3.rehydrateConnectorError)(result.error);
2210
- const usbErrorOrigin = usbError.origin;
2211
- const usbErrorCode = usbError.code;
2212
- if (!interactionRoute && !this._forcedTransport && usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.PayloadTooLarge && usbErrorOrigin !== "device") {
2213
- const displayDevice2 = toDeviceInfo(record);
2214
- return this._requestQrDisplayAndAwaitResponse(displayDevice2, {
2215
- ...requestUr,
2216
- animated
2217
- });
2066
+ const usbError = (0, import_hwk_adapter_core4.rehydrateConnectorError)(result.error);
2067
+ const { origin: usbErrorOrigin, code: usbErrorCode } = usbError;
2068
+ if (usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.PayloadTooLarge && usbErrorOrigin !== "device") {
2069
+ if (!interactionRoute && !this._forcedTransport) {
2070
+ const displayDevice2 = toDeviceInfo(record);
2071
+ return this._requestQrDisplayAndAwaitResponse(
2072
+ displayDevice2,
2073
+ { ...requestUr, animated },
2074
+ signal
2075
+ );
2076
+ }
2077
+ throw usbError;
2218
2078
  }
2219
- const deviceAnswered = usbErrorOrigin !== void 0 ? usbErrorOrigin === "device" : usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.UserRejected || usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.UserAborted || usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.DeviceLocked || usbErrorCode === import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch;
2079
+ const deviceAnswered = usbErrorOrigin !== void 0 ? usbErrorOrigin === "device" : usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.UserRejected || usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.UserAborted || usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.DeviceLocked || usbErrorCode === import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch;
2220
2080
  if (!deviceAnswered) {
2221
2081
  const lostSessionId = record.usbSessionId;
2222
2082
  if (lostSessionId) {
@@ -2224,27 +2084,19 @@ var KeystoneAdapter = class _KeystoneAdapter {
2224
2084
  }
2225
2085
  record.usbSessionId = void 0;
2226
2086
  if (interactionRoute) {
2227
- this._operations.end(interactionRoute.operationId, "disconnect");
2228
- throw (0, import_hwk_adapter_core3.createHwkError)({
2229
- code: import_hwk_adapter_core3.HardwareErrorCode.OperationEnded,
2230
- message: operationName ? `Keystone ${operationName} may have completed before the USB connection was lost` : "Keystone operation USB connection was lost",
2231
- recovery: operationName ? { scope: "unknown" } : void 0,
2232
- params: operationName ? (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(operationName, {
2233
- operationId: interactionRoute.operationId,
2234
- reason: "disconnect"
2235
- }) : {
2236
- operationId: interactionRoute.operationId,
2237
- reason: "disconnect"
2238
- }
2239
- });
2087
+ throw this._endDisconnectedOperation(
2088
+ interactionRoute.operationId,
2089
+ operationName ? `Keystone ${operationName} may have completed before the USB connection was lost` : "Keystone operation USB connection was lost",
2090
+ operationName
2091
+ );
2240
2092
  }
2241
2093
  if (operationName) {
2242
- throw (0, import_hwk_adapter_core3.createHwkError)({
2243
- code: usbErrorCode ?? import_hwk_adapter_core3.HardwareErrorCode.TransportError,
2094
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2095
+ code: usbErrorCode ?? import_hwk_adapter_core4.HardwareErrorCode.TransportError,
2244
2096
  message: `Keystone ${operationName} may have completed before the USB connection was lost`,
2245
2097
  origin: "transport",
2246
2098
  recovery: { scope: "unknown" },
2247
- params: (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(operationName)
2099
+ params: (0, import_hwk_adapter_core4.operationMayHaveCompletedParams)(operationName)
2248
2100
  });
2249
2101
  }
2250
2102
  }
@@ -2252,71 +2104,70 @@ var KeystoneAdapter = class _KeystoneAdapter {
2252
2104
  }
2253
2105
  const displayDevice = record ? toDeviceInfo(record) : placeholderDeviceInfo();
2254
2106
  try {
2255
- return await this._requestQrDisplayAndAwaitResponse(displayDevice, {
2256
- ...requestUr,
2257
- animated
2258
- });
2107
+ return await this._requestQrDisplayAndAwaitResponse(
2108
+ displayDevice,
2109
+ { ...requestUr, animated },
2110
+ signal
2111
+ );
2259
2112
  } catch (error) {
2260
2113
  const { code, _tag: tag } = error;
2261
- if (operationName && (code === import_hwk_adapter_core3.HardwareErrorCode.OperationTimeout || tag === import_hwk_adapter_core3.UI_REQUEST_TIMEOUT_TAG)) {
2262
- throw (0, import_hwk_adapter_core3.createHwkError)({
2263
- code: import_hwk_adapter_core3.HardwareErrorCode.OperationTimeout,
2114
+ if (operationName && (code === import_hwk_adapter_core4.HardwareErrorCode.OperationTimeout || tag === import_hwk_adapter_core4.UI_REQUEST_TIMEOUT_TAG)) {
2115
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2116
+ code: import_hwk_adapter_core4.HardwareErrorCode.OperationTimeout,
2264
2117
  message: `Keystone ${operationName} may have completed before the QR response timed out`,
2265
2118
  recovery: { scope: "unknown" },
2266
- params: (0, import_hwk_adapter_core3.operationMayHaveCompletedParams)(operationName)
2119
+ params: (0, import_hwk_adapter_core4.operationMayHaveCompletedParams)(operationName)
2267
2120
  });
2268
2121
  }
2269
2122
  throw error;
2270
2123
  }
2271
2124
  }
2125
+ /** Ends the operation as disconnected and returns the error to throw. */
2126
+ _endDisconnectedOperation(operationId, message, operationName) {
2127
+ this._operations.end(operationId, "disconnect");
2128
+ const params = { operationId, reason: "disconnect" };
2129
+ return (0, import_hwk_adapter_core4.createHwkError)({
2130
+ code: import_hwk_adapter_core4.HardwareErrorCode.OperationEnded,
2131
+ message,
2132
+ recovery: operationName ? { scope: "unknown" } : void 0,
2133
+ params: operationName ? (0, import_hwk_adapter_core4.operationMayHaveCompletedParams)(operationName, params) : params
2134
+ });
2135
+ }
2272
2136
  /**
2273
- * Fetch one account's key material from the device for this operation only.
2274
- * `book` lets earlier fetches of the same operation be reused; nothing is
2275
- * retained on the record. A failed USB export ends this operation without
2276
- * replaying the request or switching transports.
2137
+ * Fetches one account's key material for this operation only; `book` reuses earlier fetches. A
2138
+ * failed USB export ends the operation without replaying or switching transports.
2277
2139
  */
2278
- async _fetchAccount(connectId, deviceId, hwkChain, syncPath, signal, options) {
2140
+ async _fetchAccount(connectId, deviceId, hwkChain, syncPath, signal, book) {
2279
2141
  const target = this._resolveTarget(connectId, deviceId);
2142
+ const operationConnectionType = (0, import_hwk_adapter_core4.isHardwareOperationId)(connectId) ? this._operations.resolve(connectId).connectionType : void 0;
2280
2143
  const key = accountKey(hwkChain, syncPath);
2281
2144
  let existingRecord = target.record;
2282
2145
  if (!existingRecord) {
2283
- const attached = await this._tryUsbAttach(target.expectedWalletId);
2146
+ existingRecord = await this._tryUsbAttach(signal, target.expectedWalletId);
2284
2147
  _KeystoneAdapter._throwIfAborted(signal);
2285
- existingRecord = attached;
2286
2148
  }
2287
- const book = options?.book;
2288
2149
  const booked = book?.get(key);
2289
2150
  if (existingRecord && booked) {
2290
2151
  return { record: existingRecord, account: booked };
2291
2152
  }
2292
- if (existingRecord && !existingRecord.usbSessionId && this._forcedTransport === "usb") {
2293
- await this._tryUsbAttach(existingRecord.walletId, existingRecord.masterFingerprint, {
2153
+ if (!operationConnectionType && existingRecord && !existingRecord.usbSessionId && this._forcedTransport === "usb") {
2154
+ await this._tryUsbAttach(signal, existingRecord.walletId, existingRecord.masterFingerprint, {
2294
2155
  waitForReenumeration: existingRecord.hadUsbSession
2295
2156
  });
2296
2157
  _KeystoneAdapter._throwIfAborted(signal);
2297
2158
  existingRecord = this._devices.get(existingRecord.walletId) ?? existingRecord;
2298
2159
  }
2299
- const useSinglePathUsbExport = this._forcedTransport !== "qr" && Boolean(existingRecord?.usbSessionId);
2300
- const requestedSchemaPaths = [
2301
- { hwkChain, path: syncPath }
2302
- ];
2303
- const schemaPaths = useSinglePathUsbExport || requestedSchemaPaths.some((schema) => normalizePath(schema.path) === KEYSTONE_WALLET_ID_PATH) ? requestedSchemaPaths : [{ hwkChain: "evm", path: KEYSTONE_WALLET_ID_PATH }, ...requestedSchemaPaths];
2304
- const requestUr = this.urEngine.buildKeyDerivationRequest({
2305
- schemas: schemaPaths.map((s) => ({
2306
- path: s.path,
2307
- curve: s.hwkChain === "sol" ? "ed25519" : "secp256k1"
2308
- })),
2309
- origin: this._origin
2310
- });
2311
- const resolvedViaUsb = useSinglePathUsbExport;
2160
+ const useSinglePathUsbExport = operationConnectionType ? operationConnectionType === "usb" : this._forcedTransport !== "qr" && Boolean(existingRecord?.usbSessionId);
2161
+ const schemas = useSinglePathUsbExport || normalizePath(syncPath) === KEYSTONE_WALLET_ID_PATH ? [keySchema(hwkChain, syncPath)] : [keySchema("evm", KEYSTONE_WALLET_ID_PATH), keySchema(hwkChain, syncPath)];
2162
+ const requestUr = this.urEngine.buildKeyDerivationRequest({ schemas, origin: this._origin });
2312
2163
  const responseUr = await this._resolveUr(existingRecord, requestUr, false, connectId, signal);
2313
2164
  _KeystoneAdapter._throwIfAborted(signal);
2314
2165
  const parsed = this.urEngine.parseAccountResponse(responseUr);
2315
2166
  let record;
2316
- if (resolvedViaUsb && existingRecord) {
2167
+ if (useSinglePathUsbExport && existingRecord) {
2317
2168
  if (parsed.masterFingerprint !== existingRecord.masterFingerprint) {
2318
- throw (0, import_hwk_adapter_core3.createHwkError)({
2319
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
2169
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2170
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
2320
2171
  message: "Keystone USB account export returned a different master fingerprint"
2321
2172
  });
2322
2173
  }
@@ -2324,13 +2175,13 @@ var KeystoneAdapter = class _KeystoneAdapter {
2324
2175
  } else {
2325
2176
  this._assertParsedIdentity(parsed, target);
2326
2177
  record = this._upsertDeviceRecord(parsed, {
2327
- viaUsb: Boolean(existingRecord?.usbSessionId)
2178
+ viaUsb: operationConnectionType !== "qr" && Boolean(existingRecord?.usbSessionId)
2328
2179
  });
2329
2180
  }
2330
2181
  const account = parsed.accounts.find((a) => normalizePath(a.path) === syncPath);
2331
2182
  if (!account) {
2332
- throw (0, import_hwk_adapter_core3.createHwkError)({
2333
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
2183
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2184
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
2334
2185
  message: `Keystone did not return the requested derivation path (${syncPath})`
2335
2186
  });
2336
2187
  }
@@ -2350,88 +2201,115 @@ var KeystoneAdapter = class _KeystoneAdapter {
2350
2201
  return { record, account: entry };
2351
2202
  }
2352
2203
  /**
2353
- * Like `_fetchAccount`, but for operations (PSBT signing, master
2354
- * fingerprint) that only need to know WHICH wallet is attached, not a
2355
- * specific cached path. Syncs the account-level path for `chain` as a
2356
- * throwaway probe when the wallet record isn't already known.
2357
- *
2358
- * `CHAIN_FINGERPRINT_PATHS[chain]` is a 5-segment LEAF path for `evm`
2359
- * (`m/44'/60'/0'/0/0`) — sending that verbatim as a KeyDerivation request
2360
- * asks Keystone for a non-standard path. Keystone's own docs
2361
- * (dev.keyst.one's multichain KeyDerivation example) show the ETH
2362
- * account-level path as `m/44'/60'/0'` (3 segments), same as what
2363
- * `_fetchAccount` already requests — so
2364
- * truncate through `splitAccountPath` here too instead of using the raw
2365
- * fingerprint leaf path. `btc`/`sol` are already 3-segment account paths
2366
- * and pass through unchanged.
2204
+ * Resolves which wallet is attached, without a per-path account. `CHAIN_FINGERPRINT_PATHS.evm` is
2205
+ * a leaf path, so it is cut to the account path Keystone expects.
2367
2206
  */
2368
2207
  async _ensureWalletKnown(connectId, deviceId, chain, signal) {
2369
2208
  const target = this._resolveTarget(connectId, deviceId);
2370
2209
  if (target.record) return { record: target.record };
2371
- const attached = await this._tryUsbAttach(target.expectedWalletId);
2210
+ const attached = await this._tryUsbAttach(signal, target.expectedWalletId);
2372
2211
  _KeystoneAdapter._throwIfAborted(signal);
2373
2212
  if (attached) return { record: attached };
2374
- const { accountPath } = splitAccountPath(import_hwk_adapter_core3.CHAIN_FINGERPRINT_PATHS[chain]);
2375
- const schemas = accountPath === KEYSTONE_WALLET_ID_PATH ? [{ path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" }] : [
2376
- { path: KEYSTONE_WALLET_ID_PATH, curve: "secp256k1" },
2377
- {
2378
- path: accountPath,
2379
- curve: chain === "sol" ? "ed25519" : "secp256k1"
2380
- }
2381
- ];
2382
- const requestUr = this.urEngine.buildKeyDerivationRequest({
2383
- schemas,
2384
- origin: this._origin
2385
- });
2213
+ const { accountPath } = splitAccountPath(import_hwk_adapter_core4.CHAIN_FINGERPRINT_PATHS[chain]);
2214
+ const schemas = accountPath === KEYSTONE_WALLET_ID_PATH ? [keySchema("evm", KEYSTONE_WALLET_ID_PATH)] : [keySchema("evm", KEYSTONE_WALLET_ID_PATH), keySchema(chain, accountPath)];
2215
+ const requestUr = this.urEngine.buildKeyDerivationRequest({ schemas, origin: this._origin });
2386
2216
  const responseUr = await this._resolveUr(void 0, requestUr, false, connectId, signal);
2387
2217
  _KeystoneAdapter._throwIfAborted(signal);
2388
2218
  const parsed = this.urEngine.parseAccountResponse(responseUr);
2389
2219
  this._assertParsedIdentity(parsed, target);
2390
- const record = this._upsertDeviceRecord(parsed);
2391
- return { record };
2220
+ return { record: this._upsertDeviceRecord(parsed) };
2221
+ }
2222
+ /**
2223
+ * The operation owning the running job, so a mid-call UI request can name it. Unpinned jobs map
2224
+ * through the operation route; a cold start returns undefined.
2225
+ */
2226
+ _activeOperationId() {
2227
+ const activeJobId = this._jobQueue.getActiveJob()?.deviceId;
2228
+ if (!activeJobId) return void 0;
2229
+ if ((0, import_hwk_adapter_core4.isHardwareOperationId)(activeJobId)) return activeJobId;
2230
+ return this._operationRouteForIdentifier(activeJobId)?.operationId;
2231
+ }
2232
+ /** Routes are keyed by connectId; a job key may be the bare wallet id. */
2233
+ _operationRouteForIdentifier(identifier) {
2234
+ for (const key of keystoneCancelQueueKeys([identifier])) {
2235
+ const route = this._operationRoutes.get(key);
2236
+ if (route) return route;
2237
+ }
2238
+ const walletId = keystoneWalletIdFromIdentifier(identifier);
2239
+ return walletId ? this._operationRoutes.get(walletConnectId(walletId)) : void 0;
2392
2240
  }
2393
- async _requestQrDisplayAndAwaitResponse(device, data) {
2241
+ /**
2242
+ * A QR prompt has no transport to interrupt; releasing the registry slot on abort is what lets
2243
+ * a cancel reach the wait before the QR timeout.
2244
+ */
2245
+ async _awaitQrResponse(requestType, pending, signal) {
2246
+ if (!signal) return pending;
2247
+ let settled = false;
2248
+ const onAbort = () => {
2249
+ if (!settled) this._uiRegistry.cancel(requestType);
2250
+ };
2251
+ if (signal.aborted) onAbort();
2252
+ else signal.addEventListener("abort", onAbort, { once: true });
2253
+ try {
2254
+ return await pending;
2255
+ } finally {
2256
+ settled = true;
2257
+ signal.removeEventListener("abort", onAbort);
2258
+ }
2259
+ }
2260
+ async _requestQrDisplayAndAwaitResponse(device, data, signal) {
2261
+ const operationId = this._activeOperationId();
2394
2262
  const waitPromise = this._uiRegistry.wait(
2395
- import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_DISPLAY,
2396
- { timeoutMs: this._qrTimeoutMs }
2263
+ import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_DISPLAY,
2264
+ { timeoutMs: this._qrTimeoutMs, operationId }
2397
2265
  );
2398
- this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_DISPLAY, {
2399
- type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_DISPLAY,
2400
- payload: { device, data }
2266
+ this.emitter.emit(import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_DISPLAY, {
2267
+ type: import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_DISPLAY,
2268
+ payload: { device, data, operationId }
2401
2269
  });
2402
- const response = await waitPromise;
2270
+ const response = await this._awaitQrResponse(
2271
+ import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_DISPLAY,
2272
+ waitPromise,
2273
+ signal
2274
+ );
2403
2275
  return { urType: response.urType, urData: response.urData };
2404
2276
  }
2405
- async _requestQrScanAndAwaitResponse(device) {
2277
+ async _requestQrScanAndAwaitResponse(device, signal) {
2278
+ const operationId = this._activeOperationId();
2406
2279
  const waitPromise = this._uiRegistry.wait(
2407
- import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_SCAN,
2408
- { timeoutMs: this._qrTimeoutMs }
2280
+ import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_SCAN,
2281
+ { timeoutMs: this._qrTimeoutMs, operationId }
2409
2282
  );
2410
- this.emitter.emit(import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_SCAN, {
2411
- type: import_hwk_adapter_core3.UI_REQUEST.REQUEST_QR_SCAN,
2412
- payload: { device }
2283
+ this.emitter.emit(import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_SCAN, {
2284
+ type: import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_SCAN,
2285
+ payload: { device, operationId }
2413
2286
  });
2414
- const response = await waitPromise;
2287
+ const response = await this._awaitQrResponse(import_hwk_adapter_core4.UI_REQUEST.REQUEST_QR_SCAN, waitPromise, signal);
2415
2288
  return { urType: response.urType, urData: response.urData };
2416
2289
  }
2417
2290
  async _callUsbConnector(sessionId, method, params, signal) {
2418
2291
  if (!this._usbConnector) {
2419
- throw (0, import_hwk_adapter_core3.createHwkError)({
2420
- code: import_hwk_adapter_core3.HardwareErrorCode.TransportNotAvailable,
2292
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2293
+ code: import_hwk_adapter_core4.HardwareErrorCode.TransportNotAvailable,
2421
2294
  message: "No USB connector configured for this Keystone adapter"
2422
2295
  });
2423
2296
  }
2424
2297
  if (this._pendingUsbTeardowns > 0 || this._unsettledUsbOperations.size > 0) {
2425
- throw (0, import_hwk_adapter_core3.createHwkError)({
2426
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceBusyInternal,
2298
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2299
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceBusyInternal,
2427
2300
  message: `Keystone USB is busy while calling ${method}`
2428
2301
  });
2429
2302
  }
2303
+ if (signal?.aborted) {
2304
+ throw signal.reason instanceof Error ? signal.reason : (0, import_hwk_adapter_core4.createHwkError)({
2305
+ code: import_hwk_adapter_core4.HardwareErrorCode.UserAborted,
2306
+ message: "User aborted operation"
2307
+ });
2308
+ }
2430
2309
  const releaseOperation = this._retainUsbOperation(`call:${sessionId}`);
2431
2310
  let rawCall;
2432
2311
  try {
2433
- const connector = this._usbConnector;
2434
- rawCall = connector.call(sessionId, method, params).finally(releaseOperation);
2312
+ rawCall = this._usbConnector.call(sessionId, method, params).finally(releaseOperation);
2435
2313
  } catch (error) {
2436
2314
  releaseOperation();
2437
2315
  throw error;
@@ -2481,13 +2359,10 @@ var KeystoneAdapter = class _KeystoneAdapter {
2481
2359
  };
2482
2360
  }
2483
2361
  static _abortable(signal, promise) {
2484
- if (signal.aborted) {
2485
- return Promise.reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
2486
- }
2362
+ const abortReason = () => signal.reason instanceof Error ? signal.reason : new Error("Aborted");
2363
+ if (signal.aborted) return Promise.reject(abortReason());
2487
2364
  return new Promise((resolve, reject) => {
2488
- const onAbort = () => {
2489
- reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
2490
- };
2365
+ const onAbort = () => reject(abortReason());
2491
2366
  signal.addEventListener("abort", onAbort, { once: true });
2492
2367
  promise.then(
2493
2368
  (value) => {
@@ -2502,22 +2377,22 @@ var KeystoneAdapter = class _KeystoneAdapter {
2502
2377
  });
2503
2378
  }
2504
2379
  static _throwIfAborted(signal) {
2505
- if (signal.aborted) {
2380
+ if (signal?.aborted) {
2506
2381
  throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
2507
2382
  }
2508
2383
  }
2509
2384
  static _assertRequestIdMatches(expected, actual) {
2510
2385
  if (actual && actual.toLowerCase() !== expected.toLowerCase()) {
2511
- throw (0, import_hwk_adapter_core3.createHwkError)({
2512
- code: import_hwk_adapter_core3.HardwareErrorCode.DeviceMismatch,
2386
+ throw (0, import_hwk_adapter_core4.createHwkError)({
2387
+ code: import_hwk_adapter_core4.HardwareErrorCode.DeviceMismatch,
2513
2388
  message: "Keystone response requestId does not match the pending request \u2014 discarding a stale or unrelated scan"
2514
2389
  });
2515
2390
  }
2516
2391
  }
2517
2392
  _unsupported(method, reason) {
2518
2393
  return Promise.resolve(
2519
- (0, import_hwk_adapter_core3.failure)(
2520
- import_hwk_adapter_core3.HardwareErrorCode.MethodNotSupported,
2394
+ (0, import_hwk_adapter_core4.failure)(
2395
+ import_hwk_adapter_core4.HardwareErrorCode.MethodNotSupported,
2521
2396
  `KeystoneAdapter.${method} is not implemented yet: ${reason}`
2522
2397
  )
2523
2398
  );
@@ -2527,30 +2402,30 @@ var KeystoneAdapter = class _KeystoneAdapter {
2527
2402
  const e = err;
2528
2403
  if (typeof e.code === "number" && e.code >= 1e4 && e.code <= 99999) {
2529
2404
  const { origin } = e;
2530
- return (0, import_hwk_adapter_core3.failure)(
2405
+ return (0, import_hwk_adapter_core4.failure)(
2531
2406
  e.code,
2532
2407
  e.message ?? "Unknown error",
2533
2408
  e.params,
2534
2409
  origin === "device" || origin === "transport" || origin === "host" ? origin : void 0,
2535
- (0, import_hwk_adapter_core3.isHwkRecoveryHint)(e.recovery) ? e.recovery : void 0
2410
+ (0, import_hwk_adapter_core4.isHwkRecoveryHint)(e.recovery) ? e.recovery : void 0
2536
2411
  );
2537
2412
  }
2538
- if (e._tag === import_hwk_adapter_core3.UI_REQUEST_CANCELLED_TAG || e._tag === import_hwk_adapter_core3.UI_REQUEST_PREEMPTED_TAG) {
2539
- return (0, import_hwk_adapter_core3.failure)(
2540
- import_hwk_adapter_core3.HardwareErrorCode.UserAborted,
2413
+ if (e._tag === import_hwk_adapter_core4.UI_REQUEST_CANCELLED_TAG || e._tag === import_hwk_adapter_core4.UI_REQUEST_PREEMPTED_TAG) {
2414
+ return (0, import_hwk_adapter_core4.failure)(
2415
+ import_hwk_adapter_core4.HardwareErrorCode.UserAborted,
2541
2416
  e.message ?? "Keystone QR operation was cancelled"
2542
2417
  );
2543
2418
  }
2544
- if (e._tag === import_hwk_adapter_core3.UI_REQUEST_TIMEOUT_TAG) {
2545
- return (0, import_hwk_adapter_core3.failure)(
2546
- import_hwk_adapter_core3.HardwareErrorCode.OperationTimeout,
2419
+ if (e._tag === import_hwk_adapter_core4.UI_REQUEST_TIMEOUT_TAG) {
2420
+ return (0, import_hwk_adapter_core4.failure)(
2421
+ import_hwk_adapter_core4.HardwareErrorCode.OperationTimeout,
2547
2422
  e.message ?? "Keystone QR operation timed out"
2548
2423
  );
2549
2424
  }
2550
2425
  }
2551
2426
  const message = err instanceof Error ? err.message : String(err);
2552
2427
  const params = err instanceof Error && err.stack ? { stack: err.stack } : void 0;
2553
- return (0, import_hwk_adapter_core3.failure)(import_hwk_adapter_core3.HardwareErrorCode.UnknownError, message, params);
2428
+ return (0, import_hwk_adapter_core4.failure)(import_hwk_adapter_core4.HardwareErrorCode.UnknownError, message, params);
2554
2429
  }
2555
2430
  };
2556
2431
  // Annotate the CommonJS export names for ESM import in node: