@onekeyfe/hwk-adapter-core 1.2.2-alpha.8 → 1.2.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/chunk-3M3GMF64.mjs +68 -0
  2. package/dist/chunk-3M3GMF64.mjs.map +1 -0
  3. package/dist/{chunk-67BP77GT.mjs → chunk-72KOSYTH.mjs} +145 -56
  4. package/dist/chunk-72KOSYTH.mjs.map +1 -0
  5. package/dist/chunk-7SFFS2NL.js +68 -0
  6. package/dist/chunk-7SFFS2NL.js.map +1 -0
  7. package/dist/{chunk-SK7ZF33J.js → chunk-IZEVYMJK.js} +145 -56
  8. package/dist/chunk-IZEVYMJK.js.map +1 -0
  9. package/dist/{chunk-U63HTLGJ.mjs → chunk-KCIK56B5.mjs} +31 -2
  10. package/dist/chunk-KCIK56B5.mjs.map +1 -0
  11. package/dist/{chunk-BAGEFFFY.js → chunk-RRCFH7X6.js} +32 -3
  12. package/dist/chunk-RRCFH7X6.js.map +1 -0
  13. package/dist/errors-ChA_YEZY.d.mts +296 -0
  14. package/dist/errors-ChA_YEZY.d.ts +296 -0
  15. package/dist/errors.d.mts +1 -205
  16. package/dist/errors.d.ts +1 -205
  17. package/dist/errors.js +8 -2
  18. package/dist/errors.js.map +1 -1
  19. package/dist/errors.mjs +9 -3
  20. package/dist/index.d.mts +530 -57
  21. package/dist/index.d.ts +530 -57
  22. package/dist/index.js +600 -58
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +591 -49
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/{ui-events-Cb7haAeE.d.ts → ui-events-BXI9BBpx.d.mts} +19 -3
  27. package/dist/{ui-events-Cb7haAeE.d.mts → ui-events-BXI9BBpx.d.ts} +19 -3
  28. package/dist/ui-events.d.mts +1 -1
  29. package/dist/ui-events.d.ts +1 -1
  30. package/dist/ui-events.js +3 -2
  31. package/dist/ui-events.js.map +1 -1
  32. package/dist/ui-events.mjs +2 -1
  33. package/package.json +2 -2
  34. package/dist/chunk-67BP77GT.mjs.map +0 -1
  35. package/dist/chunk-BAGEFFFY.js.map +0 -1
  36. package/dist/chunk-SK7ZF33J.js.map +0 -1
  37. package/dist/chunk-U63HTLGJ.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,9 +1,6 @@
1
1
  import {
2
- HardwareErrorCode,
3
- ORPHAN_ELIGIBLE_ERROR_CODES,
4
- createHwkError,
5
2
  enrichErrorMessage
6
- } from "./chunk-67BP77GT.mjs";
3
+ } from "./chunk-3M3GMF64.mjs";
7
4
  import {
8
5
  UI_EVENT,
9
6
  UI_REQUEST,
@@ -13,19 +10,40 @@ import {
13
10
  UI_REQUEST_TIMEOUT_TAG,
14
11
  UI_RESPONSE,
15
12
  UiRequestRegistry
16
- } from "./chunk-U63HTLGJ.mjs";
13
+ } from "./chunk-KCIK56B5.mjs";
14
+ import {
15
+ HardwareErrorCode,
16
+ ORPHAN_ELIGIBLE_ERROR_CODES,
17
+ createHwkError,
18
+ defaultOriginForCode,
19
+ defaultRecoveryForCode,
20
+ isHwkRecoveryHint,
21
+ operationMayHaveCompletedParams
22
+ } from "./chunk-72KOSYTH.mjs";
17
23
 
18
24
  // src/types/response.ts
19
25
  function success(payload) {
20
26
  return { success: true, payload };
21
27
  }
22
- function failure(code, error, params) {
28
+ function failure(code, error, params, origin, recovery) {
23
29
  return {
24
30
  success: false,
25
- payload: { error, code, ...params ? { params } : {} }
31
+ payload: {
32
+ error,
33
+ code,
34
+ ...origin !== void 0 ? { origin } : {},
35
+ recovery: recovery ?? defaultRecoveryForCode(code),
36
+ ...params ? { params } : {}
37
+ }
26
38
  };
27
39
  }
28
40
 
41
+ // src/types/device.ts
42
+ function resolveSearchTargetReusePolicy(device) {
43
+ if (device.capabilities?.persistentDeviceIdentity) return "reconnectable";
44
+ return "current-discovery";
45
+ }
46
+
29
47
  // src/types/fingerprint.ts
30
48
  import { sha256 } from "@noble/hashes/sha256";
31
49
  import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
@@ -35,11 +53,21 @@ var CHAIN_FINGERPRINT_PATHS = {
35
53
  // Cointype 1 (testnet) is rejected by some Ledger BTC App configurations.
36
54
  btc: "m/44'/0'/0'",
37
55
  sol: "m/44'/501'/0'",
38
- tron: "m/44'/195'/0'/0/0"
56
+ tron: "m/44'/195'/0'/0/0",
57
+ // Shielded UA (single Orchard receiver) derived from the transparent path.
58
+ zcash: "m/44'/133'/0'/0/0"
39
59
  };
60
+ function parseBip32MasterFingerprint(value) {
61
+ if (typeof value !== "string") return void 0;
62
+ const normalized = value.trim().toLowerCase();
63
+ return /^[0-9a-f]{8}$/.test(normalized) ? normalized : void 0;
64
+ }
40
65
  function deriveDeviceFingerprint(value) {
41
66
  return bytesToHex(sha256(utf8ToBytes(value))).slice(0, 16);
42
67
  }
68
+ function deriveWalletId(canonicalPublicMaterial) {
69
+ return bytesToHex(sha256(utf8ToBytes(`onekey-hwk-wallet-id:v1:${canonicalPublicMaterial}`)));
70
+ }
43
71
 
44
72
  // src/events/device.ts
45
73
  var DEVICE_EVENT = "DEVICE_EVENT";
@@ -48,6 +76,10 @@ var DEVICE = {
48
76
  DISCONNECT: "device-disconnect",
49
77
  CHANGED: "device-changed",
50
78
  FEATURES: "features",
79
+ /** Trezor transport binding, emitted only after matching the expected firmware device_id. */
80
+ TREZOR_CONNECTION_VERIFIED: "device-trezor-connection-verified",
81
+ /** Ledger BLE address repair, emitted only after matching the stored chain fingerprint. */
82
+ LEDGER_CONNECTION_VERIFIED: "device-ledger-connection-verified",
51
83
  /**
52
84
  * Trezor-only. Emitted after a successful THP handshake that minted or
53
85
  * refreshed pairing credentials. The host should persist `credentials`
@@ -66,7 +98,8 @@ var SDK = {
66
98
  DEVICE_STUCK: "device-stuck",
67
99
  DEVICE_UNRESPONSIVE: "device-unresponsive",
68
100
  DEVICE_RECOVERED: "device-recovered",
69
- DEVICE_INTERACTION: "device-interaction"
101
+ DEVICE_INTERACTION: "device-interaction",
102
+ INTERACTION_ENDED: "interaction-ended"
70
103
  };
71
104
 
72
105
  // src/utils/DeviceJobQueue.ts
@@ -175,6 +208,236 @@ var DeviceJobQueue = class {
175
208
  }
176
209
  };
177
210
 
211
+ // src/utils/hardwareRuntimeId.ts
212
+ import { bytesToHex as bytesToHex2, randomBytes } from "@noble/hashes/utils";
213
+ var HARDWARE_RUNTIME_ID_PREFIX = "hwk:runtime:v1";
214
+ var RANDOM_ID_BYTES = 16;
215
+ var RANDOM_ID_PATTERN = /^[0-9a-f]{32}$/;
216
+ var VENDORS = /* @__PURE__ */ new Set(["trezor", "ledger", "keystone"]);
217
+ var CONNECTION_TYPES = /* @__PURE__ */ new Set(["usb", "ble", "qr"]);
218
+ function createNonce() {
219
+ return bytesToHex2(randomBytes(RANDOM_ID_BYTES));
220
+ }
221
+ function createHardwareInteractionId(vendor) {
222
+ return `${HARDWARE_RUNTIME_ID_PREFIX}:interaction:${vendor}:${createNonce()}`;
223
+ }
224
+ function createHardwareSearchTargetId(params) {
225
+ return `${HARDWARE_RUNTIME_ID_PREFIX}:search-target:${params.vendor}:${params.connectionType}:${createNonce()}`;
226
+ }
227
+ function createHardwareConnectorSessionId(params) {
228
+ return `${HARDWARE_RUNTIME_ID_PREFIX}:connector-session:${params.vendor}:${params.connectionType}:${createNonce()}`;
229
+ }
230
+ function hasHardwareRuntimeIdPrefix(value) {
231
+ return typeof value === "string" && value.startsWith(`${HARDWARE_RUNTIME_ID_PREFIX}:`);
232
+ }
233
+ function parseHardwareRuntimeId(value) {
234
+ if (!hasHardwareRuntimeIdPrefix(value)) return void 0;
235
+ const parts = value.split(":");
236
+ const kind = parts[3];
237
+ const vendor = parts[4];
238
+ if (!VENDORS.has(vendor)) return void 0;
239
+ if (kind === "interaction" && parts.length === 6 && RANDOM_ID_PATTERN.test(parts[5])) {
240
+ return {
241
+ kind,
242
+ vendor,
243
+ nonce: parts[5]
244
+ };
245
+ }
246
+ const connectionType = parts[5];
247
+ if ((kind === "search-target" || kind === "connector-session") && parts.length === 7 && CONNECTION_TYPES.has(connectionType) && RANDOM_ID_PATTERN.test(parts[6])) {
248
+ return {
249
+ kind,
250
+ vendor,
251
+ connectionType,
252
+ nonce: parts[6]
253
+ };
254
+ }
255
+ return void 0;
256
+ }
257
+ function isHardwareInteractionId(value) {
258
+ return parseHardwareRuntimeId(value)?.kind === "interaction";
259
+ }
260
+ function isHardwareSearchTargetId(value) {
261
+ return parseHardwareRuntimeId(value)?.kind === "search-target";
262
+ }
263
+
264
+ // src/utils/InteractionRegistry.ts
265
+ var INTERACTION_DEFAULT_TTL_MS = 6e5;
266
+ var InteractionRegistry = class {
267
+ constructor(options) {
268
+ this._active = /* @__PURE__ */ new Map();
269
+ this._ended = /* @__PURE__ */ new Map();
270
+ this._vendor = options.vendor;
271
+ this._ttlMs = options.ttlMs ?? INTERACTION_DEFAULT_TTL_MS;
272
+ this._onEnded = options.onEnded;
273
+ }
274
+ create(params) {
275
+ const now = Date.now();
276
+ const interactionId = createHardwareInteractionId(this._vendor);
277
+ const interaction = {
278
+ interactionId,
279
+ searchTargetId: params.searchTargetId,
280
+ connectId: params.connectId,
281
+ device: params.device,
282
+ connectionKeys: Array.from(
283
+ new Set(
284
+ [params.searchTargetId, params.connectId, ...params.connectionKeys ?? []].filter(
285
+ Boolean
286
+ )
287
+ )
288
+ ),
289
+ createdAt: now,
290
+ lastActiveAt: now
291
+ };
292
+ const timer = this._createTimer(interactionId);
293
+ this._active.set(interactionId, { ...interaction, timer, retainCount: 0 });
294
+ return interaction;
295
+ }
296
+ resolve(interactionId) {
297
+ const active = this._active.get(interactionId);
298
+ if (!active) {
299
+ const ended = this._ended.get(interactionId);
300
+ if (ended) {
301
+ throw createHwkError({
302
+ code: 10113 /* InteractionEnded */,
303
+ message: `Hardware interaction has ended (${ended.reason})`,
304
+ params: { interactionId, reason: ended.reason }
305
+ });
306
+ }
307
+ throw createHwkError({
308
+ code: 10112 /* InteractionNotFound */,
309
+ message: "Hardware interaction was not found",
310
+ params: { interactionId }
311
+ });
312
+ }
313
+ if (active.timer) clearTimeout(active.timer);
314
+ active.lastActiveAt = Date.now();
315
+ active.timer = active.retainCount === 0 ? this._createTimer(interactionId) : void 0;
316
+ return active;
317
+ }
318
+ /** Keep an interaction alive while one device job is actively using it. */
319
+ retain(interactionId) {
320
+ const active = this.resolve(interactionId);
321
+ if (active.timer) clearTimeout(active.timer);
322
+ active.timer = void 0;
323
+ active.retainCount += 1;
324
+ let released = false;
325
+ return () => {
326
+ if (released) return;
327
+ released = true;
328
+ const current = this._active.get(interactionId);
329
+ if (!current) return;
330
+ current.retainCount = Math.max(0, current.retainCount - 1);
331
+ current.lastActiveAt = Date.now();
332
+ if (current.retainCount === 0) {
333
+ current.timer = this._createTimer(interactionId);
334
+ }
335
+ };
336
+ }
337
+ /** Return active or tombstoned binding data without refreshing its TTL. */
338
+ find(interactionId) {
339
+ return this._active.get(interactionId) ?? this._ended.get(interactionId)?.interaction;
340
+ }
341
+ findActiveByConnectionKey(connectionKey) {
342
+ if (!connectionKey) return void 0;
343
+ return [...this._active.values()].find(
344
+ (interaction) => interaction.connectionKeys.includes(connectionKey)
345
+ );
346
+ }
347
+ /** Replace the live transport binding after the same target was reconnected. */
348
+ rebind(interactionId, params) {
349
+ const active = this._active.get(interactionId);
350
+ if (!active) {
351
+ this.resolve(interactionId);
352
+ throw new Error("Unreachable interaction rebind");
353
+ }
354
+ if (active.timer) clearTimeout(active.timer);
355
+ active.connectId = params.connectId;
356
+ active.device = params.device;
357
+ active.connectionKeys = Array.from(
358
+ new Set([params.connectId, ...params.connectionKeys ?? []].filter(Boolean))
359
+ );
360
+ active.lastActiveAt = Date.now();
361
+ active.timer = active.retainCount === 0 ? this._createTimer(interactionId) : void 0;
362
+ return active;
363
+ }
364
+ end(interactionId, reason) {
365
+ const active = this._active.get(interactionId);
366
+ if (!active) return void 0;
367
+ if (active.timer) clearTimeout(active.timer);
368
+ this._active.delete(interactionId);
369
+ this._rememberEnded(active, reason);
370
+ this._onEnded?.(active, reason);
371
+ return active;
372
+ }
373
+ endByConnectionKey(connectionKey, reason, exceptInteractionId) {
374
+ if (!connectionKey) return;
375
+ for (const interaction of [...this._active.values()]) {
376
+ if (interaction.interactionId === exceptInteractionId) continue;
377
+ if (interaction.connectionKeys.includes(connectionKey)) {
378
+ this.end(interaction.interactionId, reason);
379
+ }
380
+ }
381
+ }
382
+ endAll(reason, exceptInteractionId) {
383
+ for (const interactionId of [...this._active.keys()]) {
384
+ if (interactionId === exceptInteractionId) continue;
385
+ this.end(interactionId, reason);
386
+ }
387
+ }
388
+ _createTimer(interactionId) {
389
+ const timer = setTimeout(() => {
390
+ this.end(interactionId, "timeout");
391
+ }, this._ttlMs);
392
+ timer.unref?.();
393
+ return timer;
394
+ }
395
+ _rememberEnded(interaction, reason) {
396
+ this._ended.set(interaction.interactionId, { reason, interaction });
397
+ if (this._ended.size <= 100) return;
398
+ const oldest = this._ended.keys().next().value;
399
+ if (oldest) this._ended.delete(oldest);
400
+ }
401
+ };
402
+
403
+ // src/utils/hardwareOperationTarget.ts
404
+ function resolveHardwareOperationTarget(positionalTargetId, commonInteractionId, expectedVendor) {
405
+ const normalizedPositionalTargetId = positionalTargetId ?? void 0;
406
+ const normalizedCommonInteractionId = commonInteractionId || void 0;
407
+ const positionalInteractionId = isHardwareInteractionId(normalizedPositionalTargetId) ? normalizedPositionalTargetId : void 0;
408
+ const parsedPositionalTarget = parseHardwareRuntimeId(normalizedPositionalTargetId);
409
+ const parsedCommonInteraction = parseHardwareRuntimeId(normalizedCommonInteractionId);
410
+ if (normalizedPositionalTargetId && hasHardwareRuntimeIdPrefix(normalizedPositionalTargetId) && !parsedPositionalTarget) {
411
+ return failure(10002 /* InvalidParams */, "Invalid hardware operation target id");
412
+ }
413
+ if (parsedPositionalTarget?.kind === "connector-session") {
414
+ return failure(
415
+ 10002 /* InvalidParams */,
416
+ "Hardware connector session id cannot be used as an operation target"
417
+ );
418
+ }
419
+ if (normalizedCommonInteractionId && parsedCommonInteraction?.kind !== "interaction") {
420
+ return failure(10002 /* InvalidParams */, "Invalid hardware interaction id");
421
+ }
422
+ if (expectedVendor && (parsedPositionalTarget && parsedPositionalTarget.vendor !== expectedVendor || parsedCommonInteraction && parsedCommonInteraction.vendor !== expectedVendor)) {
423
+ return failure(
424
+ 10002 /* InvalidParams */,
425
+ `Hardware interaction does not belong to ${expectedVendor}`
426
+ );
427
+ }
428
+ if (positionalInteractionId && normalizedCommonInteractionId && positionalInteractionId !== normalizedCommonInteractionId) {
429
+ return failure(10002 /* InvalidParams */, "Conflicting hardware interaction ids", {
430
+ positionalInteractionId,
431
+ commonInteractionId: normalizedCommonInteractionId
432
+ });
433
+ }
434
+ const interactionId = normalizedCommonInteractionId ?? positionalInteractionId;
435
+ return success({
436
+ interactionId,
437
+ targetId: interactionId ?? normalizedPositionalTargetId
438
+ });
439
+ }
440
+
178
441
  // src/types/connector.ts
179
442
  var EConnectorInteraction = /* @__PURE__ */ ((EConnectorInteraction2) => {
180
443
  EConnectorInteraction2["Searching"] = "searching";
@@ -190,7 +453,7 @@ function createBridgedConnector(vendor, connectionType, bridge) {
190
453
  return {
191
454
  connectionType,
192
455
  searchDevices: (options) => bridge.searchDevices({ vendor, options }),
193
- connect: (deviceId) => bridge.connect({ vendor, deviceId }),
456
+ connect: (deviceId, options) => bridge.connect({ vendor, deviceId, ...options ? { options } : {} }),
194
457
  disconnect: (sessionId) => bridge.disconnect({ vendor, sessionId }),
195
458
  call: (sessionId, method, callParams) => bridge.call({ vendor, sessionId, method, callParams }),
196
459
  cancel: (sessionId) => bridge.cancel({ vendor, sessionId }),
@@ -331,10 +594,14 @@ function createCombinedConnector(connectors) {
331
594
  });
332
595
  }
333
596
  const searchDevices = async (options = {}) => {
597
+ const selectedConnectors = connectors.map((child, index) => ({ child, index })).filter(
598
+ ({ child }) => !options.transportType || child.connectionType === options.transportType
599
+ );
600
+ if (!selectedConnectors.length) return [];
334
601
  const perConnector = [];
335
- await new Promise((resolve) => {
602
+ await new Promise((resolve, reject) => {
336
603
  let finished = false;
337
- let remaining = connectors.length;
604
+ let remaining = selectedConnectors.length;
338
605
  let settleTimer;
339
606
  const finish = () => {
340
607
  if (finished) return;
@@ -342,19 +609,20 @@ function createCombinedConnector(connectors) {
342
609
  if (settleTimer) clearTimeout(settleTimer);
343
610
  resolve();
344
611
  };
345
- connectors.forEach((child, index) => {
346
- void child.searchDevices().then(
612
+ selectedConnectors.forEach(({ child, index }) => {
613
+ void child.searchDevices(options.purpose ? { purpose: options.purpose } : void 0).then(
347
614
  (devices) => devices.map((device) => ({
348
615
  ...device,
349
616
  connectionType: device.connectionType ?? child.connectionType
350
617
  }))
351
- ).catch(
352
- () => (
353
- // A transport that can't scan (powered off, unauthorized, absent)
354
- // must not fail the whole fused search.
355
- []
356
- )
357
- ).then((devices) => {
618
+ ).catch((error) => {
619
+ if (options.transportType) {
620
+ finished = true;
621
+ if (settleTimer) clearTimeout(settleTimer);
622
+ reject(error);
623
+ }
624
+ return [];
625
+ }).then((devices) => {
358
626
  remaining -= 1;
359
627
  if (!finished) {
360
628
  perConnector.push({ index, devices });
@@ -371,7 +639,12 @@ function createCombinedConnector(connectors) {
371
639
  });
372
640
  });
373
641
  perConnector.sort((a, b) => a.index - b.index);
374
- deviceOwner.clear();
642
+ if (!options.transportType) deviceOwner.clear();
643
+ else {
644
+ for (const [id, owner] of deviceOwner) {
645
+ if (owner.connectionType === options.transportType) deviceOwner.delete(id);
646
+ }
647
+ }
375
648
  const merged = [];
376
649
  perConnector.forEach(({ index, devices }) => {
377
650
  for (const device of devices) {
@@ -381,11 +654,11 @@ function createCombinedConnector(connectors) {
381
654
  });
382
655
  return merged.sort((a, b) => rank(a) - rank(b));
383
656
  };
384
- const resolveOwner = async (deviceId) => {
385
- if (deviceId && deviceOwner.has(deviceId)) {
657
+ const resolveOwner = async (deviceId, transportType) => {
658
+ if (deviceId && deviceOwner.has(deviceId) && (!transportType || deviceOwner.get(deviceId)?.connectionType === transportType)) {
386
659
  return { owner: deviceOwner.get(deviceId), deviceId };
387
660
  }
388
- const devices = await searchDevices({ waitForAll: Boolean(deviceId) });
661
+ const devices = await searchDevices({ waitForAll: Boolean(deviceId), transportType });
389
662
  if (deviceId) {
390
663
  const owner = deviceOwner.get(deviceId);
391
664
  if (!owner)
@@ -396,13 +669,14 @@ function createCombinedConnector(connectors) {
396
669
  if (!first) throw combinedDeviceNotFoundError("Combined connector: no devices found");
397
670
  return { owner: deviceOwner.get(first.connectId), deviceId: first.connectId };
398
671
  };
399
- const connectByExplicitId = async (deviceId) => {
672
+ const connectByExplicitId = async (deviceId, transportType) => {
400
673
  const cachedOwner = deviceOwner.get(deviceId);
401
- if (cachedOwner) {
674
+ if (cachedOwner && (!transportType || cachedOwner.connectionType === transportType)) {
402
675
  return { session: await cachedOwner.connect(deviceId), owner: cachedOwner };
403
676
  }
404
677
  let lastError;
405
678
  for (const child of connectors) {
679
+ if (transportType && child.connectionType !== transportType) continue;
406
680
  try {
407
681
  const session = await child.connect(deviceId);
408
682
  deviceOwner.set(deviceId, child);
@@ -424,14 +698,15 @@ function createCombinedConnector(connectors) {
424
698
  // Nominal value only — the per-device `connectionType` is authoritative
425
699
  // for a fused connector.
426
700
  connectionType: connectors[0].connectionType,
427
- searchDevices: (options) => searchDevices({ waitForAll: options?.waitForAll }),
428
- connect: async (deviceId) => {
701
+ availableTransports: [...new Set(connectors.map((child) => child.connectionType))],
702
+ searchDevices: (options) => searchDevices(options),
703
+ connect: async (deviceId, options) => {
429
704
  let owner;
430
705
  let session;
431
706
  if (deviceId) {
432
- ({ session, owner } = await connectByExplicitId(deviceId));
707
+ ({ session, owner } = await connectByExplicitId(deviceId, options?.transportType));
433
708
  } else {
434
- const resolved = await resolveOwner();
709
+ const resolved = await resolveOwner(void 0, options?.transportType);
435
710
  owner = resolved.owner;
436
711
  session = await owner.connect(resolved.deviceId);
437
712
  }
@@ -537,6 +812,130 @@ var TypedEventEmitter = class {
537
812
  }
538
813
  };
539
814
 
815
+ // src/utils/requestSaveDeviceBinding.ts
816
+ async function requestSaveDeviceBinding(emitter, registry, binding, signal) {
817
+ const type = UI_REQUEST.REQUEST_SAVE_DEVICE_BINDING;
818
+ if (!emitter.listenerCount(type)) return false;
819
+ if (signal?.aborted) throw signal.reason;
820
+ const requestId = registry.createRequestId();
821
+ const pending = registry.wait(type, { requestId });
822
+ void pending.catch(() => void 0);
823
+ const cancel = () => registry.cancel(type, requestId);
824
+ signal?.addEventListener("abort", cancel, { once: true });
825
+ try {
826
+ emitter.emit(type, { type, payload: { ...binding, requestId } });
827
+ const response = await pending;
828
+ if (signal?.aborted) throw signal.reason;
829
+ if (response?.saved !== true) {
830
+ throw createHwkError({
831
+ code: 1e4 /* UnknownError */,
832
+ message: "Verified device binding could not be saved"
833
+ });
834
+ }
835
+ emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
836
+ type: UI_REQUEST.DEVICE_BINDING_STATUS,
837
+ payload: { selectionRequestId: binding.selectionRequestId, status: "saved" }
838
+ });
839
+ return true;
840
+ } catch (error) {
841
+ emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
842
+ type: UI_REQUEST.DEVICE_BINDING_STATUS,
843
+ payload: {
844
+ selectionRequestId: binding.selectionRequestId,
845
+ status: signal?.aborted ? "cancelled" : "failed"
846
+ }
847
+ });
848
+ throw error;
849
+ } finally {
850
+ signal?.removeEventListener("abort", cancel);
851
+ registry.cancel(type, requestId);
852
+ }
853
+ }
854
+
855
+ // src/utils/requestBleDeviceSelection.ts
856
+ async function requestBleDeviceSelection({
857
+ emitter,
858
+ registry,
859
+ request,
860
+ scan,
861
+ signal,
862
+ pollIntervalMs = 1500
863
+ }) {
864
+ const type = UI_REQUEST.REQUEST_SELECT_DEVICE;
865
+ if (signal.aborted) throw signal.reason;
866
+ if (!emitter.listenerCount(type)) {
867
+ throw createHwkError({
868
+ code: 10100 /* DeviceNotFound */,
869
+ message: "Select a Bluetooth device before continuing"
870
+ });
871
+ }
872
+ const requestId = registry.createRequestId();
873
+ let { devices } = request;
874
+ let stopped = false;
875
+ let timer;
876
+ let wake;
877
+ const stop = () => {
878
+ stopped = true;
879
+ if (timer !== void 0) clearTimeout(timer);
880
+ wake?.();
881
+ };
882
+ const waitForNextScan = () => new Promise((resolve) => {
883
+ wake = resolve;
884
+ timer = setTimeout(resolve, pollIntervalMs);
885
+ if (stopped) resolve();
886
+ });
887
+ const cancel = () => registry.cancel(type, requestId);
888
+ const reply = registry.wait(type, { requestId }).finally(stop);
889
+ void reply.catch(() => void 0);
890
+ const publish = () => emitter.emit(type, {
891
+ type,
892
+ payload: { ...request, devices, requestId, scanning: true }
893
+ });
894
+ signal.addEventListener("abort", cancel, { once: true });
895
+ let polling;
896
+ try {
897
+ publish();
898
+ polling = (async () => {
899
+ await Promise.resolve();
900
+ while (!stopped) {
901
+ const snapshot = await scan();
902
+ if (stopped || signal.aborted) return;
903
+ devices = snapshot;
904
+ publish();
905
+ if (stopped) return;
906
+ await waitForNextScan();
907
+ }
908
+ })();
909
+ const selected = await Promise.race([reply, polling.then(() => reply)]);
910
+ stop();
911
+ await polling;
912
+ if (signal.aborted) throw signal.reason;
913
+ const device = devices.find((candidate) => candidate.connectId === selected.sdkConnectId);
914
+ if (!device) {
915
+ throw createHwkError({
916
+ code: 10100 /* DeviceNotFound */,
917
+ message: "Selected Bluetooth device is no longer available"
918
+ });
919
+ }
920
+ emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
921
+ type: UI_REQUEST.DEVICE_BINDING_STATUS,
922
+ payload: { selectionRequestId: requestId, status: "verifying" }
923
+ });
924
+ return { device, requestId };
925
+ } catch (error) {
926
+ emitter.emit(UI_REQUEST.DEVICE_BINDING_STATUS, {
927
+ type: UI_REQUEST.DEVICE_BINDING_STATUS,
928
+ payload: { selectionRequestId: requestId, status: signal.aborted ? "cancelled" : "failed" }
929
+ });
930
+ throw error;
931
+ } finally {
932
+ stop();
933
+ signal.removeEventListener("abort", cancel);
934
+ registry.cancel(type, requestId);
935
+ await polling?.catch(() => void 0);
936
+ }
937
+ }
938
+
540
939
  // src/utils/semver.ts
541
940
  function compareSemver(a, b) {
542
941
  const pa = a.split(".").map(Number);
@@ -564,14 +963,96 @@ function padHex64(hex) {
564
963
  function hexToBytes(hex) {
565
964
  return nobleHexToBytes(stripHex(hex));
566
965
  }
567
- function bytesToHex2(bytes) {
966
+ function bytesToHex3(bytes) {
568
967
  return nobleBytesToHex(bytes);
569
968
  }
570
969
 
970
+ // src/utils/solanaOffchainMessage.ts
971
+ var SOLANA_OFFCHAIN_SIGNING_DOMAIN = Uint8Array.of(
972
+ 255,
973
+ ...Array.from("solana offchain", (character) => character.charCodeAt(0))
974
+ );
975
+ var SOLANA_PUBLIC_KEY_LENGTH = 32;
976
+ var MAX_SOLANA_OFFCHAIN_SIGNERS = 255;
977
+ function compareBytes(a, b) {
978
+ for (let index = 0; index < a.length; index += 1) {
979
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
980
+ if (difference !== 0) return difference;
981
+ }
982
+ return a.length - b.length;
983
+ }
984
+ function bytesEqual(a, b) {
985
+ return a.length === b.length && a.every((value, index) => value === b[index]);
986
+ }
987
+ function decodeUtf8Strict(bytes) {
988
+ let encoded = "";
989
+ for (const value of bytes) {
990
+ encoded += `%${value.toString(16).padStart(2, "0")}`;
991
+ }
992
+ return decodeURIComponent(encoded);
993
+ }
994
+ function prepareSolanaOffchainMessageV1({
995
+ message,
996
+ requiredSigners
997
+ }) {
998
+ if (message.length === 0) {
999
+ throw new Error("Solana off-chain message cannot be empty");
1000
+ }
1001
+ if (requiredSigners.length === 0 || requiredSigners.length > MAX_SOLANA_OFFCHAIN_SIGNERS) {
1002
+ throw new Error("Solana off-chain message requires between 1 and 255 signers");
1003
+ }
1004
+ let messageText;
1005
+ try {
1006
+ messageText = decodeUtf8Strict(message);
1007
+ } catch {
1008
+ throw new Error("Solana off-chain message must contain valid UTF-8");
1009
+ }
1010
+ const signerBytes = requiredSigners.map((signer, index) => {
1011
+ let bytes;
1012
+ try {
1013
+ bytes = hexToBytes(signer);
1014
+ } catch {
1015
+ throw new Error(`Solana off-chain signer ${index} must be a hex public key`);
1016
+ }
1017
+ if (bytes.length !== SOLANA_PUBLIC_KEY_LENGTH) {
1018
+ throw new Error(`Solana off-chain signer ${index} must be 32 bytes`);
1019
+ }
1020
+ return bytes;
1021
+ });
1022
+ signerBytes.sort(compareBytes);
1023
+ for (let index = 1; index < signerBytes.length; index += 1) {
1024
+ if (bytesEqual(signerBytes[index - 1], signerBytes[index])) {
1025
+ throw new Error("Solana off-chain signers must be unique");
1026
+ }
1027
+ }
1028
+ const serializedMessage = new Uint8Array(
1029
+ SOLANA_OFFCHAIN_SIGNING_DOMAIN.length + 2 + signerBytes.length * SOLANA_PUBLIC_KEY_LENGTH + message.length
1030
+ );
1031
+ let offset = 0;
1032
+ serializedMessage.set(SOLANA_OFFCHAIN_SIGNING_DOMAIN, offset);
1033
+ offset += SOLANA_OFFCHAIN_SIGNING_DOMAIN.length;
1034
+ serializedMessage[offset] = 1;
1035
+ offset += 1;
1036
+ serializedMessage[offset] = signerBytes.length;
1037
+ offset += 1;
1038
+ for (const signer of signerBytes) {
1039
+ serializedMessage.set(signer, offset);
1040
+ offset += SOLANA_PUBLIC_KEY_LENGTH;
1041
+ }
1042
+ serializedMessage.set(message, offset);
1043
+ return {
1044
+ messageText,
1045
+ requiredSigners: signerBytes.map(bytesToHex3),
1046
+ serializedMessage
1047
+ };
1048
+ }
1049
+
571
1050
  // src/utils/deviceIdentity.ts
572
1051
  var ONEKEY_BLE_SERVICE_UUID = "00000001-0000-1000-8000-00805f9b34fb";
573
1052
  var TREZOR_BLE_SERVICE_UUID = "8c000001-a59b-4d58-a9ad-073df69fa1b1";
574
1053
  var LEDGER_USB_VENDOR_ID = 11415;
1054
+ var KEYSTONE_USB_VENDOR_ID = 4617;
1055
+ var KEYSTONE_USB_PRODUCT_ID = 12289;
575
1056
  var lower = (value) => value?.trim().toLowerCase() ?? "";
576
1057
  var hasServiceUuid = (input, uuid) => {
577
1058
  const expected = uuid.toLowerCase();
@@ -594,6 +1075,11 @@ function detectHardwareVendorFromDescriptor(input) {
594
1075
  if (typeof input.vendor === "number" && input.vendor === LEDGER_USB_VENDOR_ID || input.vendorId === LEDGER_USB_VENDOR_ID || manufacturerName.includes("ledger")) {
595
1076
  return "ledger";
596
1077
  }
1078
+ const usbVendorId = typeof input.vendor === "number" ? input.vendor : input.vendorId;
1079
+ const usbProductId = typeof input.product === "number" ? input.product : input.productId;
1080
+ if (usbVendorId === KEYSTONE_USB_VENDOR_ID && usbProductId === KEYSTONE_USB_PRODUCT_ID || manufacturerName.includes("keystone")) {
1081
+ return "keystone";
1082
+ }
597
1083
  return void 0;
598
1084
  }
599
1085
  function isKnownNonTargetHardwareVendor(input, targetVendor) {
@@ -617,28 +1103,62 @@ async function batchCall(params, callFn, onProgress) {
617
1103
 
618
1104
  // src/utils/methodCatalog.ts
619
1105
  var HARDWARE_METHOD_CATALOG = {
620
- evmGetAddress: { chain: "evm", allNetwork: true },
621
- evmSignTransaction: { chain: "evm", allNetwork: false },
622
- evmSignMessage: { chain: "evm", allNetwork: false },
623
- evmSignTypedData: { chain: "evm", allNetwork: false },
624
- btcGetAddress: { chain: "btc", allNetwork: true },
625
- btcGetPublicKey: { chain: "btc", allNetwork: true },
626
- btcSignTransaction: { chain: "btc", allNetwork: false },
627
- btcSignPsbt: { chain: "btc", allNetwork: false },
628
- btcSignMessage: { chain: "btc", allNetwork: false },
629
- btcGetMasterFingerprint: { chain: "btc", allNetwork: false },
630
- solGetAddress: { chain: "sol", allNetwork: true },
631
- solSignTransaction: { chain: "sol", allNetwork: false },
632
- solSignMessage: { chain: "sol", allNetwork: false },
633
- tronGetAddress: { chain: "tron", allNetwork: true },
634
- tronSignTransaction: { chain: "tron", allNetwork: false },
635
- tronSignMessage: { chain: "tron", allNetwork: false }
1106
+ evmGetAddress: { chain: "evm", allNetwork: true, replayAfterTransportFailure: "safe" },
1107
+ evmSignTransaction: {
1108
+ chain: "evm",
1109
+ allNetwork: false,
1110
+ replayAfterTransportFailure: "unsafe"
1111
+ },
1112
+ evmSignMessage: { chain: "evm", allNetwork: false, replayAfterTransportFailure: "unsafe" },
1113
+ evmSignTypedData: { chain: "evm", allNetwork: false, replayAfterTransportFailure: "unsafe" },
1114
+ btcGetAddress: { chain: "btc", allNetwork: true, replayAfterTransportFailure: "safe" },
1115
+ btcGetPublicKey: { chain: "btc", allNetwork: true, replayAfterTransportFailure: "safe" },
1116
+ btcSignTransaction: {
1117
+ chain: "btc",
1118
+ allNetwork: false,
1119
+ replayAfterTransportFailure: "unsafe"
1120
+ },
1121
+ btcSignPsbt: { chain: "btc", allNetwork: false, replayAfterTransportFailure: "unsafe" },
1122
+ btcSignMessage: { chain: "btc", allNetwork: false, replayAfterTransportFailure: "unsafe" },
1123
+ btcGetMasterFingerprint: {
1124
+ chain: "btc",
1125
+ allNetwork: false,
1126
+ replayAfterTransportFailure: "safe"
1127
+ },
1128
+ solGetAddress: { chain: "sol", allNetwork: true, replayAfterTransportFailure: "safe" },
1129
+ solSignTransaction: {
1130
+ chain: "sol",
1131
+ allNetwork: false,
1132
+ replayAfterTransportFailure: "unsafe"
1133
+ },
1134
+ solSignMessage: { chain: "sol", allNetwork: false, replayAfterTransportFailure: "unsafe" },
1135
+ tronGetAddress: { chain: "tron", allNetwork: true, replayAfterTransportFailure: "safe" },
1136
+ tronSignTransaction: {
1137
+ chain: "tron",
1138
+ allNetwork: false,
1139
+ replayAfterTransportFailure: "unsafe"
1140
+ },
1141
+ tronSignMessage: { chain: "tron", allNetwork: false, replayAfterTransportFailure: "unsafe" },
1142
+ zcashGetFullViewingKey: {
1143
+ chain: "zcash",
1144
+ allNetwork: false,
1145
+ replayAfterTransportFailure: "safe"
1146
+ },
1147
+ zcashGetShieldedAddress: {
1148
+ chain: "zcash",
1149
+ allNetwork: false,
1150
+ replayAfterTransportFailure: "safe"
1151
+ }
636
1152
  };
637
1153
  var ALL_NETWORK_METHOD_NAMES = Object.entries(HARDWARE_METHOD_CATALOG).filter(([, metadata]) => metadata.allNetwork).map(([method]) => method);
638
1154
  var ALL_NETWORK_METHOD_SET = new Set(ALL_NETWORK_METHOD_NAMES);
639
1155
  function getHardwareMethodMetadata(method) {
640
1156
  return HARDWARE_METHOD_CATALOG[method];
641
1157
  }
1158
+ var SAFE_NON_CHAIN_METHODS = /* @__PURE__ */ new Set(["getFeatures", "authenticateDevice"]);
1159
+ function canReplayHardwareMethodAfterTransportFailure(method) {
1160
+ return getHardwareMethodMetadata(method)?.replayAfterTransportFailure === "safe" || SAFE_NON_CHAIN_METHODS.has(method);
1161
+ }
642
1162
  function isAllNetworkMethodName(method) {
643
1163
  return ALL_NETWORK_METHOD_SET.has(method);
644
1164
  }
@@ -731,7 +1251,10 @@ export {
731
1251
  DeviceJobQueue,
732
1252
  EConnectorInteraction,
733
1253
  HARDWARE_METHOD_CATALOG,
1254
+ HARDWARE_RUNTIME_ID_PREFIX,
734
1255
  HardwareErrorCode,
1256
+ INTERACTION_DEFAULT_TTL_MS,
1257
+ InteractionRegistry,
735
1258
  ORPHAN_ELIGIBLE_ERROR_CODES,
736
1259
  SDK,
737
1260
  TypedEventEmitter,
@@ -745,23 +1268,42 @@ export {
745
1268
  UiRequestRegistry,
746
1269
  batchCall,
747
1270
  buildUnsupportedMethodResponse,
748
- bytesToHex2 as bytesToHex,
1271
+ bytesToHex3 as bytesToHex,
1272
+ canReplayHardwareMethodAfterTransportFailure,
749
1273
  compareSemver,
750
1274
  createBridgedConnector,
751
1275
  createCombinedConnector,
1276
+ createHardwareConnectorSessionId,
1277
+ createHardwareInteractionId,
1278
+ createHardwareSearchTargetId,
752
1279
  createHwkError,
1280
+ defaultOriginForCode,
1281
+ defaultRecoveryForCode,
753
1282
  deriveDeviceFingerprint,
1283
+ deriveWalletId,
754
1284
  detectHardwareVendorFromDescriptor,
755
1285
  enrichErrorMessage,
756
1286
  ensure0x,
757
1287
  failure,
758
1288
  getAllNetworkMethodChain,
759
1289
  getHardwareMethodMetadata,
1290
+ hasHardwareRuntimeIdPrefix,
760
1291
  hexToBytes,
761
1292
  isAllNetworkMethodName,
1293
+ isHardwareInteractionId,
1294
+ isHardwareSearchTargetId,
1295
+ isHwkRecoveryHint,
762
1296
  isKnownNonTargetHardwareVendor,
1297
+ operationMayHaveCompletedParams,
763
1298
  padHex64,
1299
+ parseBip32MasterFingerprint,
1300
+ parseHardwareRuntimeId,
1301
+ prepareSolanaOffchainMessageV1,
764
1302
  rehydrateConnectorError,
1303
+ requestBleDeviceSelection,
1304
+ requestSaveDeviceBinding,
1305
+ resolveHardwareOperationTarget,
1306
+ resolveSearchTargetReusePolicy,
765
1307
  runAllNetworkGetAddress,
766
1308
  serializeConnectorError,
767
1309
  stripHex,