@onekeyfe/hwk-keystone-adapter 1.2.3-alpha.8 → 1.2.5-alpha.1

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