From 12a971cc2560da4b9acf8305c00360b3a387353f Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Sun, 24 May 2026 22:29:48 +0800 Subject: [PATCH 1/4] fix(ledger): enforce USB single-session and reject multi-device Harden Ledger USB session/connection handling so multi-Ledger or replug scenarios can't route calls to the wrong device: - Reject multiple USB devices instead of auto-selecting by ephemeral path (new DeviceOneDeviceOnly error; added to ORPHAN_ELIGIBLE_ERROR_CODES so a batch aborts on it). - ensureConnected does the ambient first-session fallback only when the caller passed no connectId; an explicit miss re-resolves THAT device. - Make device-state subscription setup fatal: on failure, disconnect the just-created DMK session so no ghost entry leaks into _sessions. - Evict any pre-existing session before opening a new USB connection. Scope: Ledger-only (hwk-ledger-adapter / hwk-adapter-core). Does not touch the OneKey SDK stack. --- .../src/__tests__/core-types.test.ts | 1 + packages/hwk-adapter-core/src/types/errors.ts | 3 + .../src/utils/errorMessages.ts | 2 + .../src/__tests__/LedgerAdapter.test.ts | 55 +----- .../src/__tests__/LedgerConnectorBase.test.ts | 65 +++++-- .../src/adapter/LedgerAdapter.ts | 175 +++++++++--------- .../src/connector/LedgerConnectorBase.ts | 80 +++++--- packages/hwk-ledger-adapter/src/errors.ts | 9 + 8 files changed, 212 insertions(+), 178 deletions(-) diff --git a/packages/hwk-adapter-core/src/__tests__/core-types.test.ts b/packages/hwk-adapter-core/src/__tests__/core-types.test.ts index df6ad1403..769bf3c45 100644 --- a/packages/hwk-adapter-core/src/__tests__/core-types.test.ts +++ b/packages/hwk-adapter-core/src/__tests__/core-types.test.ts @@ -33,6 +33,7 @@ describe('HardwareErrorCode contract', () => { expect(HardwareErrorCode.DeviceNotInitialized).toBe(10104); expect(HardwareErrorCode.DeviceInBootloader).toBe(10105); expect(HardwareErrorCode.DeviceMismatch).toBe(10106); + expect(HardwareErrorCode.DeviceOneDeviceOnly).toBe(10109); }); it('firmware (10200-10299)', () => { diff --git a/packages/hwk-adapter-core/src/types/errors.ts b/packages/hwk-adapter-core/src/types/errors.ts index 67cd5a0a2..6bc05f0e0 100644 --- a/packages/hwk-adapter-core/src/types/errors.ts +++ b/packages/hwk-adapter-core/src/types/errors.ts @@ -43,6 +43,8 @@ export enum HardwareErrorCode { DeviceAppStuck = 10107, /** Vendor (Ledger / Trezor) doesn't support the chain at all. */ ChainNotSupported = 10108, + /** Current operation supports only one connected device. */ + DeviceOneDeviceOnly = 10109, // --- 10200s Firmware --- FirmwareTooOld = 10200, @@ -127,6 +129,7 @@ export const ORPHAN_ELIGIBLE_ERROR_CODES: number[] = [ HardwareErrorCode.DeviceDisconnected, HardwareErrorCode.DeviceMismatch, HardwareErrorCode.DeviceAppStuck, + HardwareErrorCode.DeviceOneDeviceOnly, HardwareErrorCode.TransportError, HardwareErrorCode.DevicePermissionDenied, HardwareErrorCode.BlePairingTimeout, diff --git a/packages/hwk-adapter-core/src/utils/errorMessages.ts b/packages/hwk-adapter-core/src/utils/errorMessages.ts index 22d0a73cf..c997c9583 100644 --- a/packages/hwk-adapter-core/src/utils/errorMessages.ts +++ b/packages/hwk-adapter-core/src/utils/errorMessages.ts @@ -16,6 +16,8 @@ export function enrichErrorMessage(code: HardwareErrorCode, originalMessage: str return `${originalMessage}. Please reconnect the device and try again.`; case HardwareErrorCode.DeviceLocked: return `${originalMessage}. Please unlock your device and try again.`; + case HardwareErrorCode.DeviceOneDeviceOnly: + return `${originalMessage}. Disconnect the extra device and try again.`; case HardwareErrorCode.UserRejected: return `${originalMessage}. The request was rejected on the device.`; case HardwareErrorCode.WrongApp: diff --git a/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts b/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts index a42e0d3a0..33ae28f66 100644 --- a/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts +++ b/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts @@ -1151,7 +1151,6 @@ describe('LedgerAdapter', () => { .mockResolvedValueOnce({ signature: '0xSIGNED' }); connector.searchDevices.mockResolvedValueOnce([ - { connectId: 'dev-new', deviceId: 'dev-new', name: 'Nano S', model: 'nanoS' }, { connectId: 'dev-1', deviceId: 'dev-1', name: 'Nano X', model: 'nanoX' }, ]); connector.connect.mockResolvedValueOnce({ @@ -1173,7 +1172,6 @@ describe('LedgerAdapter', () => { expect(result.success).toBe(true); expect(connector.connect).toHaveBeenLastCalledWith('dev-1'); - expect(connector.connect).not.toHaveBeenCalledWith('dev-new'); expect(connector.call).toHaveBeenLastCalledWith( 'session-target', 'evmSignMessage', @@ -1240,7 +1238,7 @@ describe('LedgerAdapter', () => { ); }); - it('should auto-select first device when multiple devices found and handleSelectDevice is off (default)', async () => { + it('should reject multiple USB devices instead of auto-selecting the first one', async () => { connector.searchDevices.mockResolvedValueOnce([ { connectId: 'dev-A', deviceId: 'dev-A', name: 'Nano X', model: 'nanoX' }, { connectId: 'dev-B', deviceId: 'dev-B', name: 'Nano S', model: 'nanoS' }, @@ -1258,56 +1256,17 @@ describe('LedgerAdapter', () => { }); connector.call.mockResolvedValueOnce({ address: '0xFALLBACK' }); - // No UI handler set — should fall back to first device const result = await adapter.evmGetAddress('', '', { path: "m/44'/60'/0'/0/0", showOnDevice: false, }); - expect(result.success).toBe(true); - expect(connector.connect).toHaveBeenCalledWith('dev-A'); - }); - - it('should capture a synchronous select-device response from the UI handler', async () => { - const selectAdapter = new LedgerAdapter(connector, { handleSelectDevice: true }); - selectAdapter.on(UI_REQUEST.REQUEST_DEVICE_PERMISSION, () => { - selectAdapter.uiResponse({ - type: UI_RESPONSE.RECEIVE_DEVICE_PERMISSION, - payload: { granted: true }, - }); - }); - selectAdapter.on(UI_REQUEST.REQUEST_SELECT_DEVICE, event => { - const selected = event.payload.devices.find(d => d.connectId === 'dev-B'); - selectAdapter.uiResponse({ - type: UI_RESPONSE.RECEIVE_SELECT_DEVICE, - payload: { sdkConnectId: selected?.connectId ?? 'dev-B' }, - }); - }); - - connector.searchDevices.mockResolvedValueOnce([ - { connectId: 'dev-A', deviceId: 'dev-A', name: 'Nano X', model: 'nanoX' }, - { connectId: 'dev-B', deviceId: 'dev-B', name: 'Nano S', model: 'nanoS' }, - ]); - connector.connect.mockResolvedValueOnce({ - sessionId: 'session-B', - deviceInfo: { - vendor: 'ledger', - model: 'nanoS', - firmwareVersion: 'unknown', - deviceId: 'dev-B', - connectId: 'dev-B', - connectionType: 'usb', - }, - }); - connector.call.mockResolvedValueOnce({ address: '0xSELECTED' }); - - const result = await selectAdapter.evmGetAddress('', '', { - path: "m/44'/60'/0'/0/0", - showOnDevice: false, - }); - - expect(result.success).toBe(true); - expect(connector.connect).toHaveBeenCalledWith('dev-B'); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.payload.code).toBe(HardwareErrorCode.DeviceOneDeviceOnly); + expect(result.payload.error).toContain('Multiple Ledger USB devices are connected'); + } + expect(connector.connect).not.toHaveBeenCalled(); }); it('should reject BLE business calls with an empty connectId instead of auto-selecting a device', async () => { diff --git a/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts b/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts index ab6305811..85a683ecf 100644 --- a/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts +++ b/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts @@ -3,11 +3,11 @@ import { HardwareErrorCode } from '@onekeyfe/hwk-adapter-core'; import { LedgerConnectorBase } from '../connector/LedgerConnectorBase'; import { ERROR_TAG } from '../errors'; -import type { DeviceDescriptor } from '@onekeyfe/hwk-adapter-core'; +import type { ConnectionType, DeviceDescriptor } from '@onekeyfe/hwk-adapter-core'; -class BleSearchConnector extends LedgerConnectorBase { - constructor(private readonly descriptors: DeviceDescriptor[]) { - super(async () => ({}), { connectionType: 'ble', dmk: {} as any }); +class SearchConnector extends LedgerConnectorBase { + constructor(private readonly descriptors: DeviceDescriptor[], connectionType: ConnectionType) { + super(async () => ({}), { connectionType, dmk: {} as any }); } protected override async _discoverDescriptors(): Promise { @@ -61,15 +61,18 @@ describe('LedgerConnectorBase error wrapping', () => { describe('LedgerConnectorBase BLE discovery', () => { it('allows transport ids as BLE connectId even when they are not four-character names', async () => { - const connector = new BleSearchConnector([ - { - path: 'D5:75:7D:4B:51:E8', - name: 'Nano X 123', - bleName: 'A58F', - transport: 'RN_BLE', - type: 'nanoX', - }, - ]); + const connector = new SearchConnector( + [ + { + path: 'D5:75:7D:4B:51:E8', + name: 'Nano X 123', + bleName: 'A58F', + transport: 'RN_BLE', + type: 'nanoX', + }, + ], + 'ble' + ); await expect(connector.searchDevices()).resolves.toEqual([ expect.objectContaining({ @@ -82,6 +85,31 @@ describe('LedgerConnectorBase BLE discovery', () => { }); }); +describe('LedgerConnectorBase USB single-device guard', () => { + const usbDescriptors: DeviceDescriptor[] = [ + { + path: 'usb-path-a', + name: 'Ledger Stax', + transport: 'WEB-HID', + type: 'stax', + }, + { + path: 'usb-path-b', + name: 'Ledger Nano Gen5', + transport: 'WEB-HID', + type: 'apex', + }, + ]; + + it('rejects USB search when multiple Ledger devices are visible', async () => { + const connector = new SearchConnector(usbDescriptors, 'usb'); + + await expect(connector.searchDevices()).rejects.toMatchObject({ + code: HardwareErrorCode.DeviceOneDeviceOnly, + message: expect.stringContaining('Multiple Ledger USB devices are connected'), + }); + }); +}); describe('LedgerConnectorBase BLE direct-connect gate', () => { const TARGET_PATH = 'D5:75:7D:4B:51:E8'; @@ -100,9 +128,18 @@ describe('LedgerConnectorBase BLE direct-connect gate', () => { } function setupConnector(requirePreFlightScan: boolean) { + // _watchSessionState now propagates subscribe failures (so missing + // subscriptions can't silently leave ghost entries in the adapter's + // _sessions map). Provide a no-op observable so happy-path connect() + // tests don't trip on the new strict behavior. + const fakeDmk = { + getDeviceSessionState: jest.fn().mockReturnValue({ + subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }), + }), + }; const connector = new LedgerConnectorBase(async () => ({}), { connectionType: 'ble', - dmk: {} as any, + dmk: fakeDmk as any, requirePreFlightScan, }); return connector; diff --git a/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts b/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts index 250cb95cd..4550df822 100644 --- a/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts +++ b/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts @@ -15,6 +15,7 @@ import { import { ERROR_TAG, + createMultipleUsbLedgerDevicesError, isConnectionLevelError, isDeviceDisconnectedError, isDeviceLockedError, @@ -74,15 +75,6 @@ import type { UiResponseEvent, } from '@onekeyfe/hwk-adapter-core'; -export interface LedgerAdapterOptions { - /** - * `true` — emit `REQUEST_SELECT_DEVICE` on multi-device discovery and await - * `uiResponse({ type: RECEIVE_SELECT_DEVICE, payload: { sdkConnectId } })`. - * `false` (default) — silently pick the first device. - */ - handleSelectDevice?: boolean; -} - /** * Result of `_verifyDeviceFingerprint`. * @@ -122,8 +114,6 @@ export class LedgerAdapter implements IHardwareWallet { private readonly emitter = new TypedEventEmitter(); - private readonly _handleSelectDevice: boolean; - private _discoveredDevices = new Map(); private _sessions = new Map(); @@ -148,9 +138,8 @@ export class LedgerAdapter implements IHardwareWallet { // Shared across concurrent callers — only `cancel()` aborts. private _doConnectAbortController: AbortController | null = null; - constructor(connector: IConnector, options?: LedgerAdapterOptions) { + constructor(connector: IConnector) { this.connector = connector; - this._handleSelectDevice = options?.handleSelectDevice ?? false; this._jobQueue = new DeviceJobQueue(); this.registerEventListeners(); } @@ -220,12 +209,8 @@ export class LedgerAdapter implements IHardwareWallet { const devices = await this.connector.searchDevices(); - // Replace cache with this round's raw result. DMK paths used as - // connectId on USB are ephemeral (new UUID after each replug), so - // incremental writes leave stale entries pointing at devices DMK no - // longer recognizes — the visible symptom is the same physical device - // appearing twice in the discovered list, one with a real name and one - // with the 'Ledger' placeholder from a connect-time event. + // Replace cache with this round's raw result. DMK paths used as connectId + // on USB are ephemeral — incremental writes leave stale entries. this._discoveredDevices.clear(); for (const d of devices) { if (d.connectId) { @@ -233,7 +218,6 @@ export class LedgerAdapter implements IHardwareWallet { } } - // If no devices found, ensure permission (no connectId = search context) if (this._discoveredDevices.size === 0) { await this._ensureDevicePermission(); } @@ -246,6 +230,20 @@ export class LedgerAdapter implements IHardwareWallet { return Array.from(this._discoveredDevices.values()); } + // USB single-session invariant: evict all sessions, best-effort (see connectDevice). + private async _evictAllSessions(): Promise { + if (this._sessions.size === 0) return; + const stale = [...this._sessions.values()]; + this._sessions.clear(); + for (const sid of stale) { + try { + await this.connector.disconnect(sid); + } catch { + // best-effort + } + } + } + // Layer 2 retry budget after connection-class error. Each round delegates // to Layer 1 (which owns the unlock prompt). Layer 2 never emits UI itself. private static readonly MAX_BUSINESS_RETRY_BUDGET = 3; @@ -278,6 +276,12 @@ export class LedgerAdapter implements IHardwareWallet { }); } + // USB single-session invariant: evict any stale session before opening + // a new one — guards ensureConnected's ambient fallback against ghosts. + if (!isLedgerBleConnectionType(this.connector.connectionType)) { + await this._evictAllSessions(); + } + await this._ensureDevicePermission(connectId); const session = await this.connector.connect(connectId); @@ -616,7 +620,7 @@ export class LedgerAdapter implements IHardwareWallet { * * - If a session already exists for the given connectId, reuse it. * - If ANY session exists (Ledger IDs are ephemeral), reuse it. - * - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw. + * - Otherwise: search → exactly 1 USB device auto-connects; multiple or none throws. */ // Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections private _connectingPromise: Promise | null = null; @@ -763,7 +767,20 @@ export class LedgerAdapter implements IHardwareWallet { } if (connectId && this._sessions.has(connectId)) return connectId; - if (this._sessions.size > 0) { + // Ambient fallback only when the caller gave no target. On an explicit + // connectId miss, re-resolve THAT device via _doConnect — never route to + // the first session entry (a stale entry for B once made A's calls hit B). + if (!connectId && this._sessions.size > 0) { + // Fail loud rather than route to a wrong device; connectDevice evicts + // first, so size>1 here should be impossible. + if (!isLedgerBleConnectionType(this.connector.connectionType) && this._sessions.size > 1) { + throw Object.assign( + new Error( + 'Ledger USB session invariant violated: more than one session is active. Please reconnect the device.' + ), + { code: HardwareErrorCode.DeviceOneDeviceOnly } + ); + } return this._sessions.keys().next().value as string; } @@ -883,20 +900,44 @@ export class LedgerAdapter implements IHardwareWallet { devices: DeviceInfo[], targetConnectId?: string ): Promise { + if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length > 1) { + debugLog( + `[DMK] Multiple Ledger USB devices found (${devices.length}); refusing to auto-select by ephemeral connectId.` + ); + throw createMultipleUsbLedgerDevicesError(); + } + if (targetConnectId) { const target = devices.find( d => d.connectId === targetConnectId || d.deviceId === targetConnectId ); - if (!target) { - const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), { - code: HardwareErrorCode.DeviceNotFound, - }) as Error & { _tag?: string }; - if (isLedgerBleConnectionType(this.connector.connectionType)) { - err._tag = ERROR_TAG.DeviceNotAdvertising; - } - throw err; + if (target) { + return this._connectDeviceOrThrow(target.connectId); + } + + // USB single-device fallback: target not in fresh enumeration but exactly + // one device present — assume same device, new ephemeral path after replug. + // + // IMPORTANT: `devices.length` is the FRESH searchDevices() result, NOT + // cached `_discoveredDevices`. Do NOT "simplify" this to use + // _sessions.size or _discoveredDevices.size — those can carry ghost + // entries from failed device-state subscriptions, which would resurrect + // the multi-device drift bug. + if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) { + debugLog( + `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; ` + + `accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)` + ); + return this._connectDeviceOrThrow(devices[0].connectId); + } + + const err = Object.assign(new Error(`Target Ledger unavailable: ${targetConnectId}`), { + code: HardwareErrorCode.DeviceNotFound, + }) as Error & { _tag?: string }; + if (isLedgerBleConnectionType(this.connector.connectionType)) { + err._tag = ERROR_TAG.DeviceNotAdvertising; } - return this._connectDeviceOrThrow(target.connectId); + throw err; } if (isLedgerBleConnectionType(this.connector.connectionType)) { @@ -905,10 +946,13 @@ export class LedgerAdapter implements IHardwareWallet { }); } - const chosenConnectId = - devices.length === 1 ? devices[0].connectId : await this._chooseDeviceFromList(devices); + if (devices.length !== 1) { + throw Object.assign(new Error('Ledger device not found.'), { + code: HardwareErrorCode.DeviceNotFound, + }); + } - return this._connectDeviceOrThrow(chosenConnectId); + return this._connectDeviceOrThrow(devices[0].connectId); } private async _connectDeviceOrThrow(chosenConnectId: string): Promise { @@ -927,53 +971,6 @@ export class LedgerAdapter implements IHardwareWallet { return chosenConnectId; } - private async _chooseDeviceFromList(devices: DeviceInfo[]): Promise { - if (!this._handleSelectDevice) { - debugLog( - `[DMK] Multiple Ledger devices found (${devices.length}); handleSelectDevice=false, picking first (${devices[0].connectId}).` - ); - return devices[0].connectId; - } - - let response: { sdkConnectId: string } | undefined; - try { - const waitPromise = this._uiRegistry.wait<{ sdkConnectId: string }>( - UI_REQUEST.REQUEST_SELECT_DEVICE - ); - this.emitter.emit(UI_REQUEST.REQUEST_SELECT_DEVICE, { - type: UI_REQUEST.REQUEST_SELECT_DEVICE, - payload: { devices }, - }); - response = await waitPromise; - } catch (err) { - // Defensive: same-type re-fire of REQUEST_SELECT_DEVICE could preempt - // this wait. Surface as UserAborted — _doConnect's catch only treats - // Locked / NotAdvertising / Disconnected as recoverable; raw - // UiRequestPreempted would escape the retry loop. - if ((err as { _tag?: string })?._tag === UI_REQUEST_PREEMPTED_TAG) { - this.emitter.emit(UI_REQUEST.CLOSE_UI_WINDOW, { - type: UI_REQUEST.CLOSE_UI_WINDOW, - payload: {}, - }); - throw Object.assign(new Error('Device selection superseded'), { - _tag: ERROR_TAG.UserAborted, - code: HardwareErrorCode.UserAborted, - cause: err, - }); - } - throw err; - } - - const chosen = devices.find(d => d.connectId === response?.sdkConnectId); - if (!chosen) { - throw Object.assign( - new Error(`Selected sdkConnectId '${response?.sdkConnectId}' not in discovered list`), - { _tag: ERROR_TAG.DeviceNotRecognized } - ); - } - return chosen.connectId; - } - /** * Call the connector with automatic session resolution and disconnect retry. * @@ -1214,10 +1211,9 @@ export class LedgerAdapter implements IHardwareWallet { // from APDU 0x5515 (rare hardware-direct), and _doConnect's // dialog still covers it via the same UI request. - const retryConnectId = isLedgerBleConnectionType(this.connector.connectionType) - ? resolvedConnectId - : undefined; - const reConnectId = await this.ensureConnected(retryConnectId, signal); + // Preserve connectId across retry to prevent multi-device drift; + // USB replug is handled by _connectFirstOrSelect's fallback. + const reConnectId = await this.ensureConnected(resolvedConnectId, signal); const reSessionId = this._sessions.get(reConnectId); if (!reSessionId) throw lastErr; @@ -1313,12 +1309,9 @@ export class LedgerAdapter implements IHardwareWallet { ): Promise { await this._sleepAbortable(LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal); - // BLE: keep the same connectId so we don't re-prompt the user to pair. - // USB: pass undefined; ensureConnected enumerates fresh. - const retryTargetConnectId = isLedgerBleConnectionType(this.connector.connectionType) - ? resolvedConnectId - : undefined; - const retryConnectId = await this.ensureConnected(retryTargetConnectId, signal); + // Preserve connectId across retry (APDU 6901 didn't disconnect, so it's + // still valid); USB replug is handled by _connectFirstOrSelect's fallback. + const retryConnectId = await this.ensureConnected(resolvedConnectId, signal); const retrySessionId = this._sessions.get(retryConnectId); if (!retrySessionId) throw originalErr; diff --git a/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts b/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts index 8941de5e4..9f52f38dc 100644 --- a/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts +++ b/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts @@ -4,6 +4,7 @@ import { LedgerDeviceManager } from '../device/LedgerDeviceManager'; import { SignerManager } from '../signer/SignerManager'; import { ERROR_TAG, + createMultipleUsbLedgerDevicesError, isAppStuckByApdu, isKnownConnectionTag, isTransportStuck, @@ -287,6 +288,18 @@ export class LedgerConnectorBase implements IConnector { return descriptors; } + private _assertSingleUsbDescriptor(descriptors: DeviceDescriptor[]): void { + if (isLedgerBleConnectionType(this.connectionType) || descriptors.length <= 1) { + return; + } + debugLog( + `[DMK] Multiple Ledger USB devices found (${ + descriptors.length + }); refusing to choose by ephemeral path. paths=[${descriptors.map(d => d.path).join(',')}]` + ); + throw createMultipleUsbLedgerDevicesError(); + } + // --------------------------------------------------------------------------- // IConnector -- Device discovery // --------------------------------------------------------------------------- @@ -295,6 +308,7 @@ export class LedgerConnectorBase implements IConnector { const dm = await this._getDeviceManager(); const descriptors = await this._discoverDescriptors(dm); + this._assertSingleUsbDescriptor(descriptors); const resolvedDescriptors = descriptors.map(d => ({ descriptor: d, connectId: this._resolveConnectId(d), @@ -329,6 +343,10 @@ export class LedgerConnectorBase implements IConnector { // Only the explicit-connectId path gets the BLE direct-connect treatment. const callerSuppliedConnectId = Boolean(deviceId); let targetPath = deviceId; + if (callerSuppliedConnectId && !isLedgerBleConnectionType(this.connectionType)) { + const dm = await this._getDeviceManager(); + this._assertSingleUsbDescriptor(await this._discoverDescriptors(dm)); + } if (!targetPath) { const discovered = await this.searchDevices(); if (discovered.length === 0) { @@ -440,7 +458,19 @@ export class LedgerConnectorBase implements IConnector { } throw err; } - this._watchSessionState(sessionId, externalConnectId); + try { + this._watchSessionState(sessionId, externalConnectId); + } catch (subErr) { + // Subscription failure is fatal (see _watchSessionState) — disconnect + // the just-created DMK session so connect()'s catch wraps it normally. + debugLog('[DMK] state subscription failed during connect; disconnecting session:', subErr); + try { + await dm.disconnect(sessionId); + } catch { + // best-effort cleanup + } + throw subErr; + } const info = dm.getDiscoveredDeviceInfo(path); const session: ConnectorSession = { sessionId, @@ -536,8 +566,7 @@ export class LedgerConnectorBase implements IConnector { * `LedgerAdapter._sessions` map would hold a dead session entry until * the next call hit `DeviceSessionNotFound`. * - * Best-effort: any error subscribing is swallowed so a flaky DMK - * doesn't break the connect path. + * Subscribe failure is fatal — see the inline note at the subscribe call. */ private _watchSessionState(sessionId: string, externalConnectId: string): void { const dmk = this._dmk; @@ -550,29 +579,30 @@ export class LedgerConnectorBase implements IConnector { // ignore } } - try { - const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({ - next: (state: { deviceStatus?: string }) => { - // String-compare against DeviceStatus.NOT_CONNECTED ("NOT CONNECTED") - // to avoid pulling the runtime enum import (kept type-only for - // Metro/RN compatibility — see _importLedgerKit). - if (state?.deviceStatus === 'NOT CONNECTED') { - this._handleAutonomousDisconnect(sessionId, externalConnectId); - } - }, - error: () => { - // DMK closed the observable abnormally — treat as disconnect. + // Subscribe failure is fatal: without this subscription we can't detect + // autonomous disconnect (USB unplug, BLE drop, sleep), which leaks ghost + // entries into the adapter's _sessions map and can route subsequent calls + // to the wrong device. Let the error propagate so connect() cleans up the + // just-created DMK session and fails loudly. + const sub = dmk.getDeviceSessionState({ sessionId }).subscribe({ + next: (state: { deviceStatus?: string }) => { + // String-compare against DeviceStatus.NOT_CONNECTED ("NOT CONNECTED") + // to avoid pulling the runtime enum import (kept type-only for + // Metro/RN compatibility — see _importLedgerKit). + if (state?.deviceStatus === 'NOT CONNECTED') { this._handleAutonomousDisconnect(sessionId, externalConnectId); - }, - complete: () => { - // Observable completed — session is gone from DMK's POV. - this._handleAutonomousDisconnect(sessionId, externalConnectId); - }, - }); - this._sessionStateSubs.set(sessionId, sub); - } catch (err) { - debugLog('[DMK] _watchSessionState subscribe failed:', err); - } + } + }, + error: () => { + // DMK closed the observable abnormally — treat as disconnect. + this._handleAutonomousDisconnect(sessionId, externalConnectId); + }, + complete: () => { + // Observable completed — session is gone from DMK's POV. + this._handleAutonomousDisconnect(sessionId, externalConnectId); + }, + }); + this._sessionStateSubs.set(sessionId, sub); } private _unwatchSessionState(sessionId: string): void { diff --git a/packages/hwk-ledger-adapter/src/errors.ts b/packages/hwk-ledger-adapter/src/errors.ts index fccb4f03d..8f13615ec 100644 --- a/packages/hwk-ledger-adapter/src/errors.ts +++ b/packages/hwk-ledger-adapter/src/errors.ts @@ -2,6 +2,15 @@ import { HardwareErrorCode, enrichErrorMessage } from '@onekeyfe/hwk-adapter-cor import type { Failure } from '@onekeyfe/hwk-adapter-core'; +export const MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE = + 'Multiple Ledger USB devices are connected. Please connect only one Ledger device and try again.'; + +export function createMultipleUsbLedgerDevicesError(): Error & { code: HardwareErrorCode } { + return Object.assign(new Error(MULTIPLE_USB_LEDGER_DEVICES_ERROR_MESSAGE), { + code: HardwareErrorCode.DeviceOneDeviceOnly, + }); +} + // `_tag` survives errorToFailure → re-throw so SDK classifiers keep working. export type LedgerFailure = Omit & { payload: Failure['payload'] & { appName?: string; _tag?: string }; From 3a2eb459469210489b6114f67d28f9b2d27f626b Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Mon, 25 May 2026 12:02:08 +0800 Subject: [PATCH 2/4] fix(ledger): enforce single-USB only on auto-connect, not at discovery Addresses review feedback on multi-USB handling: - searchDevices() no longer rejects when multiple USB devices are present; it returns the full list so an explicit connectId can resolve its target. - _connectFirstOrSelect rejects multiple devices only on the no-target auto-connect path; an explicit connectId connects that device (or the sole USB device after an ephemeral-path replug) and otherwise fails not-found. --- .../src/__tests__/LedgerAdapter.test.ts | 28 +++++++++++++++++++ .../src/__tests__/LedgerConnectorBase.test.ts | 11 ++++---- .../src/adapter/LedgerAdapter.ts | 13 ++++----- .../src/connector/LedgerConnectorBase.ts | 24 ++++------------ 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts b/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts index 33ae28f66..0f26192aa 100644 --- a/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts +++ b/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts @@ -1269,6 +1269,34 @@ describe('LedgerAdapter', () => { expect(connector.connect).not.toHaveBeenCalled(); }); + it('connects the explicitly targeted device even when multiple USB devices are present', async () => { + connector.searchDevices.mockResolvedValueOnce([ + { connectId: 'dev-A', deviceId: 'dev-A', name: 'Nano X', model: 'nanoX' }, + { connectId: 'dev-B', deviceId: 'dev-B', name: 'Nano S', model: 'nanoS' }, + ]); + connector.connect.mockResolvedValueOnce({ + sessionId: 'session-B', + deviceInfo: { + vendor: 'ledger', + model: 'nanoS', + firmwareVersion: 'unknown', + deviceId: 'dev-B', + connectId: 'dev-B', + connectionType: 'usb', + }, + }); + connector.call.mockResolvedValueOnce({ address: '0xTARGET', publicKey: '0xpk' }); + + const result = await adapter.evmGetAddress('dev-B', '', { + path: "m/44'/60'/0'/0/0", + showOnDevice: false, + }); + + expect(result.success).toBe(true); + expect(connector.connect).toHaveBeenCalledWith('dev-B'); + expect(connector.connect).not.toHaveBeenCalledWith('dev-A'); + }); + it('should reject BLE business calls with an empty connectId instead of auto-selecting a device', async () => { const bleConnector = createMockConnector(); (bleConnector as unknown as { connectionType: string }).connectionType = 'ble'; diff --git a/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts b/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts index 85a683ecf..38671db88 100644 --- a/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts +++ b/packages/hwk-ledger-adapter/src/__tests__/LedgerConnectorBase.test.ts @@ -85,7 +85,7 @@ describe('LedgerConnectorBase BLE discovery', () => { }); }); -describe('LedgerConnectorBase USB single-device guard', () => { +describe('LedgerConnectorBase USB discovery', () => { const usbDescriptors: DeviceDescriptor[] = [ { path: 'usb-path-a', @@ -101,13 +101,12 @@ describe('LedgerConnectorBase USB single-device guard', () => { }, ]; - it('rejects USB search when multiple Ledger devices are visible', async () => { + it('returns all USB devices (single-device restriction is enforced on auto-connect, not discovery)', async () => { const connector = new SearchConnector(usbDescriptors, 'usb'); - await expect(connector.searchDevices()).rejects.toMatchObject({ - code: HardwareErrorCode.DeviceOneDeviceOnly, - message: expect.stringContaining('Multiple Ledger USB devices are connected'), - }); + const devices = await connector.searchDevices(); + + expect(devices.map(d => d.connectId)).toEqual(['usb-path-a', 'usb-path-b']); }); }); describe('LedgerConnectorBase BLE direct-connect gate', () => { diff --git a/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts b/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts index 4550df822..f9b0d1e34 100644 --- a/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts +++ b/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts @@ -900,13 +900,6 @@ export class LedgerAdapter implements IHardwareWallet { devices: DeviceInfo[], targetConnectId?: string ): Promise { - if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length > 1) { - debugLog( - `[DMK] Multiple Ledger USB devices found (${devices.length}); refusing to auto-select by ephemeral connectId.` - ); - throw createMultipleUsbLedgerDevicesError(); - } - if (targetConnectId) { const target = devices.find( d => d.connectId === targetConnectId || d.deviceId === targetConnectId @@ -946,6 +939,12 @@ export class LedgerAdapter implements IHardwareWallet { }); } + // No target + multiple USB devices: refuse to auto-pick by ephemeral + // connectId (the multi-device drift bug). Caller must specify which device. + if (devices.length > 1) { + throw createMultipleUsbLedgerDevicesError(); + } + if (devices.length !== 1) { throw Object.assign(new Error('Ledger device not found.'), { code: HardwareErrorCode.DeviceNotFound, diff --git a/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts b/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts index 9f52f38dc..78993f75b 100644 --- a/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts +++ b/packages/hwk-ledger-adapter/src/connector/LedgerConnectorBase.ts @@ -4,7 +4,6 @@ import { LedgerDeviceManager } from '../device/LedgerDeviceManager'; import { SignerManager } from '../signer/SignerManager'; import { ERROR_TAG, - createMultipleUsbLedgerDevicesError, isAppStuckByApdu, isKnownConnectionTag, isTransportStuck, @@ -288,27 +287,20 @@ export class LedgerConnectorBase implements IConnector { return descriptors; } - private _assertSingleUsbDescriptor(descriptors: DeviceDescriptor[]): void { - if (isLedgerBleConnectionType(this.connectionType) || descriptors.length <= 1) { - return; - } - debugLog( - `[DMK] Multiple Ledger USB devices found (${ - descriptors.length - }); refusing to choose by ephemeral path. paths=[${descriptors.map(d => d.path).join(',')}]` - ); - throw createMultipleUsbLedgerDevicesError(); - } - // --------------------------------------------------------------------------- // IConnector -- Device discovery + // + // Discovery never throws on multiple USB devices — it returns the full list. + // The "connect exactly one device" rule is enforced upstream: an explicit + // connectId connects that device (or fails as not-found), and only the + // no-connectId USB path rejects when more than one device is present + // (see LedgerAdapter._connectFirstOrSelect). // --------------------------------------------------------------------------- async searchDevices(): Promise { const dm = await this._getDeviceManager(); const descriptors = await this._discoverDescriptors(dm); - this._assertSingleUsbDescriptor(descriptors); const resolvedDescriptors = descriptors.map(d => ({ descriptor: d, connectId: this._resolveConnectId(d), @@ -343,10 +335,6 @@ export class LedgerConnectorBase implements IConnector { // Only the explicit-connectId path gets the BLE direct-connect treatment. const callerSuppliedConnectId = Boolean(deviceId); let targetPath = deviceId; - if (callerSuppliedConnectId && !isLedgerBleConnectionType(this.connectionType)) { - const dm = await this._getDeviceManager(); - this._assertSingleUsbDescriptor(await this._discoverDescriptors(dm)); - } if (!targetPath) { const discovered = await this.searchDevices(); if (discovered.length === 0) { From 345d9fc93b2ead678100b3c22ee4771fd9f86034 Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Wed, 27 May 2026 14:30:56 +0800 Subject: [PATCH 3/4] fix(ledger): guard USB fallback with fingerprint --- .../src/__tests__/LedgerAdapter.test.ts | 32 ++++- .../src/adapter/LedgerAdapter.ts | 116 +++++++++++------- 2 files changed, 104 insertions(+), 44 deletions(-) diff --git a/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts b/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts index 0f26192aa..2cabced32 100644 --- a/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts +++ b/packages/hwk-ledger-adapter/src/__tests__/LedgerAdapter.test.ts @@ -1090,15 +1090,20 @@ describe('LedgerAdapter', () => { ); }); - it('should retry with fresh connection on disconnect error', async () => { + it('should retry with a recovered USB connectId when fingerprint verification is available', async () => { + const expectedAddress = '0x1111111111111111111111111111111111111111'; + const expectedFingerprint = deriveDeviceFingerprint(expectedAddress); + // First: establish a session await adapter.connectDevice('dev-1'); // Simulate disconnect error on first call, success on retry connector.call + .mockResolvedValueOnce({ address: expectedAddress }) .mockRejectedValueOnce( Object.assign(new Error('session not found'), { _tag: 'DeviceSessionNotFound' }) ) + .mockResolvedValueOnce({ address: expectedAddress }) .mockResolvedValueOnce({ address: '0xRETRY' }); // After disconnect, searchDevices returns a new device ID (DMK regenerates UUIDs) @@ -1117,7 +1122,7 @@ describe('LedgerAdapter', () => { }, }); - const result = await adapter.evmGetAddress('dev-1', '', { + const result = await adapter.evmGetAddress('dev-1', expectedFingerprint, { path: "m/44'/60'/0'/0/0", showOnDevice: false, }); @@ -1136,6 +1141,29 @@ describe('LedgerAdapter', () => { ); }); + it('should fail closed when a USB target misses and no device fingerprint is available', async () => { + await adapter.connectDevice('dev-1'); + + connector.call.mockRejectedValueOnce( + Object.assign(new Error('session not found'), { _tag: 'DeviceSessionNotFound' }) + ); + connector.searchDevices.mockResolvedValueOnce([ + { connectId: 'dev-new', deviceId: 'dev-new', name: 'Nano X', model: 'nanoX' }, + ]); + + const result = await adapter.evmGetAddress('dev-1', '', { + path: "m/44'/60'/0'/0/0", + showOnDevice: false, + }); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.payload.code).toBe(HardwareErrorCode.DeviceNotFound); + expect(result.payload.error).toContain('Target Ledger unavailable: dev-1'); + } + expect(connector.connect).not.toHaveBeenCalledWith('dev-new'); + }); + it('should reconnect the original target after timeout reset', async () => { const expectedAddress = '0x1111111111111111111111111111111111111111'; const expectedFingerprint = deriveDeviceFingerprint(expectedAddress); diff --git a/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts b/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts index f9b0d1e34..45a0f4d7f 100644 --- a/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts +++ b/packages/hwk-ledger-adapter/src/adapter/LedgerAdapter.ts @@ -86,6 +86,12 @@ type IFingerprintVerifyResult = | { success: true } | { success: false; expected: string; actual: string }; +type ConnectorCallFingerprint = { + chain: ChainForFingerprint; + deviceId: string; + skipFingerprint: boolean; +}; + // Fingerprints are deterministic 16-char hashes of fixed testnet paths, // not secrets — safe to log. function formatDeviceMismatchError(expected: string, actual: string): string { @@ -756,7 +762,8 @@ export class LedgerAdapter implements IHardwareWallet { // doesn't kill caller B's await. private async ensureConnected( connectId: string | undefined, - signal: AbortSignal + signal: AbortSignal, + allowUsbEphemeralFallback = false ): Promise { if (signal.aborted) LedgerAdapter._throwIfAborted(signal); @@ -789,7 +796,7 @@ export class LedgerAdapter implements IHardwareWallet { const innerSignal = this._doConnectAbortController.signal; this._connectingPromise = (async () => { try { - return await this._doConnect(innerSignal, connectId); + return await this._doConnect(innerSignal, connectId, allowUsbEphemeralFallback); } finally { this._connectingPromise = null; this._doConnectAbortController = null; @@ -803,7 +810,11 @@ export class LedgerAdapter implements IHardwareWallet { // Layer 1 main loop — the ONLY place in SDK that emits unlock dialog. // Bounded by MAX_DOCONNECT_CONFIRMS — after N Confirms with no progress, // throw DeviceNotFound so the user is kicked out of the loop. - private async _doConnect(internalSignal: AbortSignal, targetConnectId?: string): Promise { + private async _doConnect( + internalSignal: AbortSignal, + targetConnectId?: string, + allowUsbEphemeralFallback = false + ): Promise { if (isLedgerBleConnectionType(this.connector.connectionType) && targetConnectId) { try { return await this._connectDeviceOrThrow(targetConnectId); @@ -854,7 +865,11 @@ export class LedgerAdapter implements IHardwareWallet { if (devices.length > 0) { try { - return await this._connectFirstOrSelect(devices, targetConnectId); + return await this._connectFirstOrSelect( + devices, + targetConnectId, + allowUsbEphemeralFallback + ); } catch (err) { // PairingFailure / DeviceMismatch / unclassified → throw out, no // Layer 1 retry. PairingRefused = user declined system pair prompt @@ -898,7 +913,8 @@ export class LedgerAdapter implements IHardwareWallet { private async _connectFirstOrSelect( devices: DeviceInfo[], - targetConnectId?: string + targetConnectId?: string, + allowUsbEphemeralFallback = false ): Promise { if (targetConnectId) { const target = devices.find( @@ -908,18 +924,25 @@ export class LedgerAdapter implements IHardwareWallet { return this._connectDeviceOrThrow(target.connectId); } - // USB single-device fallback: target not in fresh enumeration but exactly - // one device present — assume same device, new ephemeral path after replug. + // Decision: a stale USB target + fresh search returning exactly one + // Ledger is not proof that the sole Ledger is the original device. It + // might be A after a replug (safe to recover), or B after A was unplugged + // (wrong-device risk). // - // IMPORTANT: `devices.length` is the FRESH searchDevices() result, NOT - // cached `_discoveredDevices`. Do NOT "simplify" this to use - // _sessions.size or _discoveredDevices.size — those can carry ghost - // entries from failed device-state subscriptions, which would resurrect - // the multi-device drift bug. - if (!isLedgerBleConnectionType(this.connector.connectionType) && devices.length === 1) { + // We only take this recovery path when the business call supplied a + // stable wallet fingerprint (`deviceId`) and did not opt out of + // fingerprint checks. The connection is then provisional: the caller must + // run `_verifyDeviceFingerprintWithSession` before sending the real APDU. + // Calls without that identity check fail closed with DeviceNotFound so + // the host can ask the user to reconnect/select again. + if ( + !isLedgerBleConnectionType(this.connector.connectionType) && + devices.length === 1 && + allowUsbEphemeralFallback + ) { debugLog( `[LedgerAdapter] target ${targetConnectId} not in fresh enumeration; ` + - `accepting sole USB device ${devices[0].connectId} (assumed ephemeral path change)` + `accepting sole USB device ${devices[0].connectId} for fingerprint-verified recovery` ); return this._connectDeviceOrThrow(devices[0].connectId); } @@ -982,11 +1005,7 @@ export class LedgerAdapter implements IHardwareWallet { connectId: string, method: string, params: unknown, - fingerprint?: { - chain: ChainForFingerprint; - deviceId: string; - skipFingerprint: boolean; - }, + fingerprint?: ConnectorCallFingerprint, permissionDeviceId?: string ): Promise { debugLog('[LedgerAdapter] connectorCall:', method, 'connectId:', connectId || '(empty)'); @@ -1050,11 +1069,7 @@ export class LedgerAdapter implements IHardwareWallet { method: string, params: unknown, signal: AbortSignal, - fingerprint?: { - chain: ChainForFingerprint; - deviceId: string; - skipFingerprint: boolean; - }, + fingerprint?: ConnectorCallFingerprint, permissionDeviceId?: string ): Promise { LedgerAdapter._throwIfAborted(signal); @@ -1076,11 +1091,17 @@ export class LedgerAdapter implements IHardwareWallet { effectiveParams = gatedParams; } + const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint; + // Wrap ensureConnected in _abortable so an abort during device discovery / // user-connect UI wait rejects this caller immediately. The underlying // _doConnect / _connectingPromise is shared across callers and continues // running — other concurrent callers aren't affected. - const resolvedConnectId = await this.ensureConnected(connectId, signal); + const resolvedConnectId = await this.ensureConnected( + connectId, + signal, + allowUsbEphemeralFallback + ); const sessionId = this._sessions.get(resolvedConnectId); if (!sessionId) { throw Object.assign(new Error('Auto-connect succeeded but no session found'), { @@ -1210,9 +1231,14 @@ export class LedgerAdapter implements IHardwareWallet { // from APDU 0x5515 (rare hardware-direct), and _doConnect's // dialog still covers it via the same UI request. - // Preserve connectId across retry to prevent multi-device drift; - // USB replug is handled by _connectFirstOrSelect's fallback. - const reConnectId = await this.ensureConnected(resolvedConnectId, signal); + // Preserve connectId across retry to prevent multi-device drift. + // USB replug fallback is allowed only when this call will verify + // the device fingerprint before sending the business APDU. + const reConnectId = await this.ensureConnected( + resolvedConnectId, + signal, + allowUsbEphemeralFallback + ); const reSessionId = this._sessions.get(reConnectId); if (!reSessionId) throw lastErr; @@ -1300,17 +1326,18 @@ export class LedgerAdapter implements IHardwareWallet { params: unknown, signal: AbortSignal, originalErr: unknown, - fingerprint?: { - chain: ChainForFingerprint; - deviceId: string; - skipFingerprint: boolean; - } + fingerprint?: ConnectorCallFingerprint ): Promise { await this._sleepAbortable(LedgerAdapter.STUCK_APP_RETRY_DELAY_MS, signal); // Preserve connectId across retry (APDU 6901 didn't disconnect, so it's - // still valid); USB replug is handled by _connectFirstOrSelect's fallback. - const retryConnectId = await this.ensureConnected(resolvedConnectId, signal); + // still valid). If a USB replug changed the connectId, recover only when + // the call has a fingerprint check queued immediately after reconnect. + const retryConnectId = await this.ensureConnected( + resolvedConnectId, + signal, + !!fingerprint?.deviceId && !fingerprint.skipFingerprint + ); const retrySessionId = this._sessions.get(retryConnectId); if (!retrySessionId) throw originalErr; @@ -1364,18 +1391,19 @@ export class LedgerAdapter implements IHardwareWallet { params: unknown, signal: AbortSignal, originalErr: unknown, - fingerprint?: { - chain: ChainForFingerprint; - deviceId: string; - skipFingerprint: boolean; - } + fingerprint?: ConnectorCallFingerprint ): Promise { this.connector.reset(); this._sessions.clear(); this._discoveredDevices.clear(); this._connectingPromise = null; - const retryConnectId = await this.ensureConnected(targetConnectId, signal); + const allowUsbEphemeralFallback = !!fingerprint?.deviceId && !fingerprint.skipFingerprint; + const retryConnectId = await this.ensureConnected( + targetConnectId, + signal, + allowUsbEphemeralFallback + ); const retrySessionId = this._sessions.get(retryConnectId); if (!retrySessionId) { throw originalErr; @@ -1417,7 +1445,11 @@ export class LedgerAdapter implements IHardwareWallet { this._sessions.clear(); this._discoveredDevices.clear(); this._connectingPromise = null; - const finalConnectId = await this.ensureConnected(targetConnectId, signal); + const finalConnectId = await this.ensureConnected( + targetConnectId, + signal, + allowUsbEphemeralFallback + ); const finalSessionId = this._sessions.get(finalConnectId); if (!finalSessionId) { throw originalErr; From b69637aa945c316e4799c722b96862c9bf44bc5b Mon Sep 17 00:00:00 2001 From: ByteZhang Date: Wed, 27 May 2026 18:18:43 +0800 Subject: [PATCH 4/4] chore(release): 1.1.27-alpha.4 --- .../electron-example/package.json | 4 ++-- .../connect-examples/expo-example/package.json | 10 +++++----- .../expo-playground/package.json | 8 ++++---- packages/core/package.json | 6 +++--- packages/hd-ble-sdk/package.json | 8 ++++---- packages/hd-cli/package.json | 10 +++++----- packages/hd-common-connect-sdk/package.json | 16 ++++++++-------- packages/hd-transport-electron/package.json | 8 ++++---- packages/hd-transport-emulator/package.json | 6 +++--- packages/hd-transport-http/package.json | 6 +++--- packages/hd-transport-lowlevel/package.json | 6 +++--- packages/hd-transport-react-native/package.json | 8 ++++---- packages/hd-transport-usb/package.json | 6 +++--- packages/hd-transport-web-device/package.json | 8 ++++---- packages/hd-transport/package.json | 2 +- packages/hd-web-sdk/package.json | 10 +++++----- packages/hwk-adapter-core/package.json | 2 +- packages/hwk-ledger-adapter/package.json | 4 ++-- packages/hwk-ledger-connector-ble/package.json | 6 +++--- .../hwk-ledger-connector-webhid/package.json | 6 +++--- packages/shared/package.json | 2 +- 21 files changed, 71 insertions(+), 71 deletions(-) diff --git a/packages/connect-examples/electron-example/package.json b/packages/connect-examples/electron-example/package.json index 656f925c6..1b36f1ab8 100644 --- a/packages/connect-examples/electron-example/package.json +++ b/packages/connect-examples/electron-example/package.json @@ -2,7 +2,7 @@ "name": "hardware-example", "productName": "HardwareExample", "executableName": "onekey-hardware-example", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "author": "OneKey", "description": "End-to-end encrypted workspaces for teams", "main": "dist/index.js", @@ -22,7 +22,7 @@ "ts:check": "yarn tsc --noEmit" }, "dependencies": { - "@onekeyfe/hd-transport-electron": "1.1.27-alpha.3", + "@onekeyfe/hd-transport-electron": "1.1.27-alpha.4", "@stoprocent/noble": "2.3.16", "debug": "4.3.4", "electron-is-dev": "^3.0.1", diff --git a/packages/connect-examples/expo-example/package.json b/packages/connect-examples/expo-example/package.json index 68afec412..297ae7e57 100644 --- a/packages/connect-examples/expo-example/package.json +++ b/packages/connect-examples/expo-example/package.json @@ -1,6 +1,6 @@ { "name": "expo-example", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "scripts": { "start": "cross-env CONNECT_SRC=https://localhost:8087/ yarn expo start --dev-client", "android": "yarn expo run:android", @@ -19,10 +19,10 @@ "@noble/ed25519": "^2.1.0", "@noble/hashes": "^1.3.3", "@noble/secp256k1": "^1.7.1", - "@onekeyfe/hd-ble-sdk": "1.1.27-alpha.3", - "@onekeyfe/hd-common-connect-sdk": "1.1.27-alpha.3", - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-web-sdk": "1.1.27-alpha.3", + "@onekeyfe/hd-ble-sdk": "1.1.27-alpha.4", + "@onekeyfe/hd-common-connect-sdk": "1.1.27-alpha.4", + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-web-sdk": "1.1.27-alpha.4", "@onekeyfe/react-native-ble-utils": "^0.1.3", "@polkadot/util-crypto": "13.1.1", "@react-native-async-storage/async-storage": "1.21.0", diff --git a/packages/connect-examples/expo-playground/package.json b/packages/connect-examples/expo-playground/package.json index 2adcc21a1..c4dfe97ce 100644 --- a/packages/connect-examples/expo-playground/package.json +++ b/packages/connect-examples/expo-playground/package.json @@ -1,6 +1,6 @@ { "name": "onekey-hardware-playground", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "private": true, "sideEffects": [ "app/utils/shim.js", @@ -17,9 +17,9 @@ }, "dependencies": { "@noble/hashes": "^1.8.0", - "@onekeyfe/hd-common-connect-sdk": "1.1.27-alpha.3", - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-shared": "1.1.27-alpha.3", + "@onekeyfe/hd-common-connect-sdk": "1.1.27-alpha.4", + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", diff --git a/packages/core/package.json b/packages/core/package.json index 53586b086..960f2a6ff 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-core", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "Core processes and APIs for communicating with OneKey hardware devices.", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", @@ -25,8 +25,8 @@ "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues" }, "dependencies": { - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4", "axios": "1.15.2", "bignumber.js": "^9.0.2", "bytebuffer": "^5.0.1", diff --git a/packages/hd-ble-sdk/package.json b/packages/hd-ble-sdk/package.json index aeb9f8865..601fbb395 100644 --- a/packages/hd-ble-sdk/package.json +++ b/packages/hd-ble-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-ble-sdk", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "ISC", @@ -20,8 +20,8 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-react-native": "1.1.27-alpha.3" + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-react-native": "1.1.27-alpha.4" } } diff --git a/packages/hd-cli/package.json b/packages/hd-cli/package.json index 023fd416d..0e5183c15 100644 --- a/packages/hd-cli/package.json +++ b/packages/hd-cli/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hardware-cli", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "OneKey hardware wallet CLI for testing device communication", "author": "OneKey", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "test": "jest" }, "dependencies": { - "@onekeyfe/hd-common-connect-sdk": "1.1.27-alpha.3", - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-usb": "1.1.27-alpha.3", + "@onekeyfe/hd-common-connect-sdk": "1.1.27-alpha.4", + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-usb": "1.1.27-alpha.4", "commander": "^12.0.0" } } diff --git a/packages/hd-common-connect-sdk/package.json b/packages/hd-common-connect-sdk/package.json index e6357b92e..0f7afa27c 100644 --- a/packages/hd-common-connect-sdk/package.json +++ b/packages/hd-common-connect-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-common-connect-sdk", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "ISC", @@ -20,12 +20,12 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-emulator": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-http": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-lowlevel": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-usb": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-web-device": "1.1.27-alpha.3" + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-emulator": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-http": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-lowlevel": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-usb": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-web-device": "1.1.27-alpha.4" } } diff --git a/packages/hd-transport-electron/package.json b/packages/hd-transport-electron/package.json index 5f5b06f67..2cac25ed0 100644 --- a/packages/hd-transport-electron/package.json +++ b/packages/hd-transport-electron/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport-electron", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "MIT", @@ -25,9 +25,9 @@ "electron-log": ">=4.0.0" }, "dependencies": { - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3", + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4", "@stoprocent/noble": "2.3.16", "p-retry": "^4.6.2" }, diff --git a/packages/hd-transport-emulator/package.json b/packages/hd-transport-emulator/package.json index 75e157e0a..a2d2adff0 100644 --- a/packages/hd-transport-emulator/package.json +++ b/packages/hd-transport-emulator/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport-emulator", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "hardware emulator transport", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", @@ -24,8 +24,8 @@ "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues" }, "dependencies": { - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4", "axios": "1.15.2", "secure-json-parse": "^4.0.0" } diff --git a/packages/hd-transport-http/package.json b/packages/hd-transport-http/package.json index 06714a621..973d493a4 100644 --- a/packages/hd-transport-http/package.json +++ b/packages/hd-transport-http/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport-http", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "hardware http transport", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", @@ -24,8 +24,8 @@ "url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues" }, "dependencies": { - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4", "axios": "1.15.2", "secure-json-parse": "^4.0.0" } diff --git a/packages/hd-transport-lowlevel/package.json b/packages/hd-transport-lowlevel/package.json index a84679162..718ddd71d 100644 --- a/packages/hd-transport-lowlevel/package.json +++ b/packages/hd-transport-lowlevel/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport-lowlevel", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "MIT", "main": "dist/index.js", @@ -19,7 +19,7 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3" + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4" } } diff --git a/packages/hd-transport-react-native/package.json b/packages/hd-transport-react-native/package.json index d01fc5a37..a64a4cb89 100644 --- a/packages/hd-transport-react-native/package.json +++ b/packages/hd-transport-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport-react-native", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "MIT", "main": "dist/index.js", @@ -19,9 +19,9 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3", + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4", "@onekeyfe/react-native-ble-utils": "^0.1.4", "react-native-ble-plx": "3.5.1" } diff --git a/packages/hd-transport-usb/package.json b/packages/hd-transport-usb/package.json index 4a9d467d5..ea27f8bc8 100644 --- a/packages/hd-transport-usb/package.json +++ b/packages/hd-transport-usb/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport-usb", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "OneKey hardware wallet direct USB transport plugin (libusb)", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "MIT", @@ -20,8 +20,8 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4", "bytebuffer": "^5.0.1", "usb": "^2.14.0" } diff --git a/packages/hd-transport-web-device/package.json b/packages/hd-transport-web-device/package.json index 88255edfb..bbfa5f02e 100644 --- a/packages/hd-transport-web-device/package.json +++ b/packages/hd-transport-web-device/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport-web-device", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "MIT", @@ -20,11 +20,11 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport": "1.1.27-alpha.3" + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport": "1.1.27-alpha.4" }, "devDependencies": { - "@onekeyfe/hd-transport-electron": "1.1.27-alpha.3", + "@onekeyfe/hd-transport-electron": "1.1.27-alpha.4", "@types/w3c-web-usb": "^1.0.6", "@types/web-bluetooth": "^0.0.17" } diff --git a/packages/hd-transport/package.json b/packages/hd-transport/package.json index 9e78b2316..91fabd115 100644 --- a/packages/hd-transport/package.json +++ b/packages/hd-transport/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-transport", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "Transport layer abstractions and utilities for OneKey hardware SDK.", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", diff --git a/packages/hd-web-sdk/package.json b/packages/hd-web-sdk/package.json index cd00238f3..f3b4eb7f7 100644 --- a/packages/hd-web-sdk/package.json +++ b/packages/hd-web-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-web-sdk", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "author": "OneKey", "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme", "license": "ISC", @@ -21,10 +21,10 @@ }, "dependencies": { "@onekeyfe/cross-inpage-provider-core": "2.2.67", - "@onekeyfe/hd-core": "1.1.27-alpha.3", - "@onekeyfe/hd-shared": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-http": "1.1.27-alpha.3", - "@onekeyfe/hd-transport-web-device": "1.1.27-alpha.3" + "@onekeyfe/hd-core": "1.1.27-alpha.4", + "@onekeyfe/hd-shared": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-http": "1.1.27-alpha.4", + "@onekeyfe/hd-transport-web-device": "1.1.27-alpha.4" }, "devDependencies": { "@babel/plugin-proposal-optional-chaining": "^7.17.12", diff --git a/packages/hwk-adapter-core/package.json b/packages/hwk-adapter-core/package.json index 25086b638..bcd12a20c 100644 --- a/packages/hwk-adapter-core/package.json +++ b/packages/hwk-adapter-core/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hwk-adapter-core", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "Shared types and utilities for OneKey hardware wallet kit", "author": "OneKey", "license": "MIT", diff --git a/packages/hwk-ledger-adapter/package.json b/packages/hwk-ledger-adapter/package.json index c52be7731..b6f90c941 100644 --- a/packages/hwk-ledger-adapter/package.json +++ b/packages/hwk-ledger-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hwk-ledger-adapter", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "Ledger hardware wallet adapter for OneKey", "author": "OneKey", "license": "MIT", @@ -50,7 +50,7 @@ "@ledgerhq/device-signer-kit-solana": "^1.7.0", "@ledgerhq/hw-app-trx": "^6.34.1", "@ledgerhq/hw-transport": "^6.34.1", - "@onekeyfe/hwk-adapter-core": "1.1.27-alpha.3", + "@onekeyfe/hwk-adapter-core": "1.1.27-alpha.4", "bitcoinjs-lib": "npm:@onekeyfe/bitcoinjs-lib@7.0.1" }, "devDependencies": { diff --git a/packages/hwk-ledger-connector-ble/package.json b/packages/hwk-ledger-connector-ble/package.json index fcd70b82a..5d03faa8e 100644 --- a/packages/hwk-ledger-connector-ble/package.json +++ b/packages/hwk-ledger-connector-ble/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hwk-ledger-connector-ble", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "IConnector implementation for Ledger hardware wallets via React Native BLE", "author": "OneKey", "license": "MIT", @@ -51,8 +51,8 @@ "@ledgerhq/device-signer-kit-ethereum": "^1.9.0", "@ledgerhq/device-signer-kit-solana": "^1.7.0", "@ledgerhq/device-transport-kit-react-native-ble": "^1.0.0", - "@onekeyfe/hwk-adapter-core": "1.1.27-alpha.3", - "@onekeyfe/hwk-ledger-adapter": "1.1.27-alpha.3" + "@onekeyfe/hwk-adapter-core": "1.1.27-alpha.4", + "@onekeyfe/hwk-ledger-adapter": "1.1.27-alpha.4" }, "peerDependencies": { "react-native": "*" diff --git a/packages/hwk-ledger-connector-webhid/package.json b/packages/hwk-ledger-connector-webhid/package.json index 96e982423..a83a52e9a 100644 --- a/packages/hwk-ledger-connector-webhid/package.json +++ b/packages/hwk-ledger-connector-webhid/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hwk-ledger-connector-webhid", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "IConnector implementation for Ledger hardware wallets via WebHID (DMK)", "author": "OneKey", "license": "MIT", @@ -49,8 +49,8 @@ "@ledgerhq/device-signer-kit-ethereum": "^1.9.0", "@ledgerhq/device-signer-kit-solana": "^1.7.0", "@ledgerhq/device-transport-kit-web-hid": "^1.0.0", - "@onekeyfe/hwk-adapter-core": "1.1.27-alpha.3", - "@onekeyfe/hwk-ledger-adapter": "1.1.27-alpha.3" + "@onekeyfe/hwk-adapter-core": "1.1.27-alpha.4", + "@onekeyfe/hwk-ledger-adapter": "1.1.27-alpha.4" }, "devDependencies": { "rimraf": "^5.0.0", diff --git a/packages/shared/package.json b/packages/shared/package.json index 5b8fe3d6e..4fe5e548a 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/hd-shared", - "version": "1.1.27-alpha.3", + "version": "1.1.27-alpha.4", "description": "Hardware SDK's shared tool library", "keywords": [ "Hardware-SDK",