Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
54 changes: 33 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,18 @@ 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) {
// Third-party vendor: resolve the per-model avatar from persisted
// settings (device.settings is always populated by
// refillDeviceInfo, parseDeviceSettingsRaw is 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 +6131,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,53 @@ 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, only the human-readable name → 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 +155,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 +180,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