Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@
"@onekeyfe/hd-transport": "1.1.26-alpha.11",
"@onekeyfe/hd-transport-electron": "1.1.26-alpha.11",
"@onekeyfe/hd-web-sdk": "1.1.26-alpha.11",
"@onekeyfe/hwk-adapter-core": "1.1.26-alpha.11",
"@onekeyfe/hwk-ledger-adapter": "1.1.26-alpha.11",
"@onekeyfe/hwk-ledger-connector-ble": "1.1.26-alpha.11",
"@onekeyfe/hwk-ledger-connector-webhid": "1.1.26-alpha.11",
"@onekeyfe/hwk-adapter-core": "1.1.26-alpha.12",
"@onekeyfe/hwk-ledger-adapter": "1.1.26-alpha.12",
"@onekeyfe/hwk-ledger-connector-ble": "1.1.26-alpha.12",
"@onekeyfe/hwk-ledger-connector-webhid": "1.1.26-alpha.12",
"@onekeyfe/onekey-cross-webview": "2.2.68",
"@polkadot/extension-inject": "0.54.1",
"@polkadot/types": "14.3.1",
Expand Down
12 changes: 9 additions & 3 deletions packages/kit-bg/src/dbs/local/LocalDbBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3320,10 +3320,16 @@ export abstract class LocalDbBase extends LocalDbBaseContainer {
const { connectId } = device;
const resolvedVendor = vendor ?? EHardwareVendor.onekey;
const profile = getVendorProfile(resolvedVendor);
const isUsbTransport =
transportType === EHardwareTransportType.WEBUSB ||
transportType === EHardwareTransportType.Bridge;

// Allow empty connectId only for vendors without persistent USB connectId
// (e.g. Ledger DMK generates temporary UUIDs that change every session).
if (!connectId && profile.hasPersistentConnectId('usb')) {
// Empty connectId is allowed only for non-persistent USB transports.
if (
!connectId &&
(profile.hasPersistentConnectId('usb') ||
(profile.isThirdParty && !isUsbTransport))
) {
throw new OneKeyLocalError('createHwWallet ERROR: connectId is required');
}
const context = await this.getContext();
Expand Down
145 changes: 104 additions & 41 deletions packages/kit-bg/src/services/ServiceAccount/ServiceAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import networkUtils from '@onekeyhq/shared/src/utils/networkUtils';
import { EMnemonicType } from '@onekeyhq/shared/src/utils/secret';
import stringUtils from '@onekeyhq/shared/src/utils/stringUtils';
import timerUtils from '@onekeyhq/shared/src/utils/timerUtils';
import { EHardwareTransportType } from '@onekeyhq/shared/types';
import type { IServerNetwork } from '@onekeyhq/shared/types';
import type {
IBatchCreateAccount,
Expand Down Expand Up @@ -3128,8 +3129,20 @@ class ServiceAccount extends ServiceBase {
| EHardwareVendor
| undefined);
const vendorProfile = vendor ? getVendorProfile(vendor) : undefined;
const isUsbTransport =
transportType === EHardwareTransportType.WEBUSB ||
transportType === EHardwareTransportType.Bridge;
if (
vendorProfile?.isThirdParty &&
!params.device.connectId &&
!isUsbTransport
) {
throw new OneKeyLocalError(
'createHWWalletBase ERROR: connectId is required for non-USB third-party hardware',
);
}

// Vendors without persistent USB connectId don't need compatible resolution
// Skip compatibility lookup for vendors without persistent USB connectId.
const compatibleConnectId =
vendorProfile?.isThirdParty &&
!vendorProfile.hasPersistentConnectId('usb')
Expand All @@ -3139,18 +3152,11 @@ class ServiceAccount extends ServiceBase {
featuresDeviceId: params.device.deviceId ?? '',
hardwareCallContext: EHardwareCallContext.USER_INTERACTION,
});
const searchDeviceId = params.device.deviceId ?? '';
const deviceId = deviceUtils.getRawDeviceId({
device: params.device,
features,
});

console.log('createHWWalletBase paramsInfo', {
connectId: compatibleConnectId,
deviceId,
searchDeviceId,
});

let xfp: string | undefined;
if (fillingXfpByCallingSdk && !isMockedStandardHwWallet) {
xfp = await this.backgroundApi.serviceHardware.buildHwWalletXfp({
Expand All @@ -3161,9 +3167,8 @@ class ServiceAccount extends ServiceBase {
withUserInteraction: true,
vendor,
});
console.log('createHWWalletBase xfp', xfp, compatibleConnectId, deviceId);
}
// if the connectId is not compatible, maybe the device is new bluetooth connection device, refresh the device info
// Refresh DB info when compatibility lookup resolves to another connectId.
if (compatibleConnectId !== params.device.connectId) {
const refreshedDevice = await localDb.getDeviceByQuery({
connectId: params.device.connectId || compatibleConnectId,
Expand Down Expand Up @@ -3205,10 +3210,7 @@ class ServiceAccount extends ServiceBase {
: undefined,
transportType,
});
// Chain fingerprints for third-party vendors (Ledger) are generated on-demand
// by ensureLedgerChainFingerprint() in KeyringHardwareBase when a chain's
// keyring is first used. This ensures the fingerprint is generated using the
// SDK's getChainFingerprint() method (single source of truth for hashing).
// Third-party chain fingerprints are generated lazily by the keyring via SDK.

appEventBus.emit(EAppEventBusNames.WalletUpdate, undefined);
return result;
Expand Down Expand Up @@ -4154,6 +4156,54 @@ class ServiceAccount extends ServiceBase {
return result;
}

/** Onboarding orphan cleanup. HW + ledger + 0 accounts; no password prompt. */
@backgroundMethod()
async removeFailedOnboardingHwWallet({ walletId }: { walletId: string }) {
if (!walletId) {
throw new OneKeyLocalError('walletId is required');
}
if (!accountUtils.isHwWallet({ walletId })) {
throw new OneKeyLocalError(
'removeFailedOnboardingHwWallet: only HW wallet allowed',
);
}

const wallet = await this.getWalletSafe({ walletId });
if (!wallet) {
// Already removed; keep cleanup idempotent.
return;
}

const vendor = wallet.associatedDeviceInfo?.vendor;
if (vendor !== EHardwareVendor.ledger) {
throw new OneKeyLocalError(
`removeFailedOnboardingHwWallet: vendor must be ledger (got ${
vendor ?? 'undefined'
})`,
);
}

// Re-check every indexed account before treating it as an orphan.
const { accounts: indexedAccounts } = await this.getIndexedAccountsOfWallet(
{
walletId,
},
);
for (const indexed of indexedAccounts) {
const { accounts } = await this.getAccountsInSameIndexedAccountId({
indexedAccountId: indexed.id,
});
if (accounts.length > 0) {
throw new OneKeyLocalError(
`removeFailedOnboardingHwWallet: wallet ${walletId} has accounts; not an orphan`,
);
}
}

// localDb.removeWallet handles events, unused devices, and indexed accounts.
await localDb.removeWallet({ walletId });
}

async buildAccountXpubOrAddress({
getAccountXpubFn,
getAccountAddressFn,
Expand Down Expand Up @@ -5501,34 +5551,47 @@ class ServiceAccount extends ServiceBase {

const isHwWallet = accountUtils.isHwWallet({ walletId });
if (isHwWallet) {
const wallet = await localDb.getWalletSafe({ walletId });
if (
wallet &&
!wallet?.deprecated &&
!accountUtils.isValidWalletXfp({ xfp: wallet.xfp })
) {
await hardwareWalletXfpStatusAtom.set((v) => ({
...v,
[walletId]: {
...v?.[walletId],
xfpMissing: true,
},
}));
}
// Third-party HW uses chain fingerprints, not wallet xfp.
const isThirdParty = await this.isThirdPartyHwByWalletId({ walletId });
if (isThirdParty) {
const status = await hardwareWalletXfpStatusAtom.get();
if (status?.[walletId]?.xfpMissing) {
await hardwareWalletXfpStatusAtom.set((v) => ({
...v,
[walletId]: { ...v?.[walletId], xfpMissing: false },
}));
}
} else {
const wallet = await localDb.getWalletSafe({ walletId });
if (
wallet &&
!wallet?.deprecated &&
!accountUtils.isValidWalletXfp({ xfp: wallet.xfp })
) {
await hardwareWalletXfpStatusAtom.set((v) => ({
...v,
[walletId]: {
...v?.[walletId],
xfpMissing: true,
},
}));
}

const hardwareWalletXfpStatus = await hardwareWalletXfpStatusAtom.get();
if (
hardwareWalletXfpStatus?.[walletId]?.xfpMissing &&
wallet &&
accountUtils.isValidWalletXfp({ xfp: wallet.xfp })
) {
await hardwareWalletXfpStatusAtom.set((v) => ({
...v,
[walletId]: {
...v?.[walletId],
xfpMissing: false,
},
}));
const hardwareWalletXfpStatus =
await hardwareWalletXfpStatusAtom.get();
if (
hardwareWalletXfpStatus?.[walletId]?.xfpMissing &&
wallet &&
accountUtils.isValidWalletXfp({ xfp: wallet.xfp })
) {
await hardwareWalletXfpStatusAtom.set((v) => ({
...v,
[walletId]: {
...v?.[walletId],
xfpMissing: false,
},
}));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
import { HardwareErrorCode as ThirdPartyHwErrorCode } from '@onekeyfe/hwk-adapter-core';
import { ORPHAN_ELIGIBLE_ERROR_CODES } from '@onekeyfe/hwk-adapter-core';
import { chunk, isNil, range, uniqBy } from 'lodash';

import {
Expand Down Expand Up @@ -215,7 +215,6 @@ class ServiceBatchCreateAccount extends ServiceBase {
networkId: payload.params.networkId,
}),
]);

const hwRootFingerprintInfo: {
rootFingerprint: number | undefined;
} = {
Expand Down Expand Up @@ -362,7 +361,6 @@ class ServiceBatchCreateAccount extends ServiceBase {
walletId,
hardwareCallContext: EHardwareCallContext.USER_INTERACTION,
});

let hwAllNetworkPrepareAccountsResponse:
| IHwAllNetworkPrepareAccountsResponse
| undefined;
Expand Down Expand Up @@ -927,7 +925,6 @@ class ServiceBatchCreateAccount extends ServiceBase {
walletId: params.walletId,
hardwareCallContext: EHardwareCallContext.USER_INTERACTION,
});

let hwAllNetworkPrepareAccountsResponse:
| IHwAllNetworkPrepareAccountsResponse
| undefined;
Expand Down Expand Up @@ -1094,17 +1091,15 @@ class ServiceBatchCreateAccount extends ServiceBase {
isHardwareErrorByCode({
error,
code: [
// OneKey HW (legacy enum)
HardwareErrorCode.DeviceNotFound,
// **** PIN\passphrase cancel
HardwareErrorCode.PinCancelled,
HardwareErrorCode.ActionCancelled,
HardwareErrorCode.CallQueueActionCancelled,
HardwareErrorCode.DeviceInterruptedFromOutside, // cancel PIN from app
HardwareErrorCode.DeviceInterruptedFromUser, // cancel PIN from app
// **** third-party hardware
ThirdPartyHwErrorCode.UserAborted,
ThirdPartyHwErrorCode.DeviceDisconnected,
ThirdPartyHwErrorCode.DeviceAppStuck,
HardwareErrorCode.DeviceInterruptedFromOutside,
HardwareErrorCode.DeviceInterruptedFromUser,
// Third-party HW batch-abort codes from SDK.
...ORPHAN_ELIGIBLE_ERROR_CODES,
],
})
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { mapThirdPartyDeviceToSearchDevice } from './thirdPartyDeviceMapping';

describe('ServiceHardware Ledger BLE device mapping', () => {
it('preserves the BLE four-character connectId and actual name when connectionType is missing', () => {
const ledgerDevice = {
vendor: 'ledger',
model: 'nanoX',
firmwareVersion: '',
deviceId: '0738',
connectId: '0738',
label: 'Leo',
};

const result = mapThirdPartyDeviceToSearchDevice({
device: ledgerDevice as never,
defaultDeviceName: 'Ledger',
});

expect(result).toMatchObject({
connectId: '0738',
name: 'Leo',
});
});

it('uses the actual BLE device name instead of the vendor default', () => {
const result = mapThirdPartyDeviceToSearchDevice({
device: {
vendor: 'ledger',
model: 'nanoX',
firmwareVersion: '',
deviceId: 'A58F',
connectId: 'A58F',
label: 'Andox',
connectionType: 'ble',
} as never,
defaultDeviceName: 'Ledger',
});

expect(result).toMatchObject({
connectId: 'A58F',
name: 'Andox',
});
});

it('falls back to the device model when the BLE device has no name', () => {
const result = mapThirdPartyDeviceToSearchDevice({
device: {
vendor: 'ledger',
model: 'nanoX',
firmwareVersion: '',
deviceId: 'A58F',
connectId: 'A58F',
connectionType: 'ble',
} as never,
defaultDeviceName: 'Ledger',
});

expect(result).toMatchObject({
connectId: 'A58F',
name: 'nanoX',
});
});

it('treats explicit USB devices as USB even when connectId looks like a BLE id', () => {
const result = mapThirdPartyDeviceToSearchDevice({
device: {
vendor: 'ledger',
model: 'nanoX',
firmwareVersion: '',
deviceId: 'A58F',
connectId: 'A58F',
label: 'Andox',
connectionType: 'usb',
} as never,
defaultDeviceName: 'Ledger',
canMatchDeviceByConnectId: () => true,
});

expect(result).toMatchObject({
connectId: null,
name: 'Andox',
});
});

it('rejects explicit BLE devices without a valid four-character connectId', () => {
expect(() =>
mapThirdPartyDeviceToSearchDevice({
device: {
vendor: 'ledger',
model: 'nanoX',
firmwareVersion: '',
deviceId: '',
connectId: '',
label: 'Leo',
connectionType: 'ble',
} as never,
defaultDeviceName: 'Ledger',
}),
).toThrow('Third-party BLE connectId is required');
});
});
Loading
Loading