@onekeyfe/hwk-keystone-connector-usb 1.2.2-alpha.100

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.
@@ -0,0 +1,211 @@
1
+ // src/errors.ts
2
+ import { HardwareErrorCode, createHwkError } from "@onekeyfe/hwk-adapter-core";
3
+ import { Status } from "@keystonehq/hw-transport-error";
4
+ var DEVICE_STATUS_MAP = {
5
+ [Status.PRS_PARSING_REJECTED]: HardwareErrorCode.UserRejected,
6
+ [Status.PRS_PARSING_DISALLOWED]: HardwareErrorCode.DeviceLocked,
7
+ [Status.PRS_PARSING_MISMATCHED_WALLET]: HardwareErrorCode.DeviceMismatch,
8
+ [Status.PRS_EXPORT_ADDRESS_REJECTED]: HardwareErrorCode.UserRejected,
9
+ [Status.PRS_EXPORT_ADDRESS_DISALLOWED]: HardwareErrorCode.DeviceLocked
10
+ };
11
+ var CLIENT_STATUS_MAP = {
12
+ [Status.ERR_DEVICE_NOT_OPENED]: HardwareErrorCode.DeviceNotFound,
13
+ [Status.ERR_DEVICE_NOT_FOUND]: HardwareErrorCode.DeviceNotFound,
14
+ [Status.ERR_TIMEOUT]: HardwareErrorCode.OperationTimeout,
15
+ [Status.ERR_DATA_TOO_LARGE]: HardwareErrorCode.PayloadTooLarge,
16
+ [Status.ERR_NOT_SUPPORTED]: HardwareErrorCode.TransportNotAvailable
17
+ };
18
+ function mapKeystoneUsbError(err) {
19
+ if (err && typeof err === "object" && typeof err.code === "number") {
20
+ return err;
21
+ }
22
+ const statusCode = err && typeof err === "object" && typeof err.transportErrorCode === "number" ? err.transportErrorCode : void 0;
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ if (statusCode !== void 0) {
25
+ const code = DEVICE_STATUS_MAP[statusCode] ?? CLIENT_STATUS_MAP[statusCode] ?? HardwareErrorCode.TransportError;
26
+ return createHwkError({
27
+ code,
28
+ message,
29
+ params: { statusCode, details: err.details }
30
+ });
31
+ }
32
+ return createHwkError({ code: HardwareErrorCode.TransportError, message });
33
+ }
34
+
35
+ // src/KeystoneUsbConnectorBase.ts
36
+ import { UR, URDecoder, UREncoder } from "@ngraveio/bc-ur";
37
+ import { Actions } from "@keystonehq/hw-transport-usb";
38
+ import {
39
+ EConnectorInteraction,
40
+ HardwareErrorCode as HardwareErrorCode2,
41
+ TypedEventEmitter,
42
+ createHwkError as createHwkError2,
43
+ serializeConnectorError,
44
+ success
45
+ } from "@onekeyfe/hwk-adapter-core";
46
+ var DEFAULT_TIMEOUT_MS = 1e5;
47
+ function toUrEncoded(ur) {
48
+ return new UREncoder(new UR(Buffer.from(ur.urData, "hex"), ur.urType), Infinity).nextPart().toUpperCase();
49
+ }
50
+ function fromUrEncoded(encoded) {
51
+ const decoder = new URDecoder();
52
+ decoder.receivePart(encoded);
53
+ if (!decoder.isComplete() || !decoder.isSuccess()) {
54
+ throw new Error("Keystone USB response is not a complete, valid UR");
55
+ }
56
+ const ur = decoder.resultUR();
57
+ return { urType: ur.type, urData: ur.cbor.toString("hex") };
58
+ }
59
+ var KeystoneUsbConnectorBase = class {
60
+ constructor(transportClass, options) {
61
+ this.connectionType = "usb";
62
+ this.emitter = new TypedEventEmitter();
63
+ this.sessions = /* @__PURE__ */ new Map();
64
+ this.transportClass = transportClass;
65
+ this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
66
+ }
67
+ async searchDevices(_options) {
68
+ const devices = await this.transportClass.getKeystoneDevices();
69
+ return devices.map((device, index) => ({
70
+ connectId: `keystone-usb:${device.serialNumber ?? index}`,
71
+ deviceId: "",
72
+ name: device.productName ?? "Keystone",
73
+ connectionType: "usb",
74
+ capabilities: { persistentDeviceIdentity: true }
75
+ }));
76
+ }
77
+ async connect(deviceId) {
78
+ this.emitter.emit("ui-event", {
79
+ type: EConnectorInteraction.Searching,
80
+ payload: { sessionId: "" }
81
+ });
82
+ const transport = await this.transportClass.connect({ timeout: this.timeoutMs });
83
+ try {
84
+ const config = await this._readAppConfig(transport);
85
+ if (deviceId && config.mfp && config.mfp.toLowerCase() !== deviceId.toLowerCase()) {
86
+ throw createHwkError2({
87
+ code: HardwareErrorCode2.DeviceMismatch,
88
+ message: `Connected Keystone wallet (mfp ${config.mfp}) does not match the requested device (${deviceId})`
89
+ });
90
+ }
91
+ const sessionId = config.mfp || "keystone-usb";
92
+ this.sessions.set(sessionId, { transport, mfp: config.mfp });
93
+ const device = {
94
+ connectId: sessionId,
95
+ deviceId: config.mfp ?? "",
96
+ name: "Keystone",
97
+ connectionType: "usb",
98
+ capabilities: { persistentDeviceIdentity: true }
99
+ };
100
+ this.emitter.emit("device-connect", { device });
101
+ return { sessionId, deviceInfo: this._toDeviceInfo(device, config.version) };
102
+ } catch (err) {
103
+ throw mapKeystoneUsbError(err);
104
+ }
105
+ }
106
+ disconnect(sessionId) {
107
+ const session = this.sessions.get(sessionId);
108
+ this.sessions.delete(sessionId);
109
+ if (session) {
110
+ this.emitter.emit("device-disconnect", { connectId: sessionId });
111
+ }
112
+ return Promise.resolve();
113
+ }
114
+ async call(sessionId, method, params) {
115
+ const session = this.sessions.get(sessionId);
116
+ if (!session) {
117
+ return {
118
+ success: false,
119
+ error: serializeConnectorError(
120
+ createHwkError2({
121
+ code: HardwareErrorCode2.DeviceNotFound,
122
+ message: `Unknown Keystone USB session: ${sessionId}`
123
+ })
124
+ )
125
+ };
126
+ }
127
+ try {
128
+ switch (method) {
129
+ case "resolveUr": {
130
+ const { urType, urData } = params;
131
+ const encoded = toUrEncoded({ urType, urData });
132
+ const response = await session.transport.send(
133
+ Actions.CMD_RESOLVE_UR,
134
+ encoded
135
+ );
136
+ return success(fromUrEncoded(response.payload));
137
+ }
138
+ case "checkLockStatus": {
139
+ const response = await session.transport.send(
140
+ Actions.CMD_CHECK_LOCK_STATUS,
141
+ ""
142
+ );
143
+ return success({ locked: response.payload });
144
+ }
145
+ case "getAppConfig": {
146
+ const config = await this._readAppConfig(session.transport);
147
+ return success(config);
148
+ }
149
+ default:
150
+ return {
151
+ success: false,
152
+ error: serializeConnectorError(
153
+ createHwkError2({
154
+ code: HardwareErrorCode2.MethodNotSupported,
155
+ message: `Unknown Keystone USB connector method: ${method}`
156
+ })
157
+ )
158
+ };
159
+ }
160
+ } catch (err) {
161
+ return { success: false, error: serializeConnectorError(mapKeystoneUsbError(err)) };
162
+ }
163
+ }
164
+ // No protocol-level cancel exists over USB (verified: nothing in the SDK's
165
+ // public surface can interrupt an in-flight transferIn/transferOut) — the
166
+ // best this connector can do is stop waiting on its side; the underlying
167
+ // `send()` promise still settles (or times out) on its own.
168
+ cancel(_sessionId) {
169
+ return Promise.resolve();
170
+ }
171
+ // Keystone USB never relays PIN/passphrase through the host — entry always
172
+ // happens on the device's own touchscreen — so there is nothing to answer.
173
+ uiResponse(_response) {
174
+ }
175
+ on(event, handler) {
176
+ this.emitter.on(event, handler);
177
+ }
178
+ off(event, handler) {
179
+ this.emitter.off(event, handler);
180
+ }
181
+ reset() {
182
+ this.sessions.clear();
183
+ }
184
+ async _readAppConfig(transport) {
185
+ const response = await transport.send(
186
+ Actions.CMD_GET_DEVICE_VERSION,
187
+ ""
188
+ );
189
+ return {
190
+ version: typeof response.firmwareVersion === "string" ? response.firmwareVersion : void 0,
191
+ mfp: typeof response.walletMFP === "string" ? response.walletMFP.toLowerCase() : void 0
192
+ };
193
+ }
194
+ _toDeviceInfo(device, firmwareVersion) {
195
+ return {
196
+ vendor: "keystone",
197
+ model: "unknown",
198
+ firmwareVersion: firmwareVersion ?? "0.0.0",
199
+ deviceId: device.deviceId,
200
+ connectId: device.connectId,
201
+ connectionType: "usb",
202
+ capabilities: { persistentDeviceIdentity: true }
203
+ };
204
+ }
205
+ };
206
+
207
+ export {
208
+ mapKeystoneUsbError,
209
+ KeystoneUsbConnectorBase
210
+ };
211
+ //# sourceMappingURL=chunk-5JU4E5XQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/KeystoneUsbConnectorBase.ts"],"sourcesContent":["import { HardwareErrorCode, createHwkError } from '@onekeyfe/hwk-adapter-core';\nimport { Status } from '@keystonehq/hw-transport-error';\n\nimport type { HwkError } from '@onekeyfe/hwk-adapter-core';\n\n/**\n * Device-side rejections (`Status` values reported inside a JSON response\n * payload's status word) — see EAPDU_Readme.md in the Keystone USB SDK repo.\n */\nconst DEVICE_STATUS_MAP: Partial<Record<number, HardwareErrorCode>> = {\n [Status.PRS_PARSING_REJECTED]: HardwareErrorCode.UserRejected,\n [Status.PRS_PARSING_DISALLOWED]: HardwareErrorCode.DeviceLocked,\n [Status.PRS_PARSING_MISMATCHED_WALLET]: HardwareErrorCode.DeviceMismatch,\n [Status.PRS_EXPORT_ADDRESS_REJECTED]: HardwareErrorCode.UserRejected,\n [Status.PRS_EXPORT_ADDRESS_DISALLOWED]: HardwareErrorCode.DeviceLocked,\n};\n\n/** Client-side (transport/framing) failures — never reached the device. */\nconst CLIENT_STATUS_MAP: Partial<Record<number, HardwareErrorCode>> = {\n [Status.ERR_DEVICE_NOT_OPENED]: HardwareErrorCode.DeviceNotFound,\n [Status.ERR_DEVICE_NOT_FOUND]: HardwareErrorCode.DeviceNotFound,\n [Status.ERR_TIMEOUT]: HardwareErrorCode.OperationTimeout,\n [Status.ERR_DATA_TOO_LARGE]: HardwareErrorCode.PayloadTooLarge,\n [Status.ERR_NOT_SUPPORTED]: HardwareErrorCode.TransportNotAvailable,\n};\n\n/**\n * Maps a Keystone USB SDK failure to the shared `HardwareErrorCode`\n * vocabulary. Every device-side rejection and client-side transport failure\n * the SDK produces is a *thrown* `TransportError` with a numeric\n * `transportErrorCode` (see `@keystonehq/hw-transport-error`) — this is\n * called from every `catch` in `KeystoneUsbConnectorBase` to build the\n * `ConnectorCallResult` data-not-exception shape `IConnector.call` requires.\n *\n * Idempotent: an error `KeystoneUsbConnectorBase` already built via\n * `createHwkError` (e.g. its own `DeviceMismatch`/`DeviceNotFound` checks)\n * carries a numeric `.code` already and passes straight through unchanged —\n * without this it would fall through to the generic `TransportError`\n * fallback below and silently lose the real code, since `HwkError` uses\n * `.code`, not the raw SDK's `.transportErrorCode`.\n *\n * Reads `transportErrorCode` duck-typed rather than `instanceof\n * TransportError` — cheap defense against the exact-same class of\n * cross-package duplicate-module-instance bug already hit once in this\n * integration (see docs/design/keystone-integration), where two copies of a\n * `@keystonehq/*` package resolve to different physical files and\n * `instanceof` silently fails across them.\n */\nexport function mapKeystoneUsbError(err: unknown): HwkError {\n if (err && typeof err === 'object' && typeof (err as { code?: unknown }).code === 'number') {\n return err as HwkError;\n }\n\n const statusCode =\n err &&\n typeof err === 'object' &&\n typeof (err as { transportErrorCode?: unknown }).transportErrorCode === 'number'\n ? (err as { transportErrorCode: number }).transportErrorCode\n : undefined;\n\n const message = err instanceof Error ? err.message : String(err);\n\n if (statusCode !== undefined) {\n const code =\n DEVICE_STATUS_MAP[statusCode] ??\n CLIENT_STATUS_MAP[statusCode] ??\n HardwareErrorCode.TransportError;\n return createHwkError({\n code,\n message,\n params: { statusCode, details: (err as { details?: string }).details },\n });\n }\n\n return createHwkError({ code: HardwareErrorCode.TransportError, message });\n}\n","import { UR, URDecoder, UREncoder } from '@ngraveio/bc-ur';\nimport { Actions } from '@keystonehq/hw-transport-usb';\nimport {\n EConnectorInteraction,\n HardwareErrorCode,\n TypedEventEmitter,\n createHwkError,\n serializeConnectorError,\n success,\n} from '@onekeyfe/hwk-adapter-core';\n\nimport { mapKeystoneUsbError } from './errors';\n\nimport type {\n ConnectorCallResult,\n ConnectorDevice,\n ConnectorEventMap,\n ConnectorEventType,\n ConnectorSearchDevicesOptions,\n ConnectorSession,\n IConnector,\n UiResponseEvent,\n} from '@onekeyfe/hwk-adapter-core';\nimport type { TransportConfig, TransportHID } from '@keystonehq/hw-transport-usb';\n\n/**\n * The static surface both `TransportWebUSB` and `TransportNodeUSB` expose\n * (verified against their real `.d.ts`/`.js` — both implement `TransportHID`\n * identically, differing only in how they enumerate/open the underlying\n * device). Parametrizing on this instead of importing either transport\n * package directly is what lets `KeystoneUsbConnectorBase` stay\n * platform-agnostic; `_subpath/webusb.ts` and `_subpath/nodeusb.ts` are the\n * only files that actually import a concrete transport.\n */\nexport interface KeystoneUsbTransportStatic {\n connect(config?: TransportConfig): Promise<TransportHID>;\n getKeystoneDevices(): Promise<ReadonlyArray<{ serialNumber?: string; productName?: string }>>;\n requestPermission?(): Promise<void>;\n isSupported(): Promise<boolean>;\n}\n\ninterface KeystoneUr {\n urType: string;\n urData: string;\n}\n\ninterface UsbSession {\n transport: TransportHID;\n mfp?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 100_000; // SDK's own raw default is 15s (\"may need users' action on the device\") — too short for real confirmation.\n\nfunction toUrEncoded(ur: KeystoneUr): string {\n return new UREncoder(new UR(Buffer.from(ur.urData, 'hex'), ur.urType), Infinity)\n .nextPart()\n .toUpperCase();\n}\n\nfunction fromUrEncoded(encoded: string): KeystoneUr {\n const decoder = new URDecoder();\n decoder.receivePart(encoded);\n if (!decoder.isComplete() || !decoder.isSuccess()) {\n throw new Error('Keystone USB response is not a complete, valid UR');\n }\n const ur = decoder.resultUR();\n return { urType: ur.type, urData: ur.cbor.toString('hex') };\n}\n\n/**\n * `IConnector` implementation for Keystone over USB. Every `TransportHID`\n * call the underlying SDK exposes is its own self-contained\n * open→claim→transfer→release→close cycle (verified in `TransportWebUSB`/\n * `TransportNodeUSB` source — there is no persistent USB claim to hold\n * across calls), so `connect()` just resolves one `TransportHID` instance\n * and caches it; each `call()` reuses that same JS object, and the\n * underlying SDK's own per-command open/close still happens on every send.\n *\n * USB carries the exact same UR payloads the QR channel does — `call()`'s\n * `'resolveUr'` method is a generic `{urType, urData}` (hex CBOR, matching\n * `hwk-keystone-adapter`'s `KeystoneUr` shape) in, `{urType, urData}` out.\n * The caller (a future dual-channel `KeystoneAdapter`) can build the UR once\n * via the same `KeystoneUrEngine` used for QR and send it down either\n * channel — this connector has no chain-specific knowledge at all.\n */\nexport class KeystoneUsbConnectorBase implements IConnector {\n readonly connectionType = 'usb' as const;\n\n private readonly transportClass: KeystoneUsbTransportStatic;\n\n private readonly timeoutMs: number;\n\n private readonly emitter = new TypedEventEmitter<ConnectorEventMap>();\n\n private readonly sessions = new Map<string, UsbSession>();\n\n constructor(transportClass: KeystoneUsbTransportStatic, options?: { timeoutMs?: number }) {\n this.transportClass = transportClass;\n this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n }\n\n async searchDevices(_options?: ConnectorSearchDevicesOptions): Promise<ConnectorDevice[]> {\n const devices = await this.transportClass.getKeystoneDevices();\n // No mfp available without opening+claiming the device (a real I/O cost\n // just to enumerate) — connectId is a placeholder until connect()\n // resolves the real identity. There is also no reliable way to target\n // one of several already-connected Keystones by id: the underlying SDK's\n // own connect() re-scans and picks for itself (WebUSB re-prompts the\n // picker when more than one device already has permission; Node USB just\n // takes the first match) — a real limitation of the vendor SDK, not\n // something this connector can route around.\n return devices.map((device, index) => ({\n connectId: `keystone-usb:${device.serialNumber ?? index}`,\n deviceId: '',\n name: device.productName ?? 'Keystone',\n connectionType: 'usb',\n capabilities: { persistentDeviceIdentity: true },\n }));\n }\n\n async connect(deviceId?: string): Promise<ConnectorSession> {\n this.emitter.emit('ui-event', {\n type: EConnectorInteraction.Searching,\n payload: { sessionId: '' },\n });\n const transport = await this.transportClass.connect({ timeout: this.timeoutMs });\n try {\n const config = await this._readAppConfig(transport);\n if (deviceId && config.mfp && config.mfp.toLowerCase() !== deviceId.toLowerCase()) {\n throw createHwkError({\n code: HardwareErrorCode.DeviceMismatch,\n message: `Connected Keystone wallet (mfp ${config.mfp}) does not match the requested device (${deviceId})`,\n });\n }\n const sessionId = config.mfp || 'keystone-usb';\n this.sessions.set(sessionId, { transport, mfp: config.mfp });\n const device: ConnectorDevice = {\n connectId: sessionId,\n deviceId: config.mfp ?? '',\n name: 'Keystone',\n connectionType: 'usb',\n capabilities: { persistentDeviceIdentity: true },\n };\n this.emitter.emit('device-connect', { device });\n return { sessionId, deviceInfo: this._toDeviceInfo(device, config.version) };\n } catch (err) {\n throw mapKeystoneUsbError(err);\n }\n }\n\n disconnect(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n this.sessions.delete(sessionId);\n if (session) {\n // No persistent claim to release (see class doc) — nothing to await\n // here beyond letting the underlying transport's own per-call close\n // run its course, which already happened on the last send().\n this.emitter.emit('device-disconnect', { connectId: sessionId });\n }\n return Promise.resolve();\n }\n\n async call(sessionId: string, method: string, params: unknown): Promise<ConnectorCallResult> {\n const session = this.sessions.get(sessionId);\n if (!session) {\n return {\n success: false,\n error: serializeConnectorError(\n createHwkError({\n code: HardwareErrorCode.DeviceNotFound,\n message: `Unknown Keystone USB session: ${sessionId}`,\n })\n ),\n };\n }\n\n try {\n switch (method) {\n case 'resolveUr': {\n const { urType, urData } = params as KeystoneUr;\n const encoded = toUrEncoded({ urType, urData });\n const response = await session.transport.send<{ payload: string }>(\n Actions.CMD_RESOLVE_UR,\n encoded\n );\n return success(fromUrEncoded(response.payload));\n }\n case 'checkLockStatus': {\n const response = await session.transport.send<{ payload: boolean }>(\n Actions.CMD_CHECK_LOCK_STATUS,\n ''\n );\n // Naming-only assumption (no positive-case fixture to confirm\n // against) — `checkDeviceLockStatus`'s own name is the only\n // evidence `true` means locked; verify against real hardware.\n return success({ locked: response.payload });\n }\n case 'getAppConfig': {\n const config = await this._readAppConfig(session.transport);\n return success(config);\n }\n default:\n return {\n success: false,\n error: serializeConnectorError(\n createHwkError({\n code: HardwareErrorCode.MethodNotSupported,\n message: `Unknown Keystone USB connector method: ${method}`,\n })\n ),\n };\n }\n } catch (err) {\n return { success: false, error: serializeConnectorError(mapKeystoneUsbError(err)) };\n }\n }\n\n // No protocol-level cancel exists over USB (verified: nothing in the SDK's\n // public surface can interrupt an in-flight transferIn/transferOut) — the\n // best this connector can do is stop waiting on its side; the underlying\n // `send()` promise still settles (or times out) on its own.\n cancel(_sessionId: string): Promise<void> {\n return Promise.resolve();\n }\n\n // Keystone USB never relays PIN/passphrase through the host — entry always\n // happens on the device's own touchscreen — so there is nothing to answer.\n uiResponse(_response: UiResponseEvent): void {}\n\n on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void {\n this.emitter.on(event, handler);\n }\n\n off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void {\n this.emitter.off(event, handler);\n }\n\n reset(): void {\n this.sessions.clear();\n }\n\n private async _readAppConfig(\n transport: TransportHID\n ): Promise<{ version?: string; mfp?: string }> {\n const response = await transport.send<Record<string, unknown>>(\n Actions.CMD_GET_DEVICE_VERSION,\n ''\n );\n return {\n version: typeof response.firmwareVersion === 'string' ? response.firmwareVersion : undefined,\n mfp: typeof response.walletMFP === 'string' ? response.walletMFP.toLowerCase() : undefined,\n };\n }\n\n private _toDeviceInfo(device: ConnectorDevice, firmwareVersion?: string) {\n return {\n vendor: 'keystone' as const,\n model: 'unknown',\n firmwareVersion: firmwareVersion ?? '0.0.0',\n deviceId: device.deviceId,\n connectId: device.connectId,\n connectionType: 'usb' as const,\n capabilities: { persistentDeviceIdentity: true },\n };\n }\n}\n"],"mappings":"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","names":["HardwareErrorCode","createHwkError","createHwkError","HardwareErrorCode"]}
@@ -0,0 +1,211 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/errors.ts
2
+ var _hwkadaptercore = require('@onekeyfe/hwk-adapter-core');
3
+ var _hwtransporterror = require('@keystonehq/hw-transport-error');
4
+ var DEVICE_STATUS_MAP = {
5
+ [_hwtransporterror.Status.PRS_PARSING_REJECTED]: _hwkadaptercore.HardwareErrorCode.UserRejected,
6
+ [_hwtransporterror.Status.PRS_PARSING_DISALLOWED]: _hwkadaptercore.HardwareErrorCode.DeviceLocked,
7
+ [_hwtransporterror.Status.PRS_PARSING_MISMATCHED_WALLET]: _hwkadaptercore.HardwareErrorCode.DeviceMismatch,
8
+ [_hwtransporterror.Status.PRS_EXPORT_ADDRESS_REJECTED]: _hwkadaptercore.HardwareErrorCode.UserRejected,
9
+ [_hwtransporterror.Status.PRS_EXPORT_ADDRESS_DISALLOWED]: _hwkadaptercore.HardwareErrorCode.DeviceLocked
10
+ };
11
+ var CLIENT_STATUS_MAP = {
12
+ [_hwtransporterror.Status.ERR_DEVICE_NOT_OPENED]: _hwkadaptercore.HardwareErrorCode.DeviceNotFound,
13
+ [_hwtransporterror.Status.ERR_DEVICE_NOT_FOUND]: _hwkadaptercore.HardwareErrorCode.DeviceNotFound,
14
+ [_hwtransporterror.Status.ERR_TIMEOUT]: _hwkadaptercore.HardwareErrorCode.OperationTimeout,
15
+ [_hwtransporterror.Status.ERR_DATA_TOO_LARGE]: _hwkadaptercore.HardwareErrorCode.PayloadTooLarge,
16
+ [_hwtransporterror.Status.ERR_NOT_SUPPORTED]: _hwkadaptercore.HardwareErrorCode.TransportNotAvailable
17
+ };
18
+ function mapKeystoneUsbError(err) {
19
+ if (err && typeof err === "object" && typeof err.code === "number") {
20
+ return err;
21
+ }
22
+ const statusCode = err && typeof err === "object" && typeof err.transportErrorCode === "number" ? err.transportErrorCode : void 0;
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ if (statusCode !== void 0) {
25
+ const code = _nullishCoalesce(_nullishCoalesce(DEVICE_STATUS_MAP[statusCode], () => ( CLIENT_STATUS_MAP[statusCode])), () => ( _hwkadaptercore.HardwareErrorCode.TransportError));
26
+ return _hwkadaptercore.createHwkError.call(void 0, {
27
+ code,
28
+ message,
29
+ params: { statusCode, details: err.details }
30
+ });
31
+ }
32
+ return _hwkadaptercore.createHwkError.call(void 0, { code: _hwkadaptercore.HardwareErrorCode.TransportError, message });
33
+ }
34
+
35
+ // src/KeystoneUsbConnectorBase.ts
36
+ var _bcur = require('@ngraveio/bc-ur');
37
+ var _hwtransportusb = require('@keystonehq/hw-transport-usb');
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ var DEFAULT_TIMEOUT_MS = 1e5;
47
+ function toUrEncoded(ur) {
48
+ return new (0, _bcur.UREncoder)(new (0, _bcur.UR)(Buffer.from(ur.urData, "hex"), ur.urType), Infinity).nextPart().toUpperCase();
49
+ }
50
+ function fromUrEncoded(encoded) {
51
+ const decoder = new (0, _bcur.URDecoder)();
52
+ decoder.receivePart(encoded);
53
+ if (!decoder.isComplete() || !decoder.isSuccess()) {
54
+ throw new Error("Keystone USB response is not a complete, valid UR");
55
+ }
56
+ const ur = decoder.resultUR();
57
+ return { urType: ur.type, urData: ur.cbor.toString("hex") };
58
+ }
59
+ var KeystoneUsbConnectorBase = class {
60
+ constructor(transportClass, options) {
61
+ this.connectionType = "usb";
62
+ this.emitter = new (0, _hwkadaptercore.TypedEventEmitter)();
63
+ this.sessions = /* @__PURE__ */ new Map();
64
+ this.transportClass = transportClass;
65
+ this.timeoutMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.timeoutMs]), () => ( DEFAULT_TIMEOUT_MS));
66
+ }
67
+ async searchDevices(_options) {
68
+ const devices = await this.transportClass.getKeystoneDevices();
69
+ return devices.map((device, index) => ({
70
+ connectId: `keystone-usb:${_nullishCoalesce(device.serialNumber, () => ( index))}`,
71
+ deviceId: "",
72
+ name: _nullishCoalesce(device.productName, () => ( "Keystone")),
73
+ connectionType: "usb",
74
+ capabilities: { persistentDeviceIdentity: true }
75
+ }));
76
+ }
77
+ async connect(deviceId) {
78
+ this.emitter.emit("ui-event", {
79
+ type: _hwkadaptercore.EConnectorInteraction.Searching,
80
+ payload: { sessionId: "" }
81
+ });
82
+ const transport = await this.transportClass.connect({ timeout: this.timeoutMs });
83
+ try {
84
+ const config = await this._readAppConfig(transport);
85
+ if (deviceId && config.mfp && config.mfp.toLowerCase() !== deviceId.toLowerCase()) {
86
+ throw _hwkadaptercore.createHwkError.call(void 0, {
87
+ code: _hwkadaptercore.HardwareErrorCode.DeviceMismatch,
88
+ message: `Connected Keystone wallet (mfp ${config.mfp}) does not match the requested device (${deviceId})`
89
+ });
90
+ }
91
+ const sessionId = config.mfp || "keystone-usb";
92
+ this.sessions.set(sessionId, { transport, mfp: config.mfp });
93
+ const device = {
94
+ connectId: sessionId,
95
+ deviceId: _nullishCoalesce(config.mfp, () => ( "")),
96
+ name: "Keystone",
97
+ connectionType: "usb",
98
+ capabilities: { persistentDeviceIdentity: true }
99
+ };
100
+ this.emitter.emit("device-connect", { device });
101
+ return { sessionId, deviceInfo: this._toDeviceInfo(device, config.version) };
102
+ } catch (err) {
103
+ throw mapKeystoneUsbError(err);
104
+ }
105
+ }
106
+ disconnect(sessionId) {
107
+ const session = this.sessions.get(sessionId);
108
+ this.sessions.delete(sessionId);
109
+ if (session) {
110
+ this.emitter.emit("device-disconnect", { connectId: sessionId });
111
+ }
112
+ return Promise.resolve();
113
+ }
114
+ async call(sessionId, method, params) {
115
+ const session = this.sessions.get(sessionId);
116
+ if (!session) {
117
+ return {
118
+ success: false,
119
+ error: _hwkadaptercore.serializeConnectorError.call(void 0,
120
+ _hwkadaptercore.createHwkError.call(void 0, {
121
+ code: _hwkadaptercore.HardwareErrorCode.DeviceNotFound,
122
+ message: `Unknown Keystone USB session: ${sessionId}`
123
+ })
124
+ )
125
+ };
126
+ }
127
+ try {
128
+ switch (method) {
129
+ case "resolveUr": {
130
+ const { urType, urData } = params;
131
+ const encoded = toUrEncoded({ urType, urData });
132
+ const response = await session.transport.send(
133
+ _hwtransportusb.Actions.CMD_RESOLVE_UR,
134
+ encoded
135
+ );
136
+ return _hwkadaptercore.success.call(void 0, fromUrEncoded(response.payload));
137
+ }
138
+ case "checkLockStatus": {
139
+ const response = await session.transport.send(
140
+ _hwtransportusb.Actions.CMD_CHECK_LOCK_STATUS,
141
+ ""
142
+ );
143
+ return _hwkadaptercore.success.call(void 0, { locked: response.payload });
144
+ }
145
+ case "getAppConfig": {
146
+ const config = await this._readAppConfig(session.transport);
147
+ return _hwkadaptercore.success.call(void 0, config);
148
+ }
149
+ default:
150
+ return {
151
+ success: false,
152
+ error: _hwkadaptercore.serializeConnectorError.call(void 0,
153
+ _hwkadaptercore.createHwkError.call(void 0, {
154
+ code: _hwkadaptercore.HardwareErrorCode.MethodNotSupported,
155
+ message: `Unknown Keystone USB connector method: ${method}`
156
+ })
157
+ )
158
+ };
159
+ }
160
+ } catch (err) {
161
+ return { success: false, error: _hwkadaptercore.serializeConnectorError.call(void 0, mapKeystoneUsbError(err)) };
162
+ }
163
+ }
164
+ // No protocol-level cancel exists over USB (verified: nothing in the SDK's
165
+ // public surface can interrupt an in-flight transferIn/transferOut) — the
166
+ // best this connector can do is stop waiting on its side; the underlying
167
+ // `send()` promise still settles (or times out) on its own.
168
+ cancel(_sessionId) {
169
+ return Promise.resolve();
170
+ }
171
+ // Keystone USB never relays PIN/passphrase through the host — entry always
172
+ // happens on the device's own touchscreen — so there is nothing to answer.
173
+ uiResponse(_response) {
174
+ }
175
+ on(event, handler) {
176
+ this.emitter.on(event, handler);
177
+ }
178
+ off(event, handler) {
179
+ this.emitter.off(event, handler);
180
+ }
181
+ reset() {
182
+ this.sessions.clear();
183
+ }
184
+ async _readAppConfig(transport) {
185
+ const response = await transport.send(
186
+ _hwtransportusb.Actions.CMD_GET_DEVICE_VERSION,
187
+ ""
188
+ );
189
+ return {
190
+ version: typeof response.firmwareVersion === "string" ? response.firmwareVersion : void 0,
191
+ mfp: typeof response.walletMFP === "string" ? response.walletMFP.toLowerCase() : void 0
192
+ };
193
+ }
194
+ _toDeviceInfo(device, firmwareVersion) {
195
+ return {
196
+ vendor: "keystone",
197
+ model: "unknown",
198
+ firmwareVersion: _nullishCoalesce(firmwareVersion, () => ( "0.0.0")),
199
+ deviceId: device.deviceId,
200
+ connectId: device.connectId,
201
+ connectionType: "usb",
202
+ capabilities: { persistentDeviceIdentity: true }
203
+ };
204
+ }
205
+ };
206
+
207
+
208
+
209
+
210
+ exports.mapKeystoneUsbError = mapKeystoneUsbError; exports.KeystoneUsbConnectorBase = KeystoneUsbConnectorBase;
211
+ //# sourceMappingURL=chunk-LA3RVME7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/chunk-LA3RVME7.js","../src/errors.ts","../src/KeystoneUsbConnectorBase.ts"],"names":[],"mappings":"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","file":"/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/chunk-LA3RVME7.js","sourcesContent":[null,"import { HardwareErrorCode, createHwkError } from '@onekeyfe/hwk-adapter-core';\nimport { Status } from '@keystonehq/hw-transport-error';\n\nimport type { HwkError } from '@onekeyfe/hwk-adapter-core';\n\n/**\n * Device-side rejections (`Status` values reported inside a JSON response\n * payload's status word) — see EAPDU_Readme.md in the Keystone USB SDK repo.\n */\nconst DEVICE_STATUS_MAP: Partial<Record<number, HardwareErrorCode>> = {\n [Status.PRS_PARSING_REJECTED]: HardwareErrorCode.UserRejected,\n [Status.PRS_PARSING_DISALLOWED]: HardwareErrorCode.DeviceLocked,\n [Status.PRS_PARSING_MISMATCHED_WALLET]: HardwareErrorCode.DeviceMismatch,\n [Status.PRS_EXPORT_ADDRESS_REJECTED]: HardwareErrorCode.UserRejected,\n [Status.PRS_EXPORT_ADDRESS_DISALLOWED]: HardwareErrorCode.DeviceLocked,\n};\n\n/** Client-side (transport/framing) failures — never reached the device. */\nconst CLIENT_STATUS_MAP: Partial<Record<number, HardwareErrorCode>> = {\n [Status.ERR_DEVICE_NOT_OPENED]: HardwareErrorCode.DeviceNotFound,\n [Status.ERR_DEVICE_NOT_FOUND]: HardwareErrorCode.DeviceNotFound,\n [Status.ERR_TIMEOUT]: HardwareErrorCode.OperationTimeout,\n [Status.ERR_DATA_TOO_LARGE]: HardwareErrorCode.PayloadTooLarge,\n [Status.ERR_NOT_SUPPORTED]: HardwareErrorCode.TransportNotAvailable,\n};\n\n/**\n * Maps a Keystone USB SDK failure to the shared `HardwareErrorCode`\n * vocabulary. Every device-side rejection and client-side transport failure\n * the SDK produces is a *thrown* `TransportError` with a numeric\n * `transportErrorCode` (see `@keystonehq/hw-transport-error`) — this is\n * called from every `catch` in `KeystoneUsbConnectorBase` to build the\n * `ConnectorCallResult` data-not-exception shape `IConnector.call` requires.\n *\n * Idempotent: an error `KeystoneUsbConnectorBase` already built via\n * `createHwkError` (e.g. its own `DeviceMismatch`/`DeviceNotFound` checks)\n * carries a numeric `.code` already and passes straight through unchanged —\n * without this it would fall through to the generic `TransportError`\n * fallback below and silently lose the real code, since `HwkError` uses\n * `.code`, not the raw SDK's `.transportErrorCode`.\n *\n * Reads `transportErrorCode` duck-typed rather than `instanceof\n * TransportError` — cheap defense against the exact-same class of\n * cross-package duplicate-module-instance bug already hit once in this\n * integration (see docs/design/keystone-integration), where two copies of a\n * `@keystonehq/*` package resolve to different physical files and\n * `instanceof` silently fails across them.\n */\nexport function mapKeystoneUsbError(err: unknown): HwkError {\n if (err && typeof err === 'object' && typeof (err as { code?: unknown }).code === 'number') {\n return err as HwkError;\n }\n\n const statusCode =\n err &&\n typeof err === 'object' &&\n typeof (err as { transportErrorCode?: unknown }).transportErrorCode === 'number'\n ? (err as { transportErrorCode: number }).transportErrorCode\n : undefined;\n\n const message = err instanceof Error ? err.message : String(err);\n\n if (statusCode !== undefined) {\n const code =\n DEVICE_STATUS_MAP[statusCode] ??\n CLIENT_STATUS_MAP[statusCode] ??\n HardwareErrorCode.TransportError;\n return createHwkError({\n code,\n message,\n params: { statusCode, details: (err as { details?: string }).details },\n });\n }\n\n return createHwkError({ code: HardwareErrorCode.TransportError, message });\n}\n","import { UR, URDecoder, UREncoder } from '@ngraveio/bc-ur';\nimport { Actions } from '@keystonehq/hw-transport-usb';\nimport {\n EConnectorInteraction,\n HardwareErrorCode,\n TypedEventEmitter,\n createHwkError,\n serializeConnectorError,\n success,\n} from '@onekeyfe/hwk-adapter-core';\n\nimport { mapKeystoneUsbError } from './errors';\n\nimport type {\n ConnectorCallResult,\n ConnectorDevice,\n ConnectorEventMap,\n ConnectorEventType,\n ConnectorSearchDevicesOptions,\n ConnectorSession,\n IConnector,\n UiResponseEvent,\n} from '@onekeyfe/hwk-adapter-core';\nimport type { TransportConfig, TransportHID } from '@keystonehq/hw-transport-usb';\n\n/**\n * The static surface both `TransportWebUSB` and `TransportNodeUSB` expose\n * (verified against their real `.d.ts`/`.js` — both implement `TransportHID`\n * identically, differing only in how they enumerate/open the underlying\n * device). Parametrizing on this instead of importing either transport\n * package directly is what lets `KeystoneUsbConnectorBase` stay\n * platform-agnostic; `_subpath/webusb.ts` and `_subpath/nodeusb.ts` are the\n * only files that actually import a concrete transport.\n */\nexport interface KeystoneUsbTransportStatic {\n connect(config?: TransportConfig): Promise<TransportHID>;\n getKeystoneDevices(): Promise<ReadonlyArray<{ serialNumber?: string; productName?: string }>>;\n requestPermission?(): Promise<void>;\n isSupported(): Promise<boolean>;\n}\n\ninterface KeystoneUr {\n urType: string;\n urData: string;\n}\n\ninterface UsbSession {\n transport: TransportHID;\n mfp?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 100_000; // SDK's own raw default is 15s (\"may need users' action on the device\") — too short for real confirmation.\n\nfunction toUrEncoded(ur: KeystoneUr): string {\n return new UREncoder(new UR(Buffer.from(ur.urData, 'hex'), ur.urType), Infinity)\n .nextPart()\n .toUpperCase();\n}\n\nfunction fromUrEncoded(encoded: string): KeystoneUr {\n const decoder = new URDecoder();\n decoder.receivePart(encoded);\n if (!decoder.isComplete() || !decoder.isSuccess()) {\n throw new Error('Keystone USB response is not a complete, valid UR');\n }\n const ur = decoder.resultUR();\n return { urType: ur.type, urData: ur.cbor.toString('hex') };\n}\n\n/**\n * `IConnector` implementation for Keystone over USB. Every `TransportHID`\n * call the underlying SDK exposes is its own self-contained\n * open→claim→transfer→release→close cycle (verified in `TransportWebUSB`/\n * `TransportNodeUSB` source — there is no persistent USB claim to hold\n * across calls), so `connect()` just resolves one `TransportHID` instance\n * and caches it; each `call()` reuses that same JS object, and the\n * underlying SDK's own per-command open/close still happens on every send.\n *\n * USB carries the exact same UR payloads the QR channel does — `call()`'s\n * `'resolveUr'` method is a generic `{urType, urData}` (hex CBOR, matching\n * `hwk-keystone-adapter`'s `KeystoneUr` shape) in, `{urType, urData}` out.\n * The caller (a future dual-channel `KeystoneAdapter`) can build the UR once\n * via the same `KeystoneUrEngine` used for QR and send it down either\n * channel — this connector has no chain-specific knowledge at all.\n */\nexport class KeystoneUsbConnectorBase implements IConnector {\n readonly connectionType = 'usb' as const;\n\n private readonly transportClass: KeystoneUsbTransportStatic;\n\n private readonly timeoutMs: number;\n\n private readonly emitter = new TypedEventEmitter<ConnectorEventMap>();\n\n private readonly sessions = new Map<string, UsbSession>();\n\n constructor(transportClass: KeystoneUsbTransportStatic, options?: { timeoutMs?: number }) {\n this.transportClass = transportClass;\n this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n }\n\n async searchDevices(_options?: ConnectorSearchDevicesOptions): Promise<ConnectorDevice[]> {\n const devices = await this.transportClass.getKeystoneDevices();\n // No mfp available without opening+claiming the device (a real I/O cost\n // just to enumerate) — connectId is a placeholder until connect()\n // resolves the real identity. There is also no reliable way to target\n // one of several already-connected Keystones by id: the underlying SDK's\n // own connect() re-scans and picks for itself (WebUSB re-prompts the\n // picker when more than one device already has permission; Node USB just\n // takes the first match) — a real limitation of the vendor SDK, not\n // something this connector can route around.\n return devices.map((device, index) => ({\n connectId: `keystone-usb:${device.serialNumber ?? index}`,\n deviceId: '',\n name: device.productName ?? 'Keystone',\n connectionType: 'usb',\n capabilities: { persistentDeviceIdentity: true },\n }));\n }\n\n async connect(deviceId?: string): Promise<ConnectorSession> {\n this.emitter.emit('ui-event', {\n type: EConnectorInteraction.Searching,\n payload: { sessionId: '' },\n });\n const transport = await this.transportClass.connect({ timeout: this.timeoutMs });\n try {\n const config = await this._readAppConfig(transport);\n if (deviceId && config.mfp && config.mfp.toLowerCase() !== deviceId.toLowerCase()) {\n throw createHwkError({\n code: HardwareErrorCode.DeviceMismatch,\n message: `Connected Keystone wallet (mfp ${config.mfp}) does not match the requested device (${deviceId})`,\n });\n }\n const sessionId = config.mfp || 'keystone-usb';\n this.sessions.set(sessionId, { transport, mfp: config.mfp });\n const device: ConnectorDevice = {\n connectId: sessionId,\n deviceId: config.mfp ?? '',\n name: 'Keystone',\n connectionType: 'usb',\n capabilities: { persistentDeviceIdentity: true },\n };\n this.emitter.emit('device-connect', { device });\n return { sessionId, deviceInfo: this._toDeviceInfo(device, config.version) };\n } catch (err) {\n throw mapKeystoneUsbError(err);\n }\n }\n\n disconnect(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n this.sessions.delete(sessionId);\n if (session) {\n // No persistent claim to release (see class doc) — nothing to await\n // here beyond letting the underlying transport's own per-call close\n // run its course, which already happened on the last send().\n this.emitter.emit('device-disconnect', { connectId: sessionId });\n }\n return Promise.resolve();\n }\n\n async call(sessionId: string, method: string, params: unknown): Promise<ConnectorCallResult> {\n const session = this.sessions.get(sessionId);\n if (!session) {\n return {\n success: false,\n error: serializeConnectorError(\n createHwkError({\n code: HardwareErrorCode.DeviceNotFound,\n message: `Unknown Keystone USB session: ${sessionId}`,\n })\n ),\n };\n }\n\n try {\n switch (method) {\n case 'resolveUr': {\n const { urType, urData } = params as KeystoneUr;\n const encoded = toUrEncoded({ urType, urData });\n const response = await session.transport.send<{ payload: string }>(\n Actions.CMD_RESOLVE_UR,\n encoded\n );\n return success(fromUrEncoded(response.payload));\n }\n case 'checkLockStatus': {\n const response = await session.transport.send<{ payload: boolean }>(\n Actions.CMD_CHECK_LOCK_STATUS,\n ''\n );\n // Naming-only assumption (no positive-case fixture to confirm\n // against) — `checkDeviceLockStatus`'s own name is the only\n // evidence `true` means locked; verify against real hardware.\n return success({ locked: response.payload });\n }\n case 'getAppConfig': {\n const config = await this._readAppConfig(session.transport);\n return success(config);\n }\n default:\n return {\n success: false,\n error: serializeConnectorError(\n createHwkError({\n code: HardwareErrorCode.MethodNotSupported,\n message: `Unknown Keystone USB connector method: ${method}`,\n })\n ),\n };\n }\n } catch (err) {\n return { success: false, error: serializeConnectorError(mapKeystoneUsbError(err)) };\n }\n }\n\n // No protocol-level cancel exists over USB (verified: nothing in the SDK's\n // public surface can interrupt an in-flight transferIn/transferOut) — the\n // best this connector can do is stop waiting on its side; the underlying\n // `send()` promise still settles (or times out) on its own.\n cancel(_sessionId: string): Promise<void> {\n return Promise.resolve();\n }\n\n // Keystone USB never relays PIN/passphrase through the host — entry always\n // happens on the device's own touchscreen — so there is nothing to answer.\n uiResponse(_response: UiResponseEvent): void {}\n\n on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void {\n this.emitter.on(event, handler);\n }\n\n off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void {\n this.emitter.off(event, handler);\n }\n\n reset(): void {\n this.sessions.clear();\n }\n\n private async _readAppConfig(\n transport: TransportHID\n ): Promise<{ version?: string; mfp?: string }> {\n const response = await transport.send<Record<string, unknown>>(\n Actions.CMD_GET_DEVICE_VERSION,\n ''\n );\n return {\n version: typeof response.firmwareVersion === 'string' ? response.firmwareVersion : undefined,\n mfp: typeof response.walletMFP === 'string' ? response.walletMFP.toLowerCase() : undefined,\n };\n }\n\n private _toDeviceInfo(device: ConnectorDevice, firmwareVersion?: string) {\n return {\n vendor: 'keystone' as const,\n model: 'unknown',\n firmwareVersion: firmwareVersion ?? '0.0.0',\n deviceId: device.deviceId,\n connectId: device.connectId,\n connectionType: 'usb' as const,\n capabilities: { persistentDeviceIdentity: true },\n };\n }\n}\n"]}
@@ -0,0 +1,84 @@
1
+ import { IConnector, ConnectorSearchDevicesOptions, ConnectorDevice, ConnectorSession, ConnectorCallResult, UiResponseEvent, ConnectorEventType, ConnectorEventMap, HwkError } from '@onekeyfe/hwk-adapter-core';
2
+ import { TransportConfig, TransportHID } from '@keystonehq/hw-transport-usb';
3
+
4
+ /**
5
+ * The static surface both `TransportWebUSB` and `TransportNodeUSB` expose
6
+ * (verified against their real `.d.ts`/`.js` — both implement `TransportHID`
7
+ * identically, differing only in how they enumerate/open the underlying
8
+ * device). Parametrizing on this instead of importing either transport
9
+ * package directly is what lets `KeystoneUsbConnectorBase` stay
10
+ * platform-agnostic; `_subpath/webusb.ts` and `_subpath/nodeusb.ts` are the
11
+ * only files that actually import a concrete transport.
12
+ */
13
+ interface KeystoneUsbTransportStatic {
14
+ connect(config?: TransportConfig): Promise<TransportHID>;
15
+ getKeystoneDevices(): Promise<ReadonlyArray<{
16
+ serialNumber?: string;
17
+ productName?: string;
18
+ }>>;
19
+ requestPermission?(): Promise<void>;
20
+ isSupported(): Promise<boolean>;
21
+ }
22
+ /**
23
+ * `IConnector` implementation for Keystone over USB. Every `TransportHID`
24
+ * call the underlying SDK exposes is its own self-contained
25
+ * open→claim→transfer→release→close cycle (verified in `TransportWebUSB`/
26
+ * `TransportNodeUSB` source — there is no persistent USB claim to hold
27
+ * across calls), so `connect()` just resolves one `TransportHID` instance
28
+ * and caches it; each `call()` reuses that same JS object, and the
29
+ * underlying SDK's own per-command open/close still happens on every send.
30
+ *
31
+ * USB carries the exact same UR payloads the QR channel does — `call()`'s
32
+ * `'resolveUr'` method is a generic `{urType, urData}` (hex CBOR, matching
33
+ * `hwk-keystone-adapter`'s `KeystoneUr` shape) in, `{urType, urData}` out.
34
+ * The caller (a future dual-channel `KeystoneAdapter`) can build the UR once
35
+ * via the same `KeystoneUrEngine` used for QR and send it down either
36
+ * channel — this connector has no chain-specific knowledge at all.
37
+ */
38
+ declare class KeystoneUsbConnectorBase implements IConnector {
39
+ readonly connectionType: "usb";
40
+ private readonly transportClass;
41
+ private readonly timeoutMs;
42
+ private readonly emitter;
43
+ private readonly sessions;
44
+ constructor(transportClass: KeystoneUsbTransportStatic, options?: {
45
+ timeoutMs?: number;
46
+ });
47
+ searchDevices(_options?: ConnectorSearchDevicesOptions): Promise<ConnectorDevice[]>;
48
+ connect(deviceId?: string): Promise<ConnectorSession>;
49
+ disconnect(sessionId: string): Promise<void>;
50
+ call(sessionId: string, method: string, params: unknown): Promise<ConnectorCallResult>;
51
+ cancel(_sessionId: string): Promise<void>;
52
+ uiResponse(_response: UiResponseEvent): void;
53
+ on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
54
+ off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
55
+ reset(): void;
56
+ private _readAppConfig;
57
+ private _toDeviceInfo;
58
+ }
59
+
60
+ /**
61
+ * Maps a Keystone USB SDK failure to the shared `HardwareErrorCode`
62
+ * vocabulary. Every device-side rejection and client-side transport failure
63
+ * the SDK produces is a *thrown* `TransportError` with a numeric
64
+ * `transportErrorCode` (see `@keystonehq/hw-transport-error`) — this is
65
+ * called from every `catch` in `KeystoneUsbConnectorBase` to build the
66
+ * `ConnectorCallResult` data-not-exception shape `IConnector.call` requires.
67
+ *
68
+ * Idempotent: an error `KeystoneUsbConnectorBase` already built via
69
+ * `createHwkError` (e.g. its own `DeviceMismatch`/`DeviceNotFound` checks)
70
+ * carries a numeric `.code` already and passes straight through unchanged —
71
+ * without this it would fall through to the generic `TransportError`
72
+ * fallback below and silently lose the real code, since `HwkError` uses
73
+ * `.code`, not the raw SDK's `.transportErrorCode`.
74
+ *
75
+ * Reads `transportErrorCode` duck-typed rather than `instanceof
76
+ * TransportError` — cheap defense against the exact-same class of
77
+ * cross-package duplicate-module-instance bug already hit once in this
78
+ * integration (see docs/design/keystone-integration), where two copies of a
79
+ * `@keystonehq/*` package resolve to different physical files and
80
+ * `instanceof` silently fails across them.
81
+ */
82
+ declare function mapKeystoneUsbError(err: unknown): HwkError;
83
+
84
+ export { KeystoneUsbConnectorBase, type KeystoneUsbTransportStatic, mapKeystoneUsbError };
@@ -0,0 +1,84 @@
1
+ import { IConnector, ConnectorSearchDevicesOptions, ConnectorDevice, ConnectorSession, ConnectorCallResult, UiResponseEvent, ConnectorEventType, ConnectorEventMap, HwkError } from '@onekeyfe/hwk-adapter-core';
2
+ import { TransportConfig, TransportHID } from '@keystonehq/hw-transport-usb';
3
+
4
+ /**
5
+ * The static surface both `TransportWebUSB` and `TransportNodeUSB` expose
6
+ * (verified against their real `.d.ts`/`.js` — both implement `TransportHID`
7
+ * identically, differing only in how they enumerate/open the underlying
8
+ * device). Parametrizing on this instead of importing either transport
9
+ * package directly is what lets `KeystoneUsbConnectorBase` stay
10
+ * platform-agnostic; `_subpath/webusb.ts` and `_subpath/nodeusb.ts` are the
11
+ * only files that actually import a concrete transport.
12
+ */
13
+ interface KeystoneUsbTransportStatic {
14
+ connect(config?: TransportConfig): Promise<TransportHID>;
15
+ getKeystoneDevices(): Promise<ReadonlyArray<{
16
+ serialNumber?: string;
17
+ productName?: string;
18
+ }>>;
19
+ requestPermission?(): Promise<void>;
20
+ isSupported(): Promise<boolean>;
21
+ }
22
+ /**
23
+ * `IConnector` implementation for Keystone over USB. Every `TransportHID`
24
+ * call the underlying SDK exposes is its own self-contained
25
+ * open→claim→transfer→release→close cycle (verified in `TransportWebUSB`/
26
+ * `TransportNodeUSB` source — there is no persistent USB claim to hold
27
+ * across calls), so `connect()` just resolves one `TransportHID` instance
28
+ * and caches it; each `call()` reuses that same JS object, and the
29
+ * underlying SDK's own per-command open/close still happens on every send.
30
+ *
31
+ * USB carries the exact same UR payloads the QR channel does — `call()`'s
32
+ * `'resolveUr'` method is a generic `{urType, urData}` (hex CBOR, matching
33
+ * `hwk-keystone-adapter`'s `KeystoneUr` shape) in, `{urType, urData}` out.
34
+ * The caller (a future dual-channel `KeystoneAdapter`) can build the UR once
35
+ * via the same `KeystoneUrEngine` used for QR and send it down either
36
+ * channel — this connector has no chain-specific knowledge at all.
37
+ */
38
+ declare class KeystoneUsbConnectorBase implements IConnector {
39
+ readonly connectionType: "usb";
40
+ private readonly transportClass;
41
+ private readonly timeoutMs;
42
+ private readonly emitter;
43
+ private readonly sessions;
44
+ constructor(transportClass: KeystoneUsbTransportStatic, options?: {
45
+ timeoutMs?: number;
46
+ });
47
+ searchDevices(_options?: ConnectorSearchDevicesOptions): Promise<ConnectorDevice[]>;
48
+ connect(deviceId?: string): Promise<ConnectorSession>;
49
+ disconnect(sessionId: string): Promise<void>;
50
+ call(sessionId: string, method: string, params: unknown): Promise<ConnectorCallResult>;
51
+ cancel(_sessionId: string): Promise<void>;
52
+ uiResponse(_response: UiResponseEvent): void;
53
+ on<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
54
+ off<K extends ConnectorEventType>(event: K, handler: (data: ConnectorEventMap[K]) => void): void;
55
+ reset(): void;
56
+ private _readAppConfig;
57
+ private _toDeviceInfo;
58
+ }
59
+
60
+ /**
61
+ * Maps a Keystone USB SDK failure to the shared `HardwareErrorCode`
62
+ * vocabulary. Every device-side rejection and client-side transport failure
63
+ * the SDK produces is a *thrown* `TransportError` with a numeric
64
+ * `transportErrorCode` (see `@keystonehq/hw-transport-error`) — this is
65
+ * called from every `catch` in `KeystoneUsbConnectorBase` to build the
66
+ * `ConnectorCallResult` data-not-exception shape `IConnector.call` requires.
67
+ *
68
+ * Idempotent: an error `KeystoneUsbConnectorBase` already built via
69
+ * `createHwkError` (e.g. its own `DeviceMismatch`/`DeviceNotFound` checks)
70
+ * carries a numeric `.code` already and passes straight through unchanged —
71
+ * without this it would fall through to the generic `TransportError`
72
+ * fallback below and silently lose the real code, since `HwkError` uses
73
+ * `.code`, not the raw SDK's `.transportErrorCode`.
74
+ *
75
+ * Reads `transportErrorCode` duck-typed rather than `instanceof
76
+ * TransportError` — cheap defense against the exact-same class of
77
+ * cross-package duplicate-module-instance bug already hit once in this
78
+ * integration (see docs/design/keystone-integration), where two copies of a
79
+ * `@keystonehq/*` package resolve to different physical files and
80
+ * `instanceof` silently fails across them.
81
+ */
82
+ declare function mapKeystoneUsbError(err: unknown): HwkError;
83
+
84
+ export { KeystoneUsbConnectorBase, type KeystoneUsbTransportStatic, mapKeystoneUsbError };
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+ var _chunkLA3RVME7js = require('./chunk-LA3RVME7.js');
5
+
6
+
7
+
8
+ exports.KeystoneUsbConnectorBase = _chunkLA3RVME7js.KeystoneUsbConnectorBase; exports.mapKeystoneUsbError = _chunkLA3RVME7js.mapKeystoneUsbError;
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/index.js"],"names":[],"mappings":"AAAA;AACE;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,iJAAC","file":"/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/index.js"}
package/dist/index.mjs ADDED
@@ -0,0 +1,9 @@
1
+ import {
2
+ KeystoneUsbConnectorBase,
3
+ mapKeystoneUsbError
4
+ } from "./chunk-5JU4E5XQ.mjs";
5
+ export {
6
+ KeystoneUsbConnectorBase,
7
+ mapKeystoneUsbError
8
+ };
9
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,7 @@
1
+ import { IConnector } from '@onekeyfe/hwk-adapter-core';
2
+
3
+ declare function createKeystoneNodeUsbConnector(options?: {
4
+ timeoutMs?: number;
5
+ }): IConnector;
6
+
7
+ export { createKeystoneNodeUsbConnector };
@@ -0,0 +1,7 @@
1
+ import { IConnector } from '@onekeyfe/hwk-adapter-core';
2
+
3
+ declare function createKeystoneNodeUsbConnector(options?: {
4
+ timeoutMs?: number;
5
+ }): IConnector;
6
+
7
+ export { createKeystoneNodeUsbConnector };
@@ -0,0 +1,13 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkLA3RVME7js = require('./chunk-LA3RVME7.js');
4
+
5
+ // src/_subpath/nodeusb.ts
6
+ var _hwtransportnodeusb = require('@keystonehq/hw-transport-nodeusb');
7
+ function createKeystoneNodeUsbConnector(options) {
8
+ return new (0, _chunkLA3RVME7js.KeystoneUsbConnectorBase)(_hwtransportnodeusb.TransportNodeUSB, options);
9
+ }
10
+
11
+
12
+ exports.createKeystoneNodeUsbConnector = createKeystoneNodeUsbConnector;
13
+ //# sourceMappingURL=nodeusb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/nodeusb.js","../src/_subpath/nodeusb.ts"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACA;ACJA,sEAAiC;AAM1B,SAAS,8BAAA,CAA+B,OAAA,EAA8C;AAC3F,EAAA,OAAO,IAAI,8CAAA,CAAyB,oCAAA,EAAkB,OAAO,CAAA;AAC/D;ADCA;AACE;AACF,wEAAC","file":"/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/nodeusb.js","sourcesContent":[null,"import { TransportNodeUSB } from '@keystonehq/hw-transport-nodeusb';\n\nimport { KeystoneUsbConnectorBase } from '../KeystoneUsbConnectorBase';\n\nimport type { IConnector } from '@onekeyfe/hwk-adapter-core';\n\nexport function createKeystoneNodeUsbConnector(options?: { timeoutMs?: number }): IConnector {\n return new KeystoneUsbConnectorBase(TransportNodeUSB, options);\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import {
2
+ KeystoneUsbConnectorBase
3
+ } from "./chunk-5JU4E5XQ.mjs";
4
+
5
+ // src/_subpath/nodeusb.ts
6
+ import { TransportNodeUSB } from "@keystonehq/hw-transport-nodeusb";
7
+ function createKeystoneNodeUsbConnector(options) {
8
+ return new KeystoneUsbConnectorBase(TransportNodeUSB, options);
9
+ }
10
+ export {
11
+ createKeystoneNodeUsbConnector
12
+ };
13
+ //# sourceMappingURL=nodeusb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_subpath/nodeusb.ts"],"sourcesContent":["import { TransportNodeUSB } from '@keystonehq/hw-transport-nodeusb';\n\nimport { KeystoneUsbConnectorBase } from '../KeystoneUsbConnectorBase';\n\nimport type { IConnector } from '@onekeyfe/hwk-adapter-core';\n\nexport function createKeystoneNodeUsbConnector(options?: { timeoutMs?: number }): IConnector {\n return new KeystoneUsbConnectorBase(TransportNodeUSB, options);\n}\n"],"mappings":";;;;;AAAA,SAAS,wBAAwB;AAM1B,SAAS,+BAA+B,SAA8C;AAC3F,SAAO,IAAI,yBAAyB,kBAAkB,OAAO;AAC/D;","names":[]}
@@ -0,0 +1,13 @@
1
+ import { IConnector } from '@onekeyfe/hwk-adapter-core';
2
+
3
+ declare function createKeystoneWebUsbConnector(options?: {
4
+ timeoutMs?: number;
5
+ }): IConnector;
6
+ /**
7
+ * Triggers the browser's WebUSB device picker. Must be called from a
8
+ * user-gesture handler (e.g. directly inside a button's `onclick`) — same
9
+ * requirement as any other WebUSB permission request.
10
+ */
11
+ declare function requestKeystoneUsbPermission(): Promise<void>;
12
+
13
+ export { createKeystoneWebUsbConnector, requestKeystoneUsbPermission };
@@ -0,0 +1,13 @@
1
+ import { IConnector } from '@onekeyfe/hwk-adapter-core';
2
+
3
+ declare function createKeystoneWebUsbConnector(options?: {
4
+ timeoutMs?: number;
5
+ }): IConnector;
6
+ /**
7
+ * Triggers the browser's WebUSB device picker. Must be called from a
8
+ * user-gesture handler (e.g. directly inside a button's `onclick`) — same
9
+ * requirement as any other WebUSB permission request.
10
+ */
11
+ declare function requestKeystoneUsbPermission(): Promise<void>;
12
+
13
+ export { createKeystoneWebUsbConnector, requestKeystoneUsbPermission };
package/dist/webusb.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkLA3RVME7js = require('./chunk-LA3RVME7.js');
4
+
5
+ // src/_subpath/webusb.ts
6
+ var _hwtransportwebusb = require('@keystonehq/hw-transport-webusb');
7
+ function createKeystoneWebUsbConnector(options) {
8
+ return new (0, _chunkLA3RVME7js.KeystoneUsbConnectorBase)(_hwtransportwebusb.TransportWebUSB, options);
9
+ }
10
+ async function requestKeystoneUsbPermission() {
11
+ await _hwtransportwebusb.TransportWebUSB.requestPermission();
12
+ }
13
+
14
+
15
+
16
+ exports.createKeystoneWebUsbConnector = createKeystoneWebUsbConnector; exports.requestKeystoneUsbPermission = requestKeystoneUsbPermission;
17
+ //# sourceMappingURL=webusb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/webusb.js","../src/_subpath/webusb.ts"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACA;ACJA,oEAAgC;AAMzB,SAAS,6BAAA,CAA8B,OAAA,EAA8C;AAC1F,EAAA,OAAO,IAAI,8CAAA,CAAyB,kCAAA,EAAiB,OAAO,CAAA;AAC9D;AAOA,MAAA,SAAsB,4BAAA,CAAA,EAA8C;AAClE,EAAA,MAAM,kCAAA,CAAgB,iBAAA,CAAkB,CAAA;AAC1C;ADLA;AACE;AACA;AACF,2IAAC","file":"/home/runner/work/hardware-js-sdk/hardware-js-sdk/packages/hwk-keystone-connector-usb/dist/webusb.js","sourcesContent":[null,"import { TransportWebUSB } from '@keystonehq/hw-transport-webusb';\n\nimport { KeystoneUsbConnectorBase } from '../KeystoneUsbConnectorBase';\n\nimport type { IConnector } from '@onekeyfe/hwk-adapter-core';\n\nexport function createKeystoneWebUsbConnector(options?: { timeoutMs?: number }): IConnector {\n return new KeystoneUsbConnectorBase(TransportWebUSB, options);\n}\n\n/**\n * Triggers the browser's WebUSB device picker. Must be called from a\n * user-gesture handler (e.g. directly inside a button's `onclick`) — same\n * requirement as any other WebUSB permission request.\n */\nexport async function requestKeystoneUsbPermission(): Promise<void> {\n await TransportWebUSB.requestPermission();\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import {
2
+ KeystoneUsbConnectorBase
3
+ } from "./chunk-5JU4E5XQ.mjs";
4
+
5
+ // src/_subpath/webusb.ts
6
+ import { TransportWebUSB } from "@keystonehq/hw-transport-webusb";
7
+ function createKeystoneWebUsbConnector(options) {
8
+ return new KeystoneUsbConnectorBase(TransportWebUSB, options);
9
+ }
10
+ async function requestKeystoneUsbPermission() {
11
+ await TransportWebUSB.requestPermission();
12
+ }
13
+ export {
14
+ createKeystoneWebUsbConnector,
15
+ requestKeystoneUsbPermission
16
+ };
17
+ //# sourceMappingURL=webusb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_subpath/webusb.ts"],"sourcesContent":["import { TransportWebUSB } from '@keystonehq/hw-transport-webusb';\n\nimport { KeystoneUsbConnectorBase } from '../KeystoneUsbConnectorBase';\n\nimport type { IConnector } from '@onekeyfe/hwk-adapter-core';\n\nexport function createKeystoneWebUsbConnector(options?: { timeoutMs?: number }): IConnector {\n return new KeystoneUsbConnectorBase(TransportWebUSB, options);\n}\n\n/**\n * Triggers the browser's WebUSB device picker. Must be called from a\n * user-gesture handler (e.g. directly inside a button's `onclick`) — same\n * requirement as any other WebUSB permission request.\n */\nexport async function requestKeystoneUsbPermission(): Promise<void> {\n await TransportWebUSB.requestPermission();\n}\n"],"mappings":";;;;;AAAA,SAAS,uBAAuB;AAMzB,SAAS,8BAA8B,SAA8C;AAC1F,SAAO,IAAI,yBAAyB,iBAAiB,OAAO;AAC9D;AAOA,eAAsB,+BAA8C;AAClE,QAAM,gBAAgB,kBAAkB;AAC1C;","names":[]}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@onekeyfe/hwk-keystone-connector-usb",
3
+ "version": "1.2.2-alpha.100",
4
+ "description": "IConnector implementation for Keystone hardware wallets via USB (WebUSB + Node USB)",
5
+ "author": "OneKey",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.mts",
14
+ "default": "./dist/index.mjs"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
20
+ },
21
+ "./webusb": {
22
+ "import": {
23
+ "types": "./dist/webusb.d.mts",
24
+ "default": "./dist/webusb.mjs"
25
+ },
26
+ "require": {
27
+ "types": "./dist/webusb.d.ts",
28
+ "default": "./dist/webusb.js"
29
+ }
30
+ },
31
+ "./nodeusb": {
32
+ "import": {
33
+ "types": "./dist/nodeusb.d.mts",
34
+ "default": "./dist/nodeusb.mjs"
35
+ },
36
+ "require": {
37
+ "types": "./dist/nodeusb.d.ts",
38
+ "default": "./dist/nodeusb.js"
39
+ }
40
+ }
41
+ },
42
+ "files": [
43
+ "dist"
44
+ ],
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "dev": "tsup --watch",
48
+ "clean": "rimraf dist",
49
+ "test": "jest"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/OneKeyHQ/hardware-js-sdk.git",
57
+ "directory": "packages/hwk-keystone-connector-usb"
58
+ },
59
+ "keywords": [
60
+ "keystone",
61
+ "usb",
62
+ "webusb",
63
+ "hardware-wallet",
64
+ "onekey",
65
+ "connector"
66
+ ],
67
+ "dependencies": {
68
+ "@keystonehq/hw-transport-error": "0.0.2",
69
+ "@keystonehq/hw-transport-nodeusb": "0.2.0",
70
+ "@keystonehq/hw-transport-usb": "0.2.0",
71
+ "@keystonehq/hw-transport-webusb": "0.5.3",
72
+ "@ngraveio/bc-ur": "1.1.13",
73
+ "@onekeyfe/hwk-adapter-core": "1.2.2-alpha.100"
74
+ },
75
+ "devDependencies": {
76
+ "@keystonehq/bc-ur-registry-eth": "0.22.1",
77
+ "rimraf": "^5.0.0",
78
+ "tsup": "^8.0.0",
79
+ "typescript": "^5.4.0"
80
+ },
81
+ "gitHead": "c40dad085297b0e3cc2020bd218c146dff6ed3e1"
82
+ }