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
1 change: 1 addition & 0 deletions development/spellCheckerSkipWords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ sompi
Sompi
Sparkline
splitter
Stax
sr25519
starcoin
Starcoin
Expand Down
32 changes: 0 additions & 32 deletions packages/kit-bg/src/dbs/local/LocalDbBase.trezor.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getVendorProfile } from '@onekeyhq/shared/src/hardware/vendorProfile';
import { EHardwareTransportType } from '@onekeyhq/shared/types';
import { EHardwareVendor } from '@onekeyhq/shared/types/device';

Expand All @@ -7,7 +6,6 @@ import {
buildThirdPartyFeaturesInfoFromDevice,
buildTrezorDesktopBleUsbConnectId,
clearTrezorThpSettingsRaw,
getThirdPartyDeviceAvatarImage,
getThirdPartyDeviceModelName,
resolveBleConnectIdForCreate,
} from './LocalDbBase';
Expand Down Expand Up @@ -220,36 +218,6 @@ describe('getThirdPartyDeviceModelName', () => {
});
});

describe('getThirdPartyDeviceAvatarImage', () => {
it.each([
'Safe 3',
'Safe 5',
'Safe 7',
'Trezor Safe 7',
'Model One',
'Model T',
])(
'uses the Trezor vendorModelName avatar key when the %s asset is registered',
(modelName) => {
expect(
getThirdPartyDeviceAvatarImage({
profile: getVendorProfile(EHardwareVendor.trezor),
modelName,
}),
).toBe(modelName);
},
);

it('falls back to the Trezor vendor avatar for unknown model assets', () => {
expect(
getThirdPartyDeviceAvatarImage({
profile: getVendorProfile(EHardwareVendor.trezor),
modelName: 'Unknown Model',
}),
).toBe('trezor');
});
});

describe('buildTrezorDesktopBleUsbConnectId', () => {
it('uses firmware device_id as usbConnectId only for Trezor Desktop BLE', () => {
expect(
Expand Down
52 changes: 31 additions & 21 deletions packages/kit-bg/src/dbs/local/LocalDbBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ import platformEnv from '@onekeyhq/shared/src/platformEnv';
import accountUtils from '@onekeyhq/shared/src/utils/accountUtils';
import { checkIsDefined } from '@onekeyhq/shared/src/utils/assertUtils';
import {
AllWalletAvatarImages,
getDeviceAvatarImage,
getThirdPartyDeviceAvatarImage,
} from '@onekeyhq/shared/src/utils/avatarUtils';
import type { IAllWalletAvatarImageNamesWithoutDividers } from '@onekeyhq/shared/src/utils/avatarUtils';
import type { IThirdPartyWalletAvatarImageNames } from '@onekeyhq/shared/src/utils/avatarUtils';
import bufferUtils from '@onekeyhq/shared/src/utils/bufferUtils';
import perfUtils, {
EPerformanceTimerLogNames,
Expand Down Expand Up @@ -630,21 +630,20 @@ export function buildThirdPartyDeviceDisplayName({
return `${vendorName} Device`;
}

export function getThirdPartyDeviceAvatarImage({
profile,
modelName,
function getThirdPartyDeviceModelCode({
device,
features,
}: {
profile: ReturnType<typeof getVendorProfile>;
modelName?: string;
}): IAllWalletAvatarImageNamesWithoutDividers {
if (
profile.vendor === EHardwareVendor.trezor &&
modelName &&
modelName in AllWalletAvatarImages
) {
return modelName as IAllWalletAvatarImageNamesWithoutDividers;
}
return profile.avatarKey as IAllWalletAvatarImageNamesWithoutDividers;
device: IDBCreateHwWalletParams['device'];
features: IOneKeyDeviceFeatures;
}): string | undefined {
const featureRecord = features as IOneKeyDeviceFeatures & {
internal_model?: string;
};
return (
featureRecord.internal_model ||
getExtraDeviceFieldString(device, 'vendorModel')
);
}

function parseDeviceSettingsRaw(settingsRaw?: string): IDBDeviceSettings {
Expand Down Expand Up @@ -3412,10 +3411,16 @@ export abstract class LocalDbBase extends LocalDbBaseContainer {

if (shouldFixAvatar) {
if (profile.isThirdParty) {
// Third-party vendor: fix avatar to match vendor key
const expectedImg =
profile.avatarKey as IAllWalletAvatarImageNamesWithoutDividers;
if (avatarInfo?.img && avatarInfo.img !== expectedImg) {
// Resolve per-model avatar; parseDeviceSettingsRaw is a defensive fallback.
const deviceSettings =
device?.settings ?? parseDeviceSettingsRaw(device?.settingsRaw);
const expectedImg = getThirdPartyDeviceAvatarImage({
vendor: profile.vendor,
vendorModel: deviceSettings.vendorModel,
vendorModelName: deviceSettings.vendorModelName,
fallback: profile.avatarKey as IThirdPartyWalletAvatarImageNames,
});
if (avatarInfo?.img !== expectedImg) {
wallet.avatarInfo = { ...avatarInfo, img: expectedImg };
wallet.avatar = JSON.stringify(wallet.avatarInfo);
}
Expand Down Expand Up @@ -6124,7 +6129,12 @@ export abstract class LocalDbBase extends LocalDbBaseContainer {
deviceType: EDeviceType.Unknown,
firmwareType: thirdPartyDeviceUtils.getFirmwareType({ features }),
avatar: {
img: getThirdPartyDeviceAvatarImage({ profile, modelName }),
img: getThirdPartyDeviceAvatarImage({
vendor: profile.vendor,
vendorModel: getThirdPartyDeviceModelCode({ device, features }),
vendorModelName: modelName,
fallback: profile.avatarKey as IThirdPartyWalletAvatarImageNames,
}),
},
deviceName: finalDeviceName,
featuresInfo: buildThirdPartyFeaturesInfoFromDevice({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { WalletAvatar } from '@onekeyhq/kit/src/components/WalletAvatar';
import { convertDeviceError } from '@onekeyhq/shared/src/errors/utils/deviceErrorUtils';
import { ETranslations } from '@onekeyhq/shared/src/locale';
import platformEnv from '@onekeyhq/shared/src/platformEnv';
import { getThirdPartyDeviceAvatarImage } from '@onekeyhq/shared/src/utils/avatarUtils';
import deviceUtils from '@onekeyhq/shared/src/utils/deviceUtils';
import { EHardwareVendor } from '@onekeyhq/shared/types/device';

Expand Down Expand Up @@ -361,6 +362,12 @@ function TrezorBleBindingContent({
bindingId,
rejectedConnectIds,
});
const avatarImg = getThirdPartyDeviceAvatarImage({
vendor: EHardwareVendor.trezor,
vendorModel: device.vendorModel,
vendorModelName: device.vendorModelName,
fallback: 'trezor',
});
return (
<ListItem
key={device.connectId}
Expand All @@ -372,7 +379,7 @@ function TrezorBleBindingContent({
}}
userSelect="none"
>
<WalletAvatar wallet={undefined} img="trezor" />
<WalletAvatar wallet={undefined} img={avatarImg} />
<ListItem.Text
primary={device.name}
secondary={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export type ITrezorBleBindingMode = 'manual-binding' | 'auto-fallback';

export type ITrezorBleBindingScannedDevice = SearchDevice & {
raw?: { connectionType?: 'usb' | 'ble' };
vendorModel?: string;
vendorModelName?: string;
};

export function getTrezorBleBindingScanOptions(mode: ITrezorBleBindingMode): {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import {
EThirdPartyHardwareUiAction,
thirdPartyHardwareUiStateAtom,
} from '@onekeyhq/kit-bg/src/states/jotai/atoms';
import { getVendorProfile } from '@onekeyhq/shared/src/hardware/vendorProfile';
import { ThirdPartyWalletAvatarImages } from '@onekeyhq/shared/src/utils/avatarUtils';
import {
AllWalletAvatarImages,
getThirdPartyDeviceAvatarImage,
} from '@onekeyhq/shared/src/utils/avatarUtils';
import { EHardwareVendor } from '@onekeyhq/shared/types/device';

import { Layout } from './utils/Layout';
Expand All @@ -24,37 +26,52 @@ import { Layout } from './utils/Layout';
type IDeviceMock = {
label: string;
vendor: EHardwareVendor;
model: string;
vendorModel?: string;
vendorModelName?: string;
};

const DEVICE_MOCKS: IDeviceMock[] = [
// vendorModel set → exercises the exact-case code lookup.
{
label: 'Trezor Model One',
vendor: EHardwareVendor.trezor,
model: 'Model One',
vendorModel: 'T1B1',
},
{
label: 'Trezor Model T',
vendor: EHardwareVendor.trezor,
vendorModel: 'T2T1',
},
// vendorModel unavailable → exercises the normalized name-alias fallback.
{
label: 'Trezor Safe 3',
vendor: EHardwareVendor.trezor,
vendorModelName: 'Safe 3',
},
{
label: 'Trezor Safe 5',
vendor: EHardwareVendor.trezor,
vendorModelName: 'Safe 5',
},
{
label: 'Trezor Safe 7',
vendor: EHardwareVendor.trezor,
vendorModel: 'T3W1',
},
{ label: 'Trezor Model T', vendor: EHardwareVendor.trezor, model: 'Model T' },
{ label: 'Trezor Safe 3', vendor: EHardwareVendor.trezor, model: 'Safe 3' },
{ label: 'Trezor Safe 5', vendor: EHardwareVendor.trezor, model: 'Safe 5' },
{ label: 'Trezor Safe 7', vendor: EHardwareVendor.trezor, model: 'Safe 7' },
// Unknown model → must fall back to the generic Trezor avatar.
{ label: 'Trezor (unknown)', vendor: EHardwareVendor.trezor, model: 'T9X9' },
{ label: 'Ledger Nano X', vendor: EHardwareVendor.ledger, model: 'Nano X' },
{ label: 'Ledger Stax', vendor: EHardwareVendor.ledger, model: 'Stax' },
{
label: 'Trezor (unknown)',
vendor: EHardwareVendor.trezor,
vendorModel: 'T9X9',
},
{
label: 'Ledger Nano X',
vendor: EHardwareVendor.ledger,
vendorModel: 'nanoX',
},
{ label: 'Ledger Stax', vendor: EHardwareVendor.ledger, vendorModel: 'stax' },
];

// Mirrors getThirdPartyDeviceAvatarImage: a known Trezor model uses its own
// avatar key, everything else falls back to the vendor avatar.
function resolveAvatarSource(device: IDeviceMock) {
const avatars: Record<string, number> = ThirdPartyWalletAvatarImages;
const profile = getVendorProfile(device.vendor);
const key =
device.vendor === EHardwareVendor.trezor && device.model in avatars
? device.model
: profile.avatarKey;
return avatars[key] ?? avatars.trezor;
}

type IActionItem = {
label: string;
action: EThirdPartyHardwareUiAction;
Expand Down Expand Up @@ -137,6 +154,12 @@ function ActionRows({

function ThirdPartyHardwareActionsTest() {
const [device, setDevice] = useState<IDeviceMock>(DEVICE_MOCKS[0]);
const avatarKey = getThirdPartyDeviceAvatarImage({
vendor: device.vendor,
vendorModel: device.vendorModel,
vendorModelName: device.vendorModelName,
fallback: device.vendor === EHardwareVendor.ledger ? 'ledger' : 'trezor',
});

return (
<YStack gap="$5">
Expand All @@ -156,15 +179,15 @@ function ThirdPartyHardwareActionsTest() {
</XStack>
<XStack alignItems="center" gap="$3" pt="$2">
<Image
source={resolveAvatarSource(device)}
source={AllWalletAvatarImages[avatarKey]}
w="$10"
h="$10"
borderRadius="$2"
/>
<YStack>
<SizableText size="$bodyMdMedium">{device.model}</SizableText>
<SizableText size="$bodyMdMedium">{device.label}</SizableText>
<SizableText size="$bodySm" color="$textSubdued">
{`vendor=${device.vendor}`}
{`vendor=${device.vendor} avatarKey=${avatarKey}`}
</SizableText>
</YStack>
</XStack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import { convertDeviceError } from '@onekeyhq/shared/src/errors/utils/deviceErro
import { ETranslations } from '@onekeyhq/shared/src/locale';
import platformEnv from '@onekeyhq/shared/src/platformEnv';
import { EOnboardingPagesV2 } from '@onekeyhq/shared/src/routes/onboardingv2';
import { ThirdPartyWalletAvatarImages } from '@onekeyhq/shared/src/utils/avatarUtils';
import {
ThirdPartyWalletAvatarImages,
getThirdPartyDeviceAvatarImage,
} from '@onekeyhq/shared/src/utils/avatarUtils';
import deviceUtils from '@onekeyhq/shared/src/utils/deviceUtils';
import { EConnectDeviceChannel } from '@onekeyhq/shared/types/connectDevice';
import type { IConnectYourDeviceItem } from '@onekeyhq/shared/types/device';
Expand Down Expand Up @@ -223,11 +226,23 @@ export default function LedgerConnectionFlow() {
// --- Device list data ---
const devicesData = useMemo<IConnectYourDeviceItem[]>(
() =>
searchedDevices.map((item: SearchDevice) => ({
title: item.name,
src: ThirdPartyWalletAvatarImages.ledger,
device: item,
})),
searchedDevices.map((item: SearchDevice) => {
const vendorFields = item as SearchDevice & {
vendorModel?: string;
vendorModelName?: string;
};
return {
title: item.name,
src: ThirdPartyWalletAvatarImages.ledger,
device: item,
avatarImg: getThirdPartyDeviceAvatarImage({
vendor: EHardwareVendor.ledger,
vendorModel: vendorFields.vendorModel,
vendorModelName: vendorFields.vendorModelName,
fallback: 'ledger',
}),
};
}),
[searchedDevices],
);

Expand Down Expand Up @@ -397,7 +412,10 @@ export default function LedgerConnectionFlow() {
}}
userSelect="none"
>
<WalletAvatar wallet={undefined} img="ledger" />
<WalletAvatar
wallet={undefined}
img={data.avatarImg ?? 'ledger'}
/>
<ListItem.Text primary={data.device?.name} flex={1} />
</ListItem>
))}
Expand Down
Loading
Loading