Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: support non-evm tokens in asset-picker #30313

Merged
merged 44 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cc2e09a
chore: support non-evm tokens in asset-picker
micaelae Feb 12, 2025
5dba18c
fix: use EVM account in getSelectedAccountNativeTokenCachedBalanceByC…
micaelae Feb 18, 2025
a187401
fix: don't parse caip chain id to int in TokenListItem
micaelae Feb 18, 2025
d2ba624
chore: include non-evm assets in useMultichainbalances results
micaelae Feb 18, 2025
b12c228
chore: use props instead of getTokenList to populate Asset metadata
micaelae Feb 18, 2025
4a83119
chore: get last selected multichainAccount and assets rates placeholder
micaelae Feb 18, 2025
671533d
chore: update asset-picker types
micaelae Feb 18, 2025
3ea64f5
chore: use evm account and token list in asset-picker-modal
micaelae Feb 18, 2025
65f1cf0
chore: include solana tokens in asset picker list
micaelae Feb 18, 2025
38b73d4
fix: only include nativeAsset when isEvm=true
micaelae Feb 18, 2025
821c146
refactor: balance hook names
micaelae Feb 19, 2025
177d4d9
fix: type errors
micaelae Feb 19, 2025
87b91a6
refactor: support networks that use CAIP chainIds
micaelae Feb 19, 2025
e83df81
chore: rm getAssetsRates placeholder
micaelae Feb 19, 2025
8218c89
fix: incorrect import
micaelae Feb 19, 2025
bfa4489
chore: add TODO comments
micaelae Feb 20, 2025
b31c3de
chore: reuse getTokenBalancesEvm selector in asset-picker-modal
micaelae Feb 20, 2025
6280c30
refactor: rm useTokenTracker from asset-picker list
micaelae Feb 20, 2025
a2537d7
fix: hide assets with no metadata
micaelae Feb 20, 2025
e9934e5
fix: preserve non-evm assetIds
micaelae Feb 20, 2025
7431f30
chore: placeholder network config for solana
micaelae Feb 20, 2025
47b8cc6
fix: lint and type errors
micaelae Feb 21, 2025
33d7b6a
fix: more lint and type errors
micaelae Feb 21, 2025
40b67e7
Revert "chore: placeholder network config for solana"
micaelae Feb 21, 2025
18edbab
Merge branch 'main' into mms1867-asset-picker-caip-multichain
micaelae Feb 21, 2025
d409666
fix: native token address
micaelae Feb 21, 2025
803525c
Merge branch 'main' into mms1867-asset-picker-caip-multichain
micaelae Feb 21, 2025
34bef50
fix: unit tests
micaelae Feb 21, 2025
e8b29e7
fix: unit tests
micaelae Feb 22, 2025
bf2e551
Merge branch 'main' into mms1867-asset-picker-caip-multichain
micaelae Feb 24, 2025
b2142b7
fix: types
micaelae Feb 24, 2025
6183900
fix: lint issues
micaelae Feb 25, 2025
b1b568a
fix: crash on initial load with no multichain account
micaelae Feb 21, 2025
354eff1
fix: Send flow balance
micaelae Feb 25, 2025
a56efcf
fix: fallback image for ERC20 tokens
micaelae Feb 25, 2025
d89963f
fix: bridge dest token list
micaelae Feb 25, 2025
0374f1e
fix: placeholder solana network
micaelae Feb 25, 2025
338f530
fix: include type in multichain balances
micaelae Feb 25, 2025
1d580c8
fix: e2e swap+send test
micaelae Feb 25, 2025
e8bcc49
fix: lint issues
micaelae Feb 25, 2025
4b1aeac
fix: build error
micaelae Feb 25, 2025
e1c1d5c
fix: asset-picker-modal story
micaelae Feb 25, 2025
da1c4e5
chore: rm comments
micaelae Feb 25, 2025
da3158b
chore: add comment
micaelae Feb 25, 2025
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
5 changes: 4 additions & 1 deletion test/e2e/tests/swap-send/swap-send-erc20.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ describe('Swap-Send ERC20', function () {
4000,
);

await swapSendPage.verifyMaxButtonClick(['TST', 'TST'], ['10', '10']);
await swapSendPage.verifyMaxButtonClick(
['TST', 'TST'],
['10.0000', '10.0000'],
);

await swapSendPage.fillAmountInput('10');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import React from 'react';
import { render } from '@testing-library/react';
import { useSelector } from 'react-redux';
import {
getNetworkConfigurationIdByChainId,
getTokenList,
} from '../../../../selectors';
import { getTokenList } from '../../../../selectors';
import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount';
import { getIntlLocale } from '../../../../ducks/locale/locale';
import { TokenListItem } from '../../token-list-item';
import { AssetType } from '../../../../../shared/constants/transaction';
import { getMultichainNetworkConfigurationsByChainId } from '../../../../selectors/multichain';
import Asset from './Asset';

jest.mock('react-redux', () => ({
Expand Down Expand Up @@ -54,7 +52,7 @@ describe('Asset', () => {
return mockState.getTokenList;
} else if (selector === getIntlLocale) {
return mockState.getIntlLocale;
} else if (selector === getNetworkConfigurationIdByChainId) {
} else if (selector === getMultichainNetworkConfigurationsByChainId) {
return {
'0x1': { networkName: 'Ethereum', iconUrl: 'network-icon-url' },
};
Expand Down Expand Up @@ -83,13 +81,15 @@ describe('Asset', () => {
expect(getByText('TokenListItem')).toBeInTheDocument();
expect(TokenListItem).toHaveBeenCalledWith(
expect.objectContaining({
tokenSymbol: 'WETH',
tokenImage: 'token-icon-url',
chainId: '0x1',
isPrimaryTokenSymbolHidden: true,
primary: '$10.10',
secondary: '10 WETH',
title: 'Token',
title: 'WETH',
tokenChainImage: './images/eth_logo.svg',
tokenImage: 'token-icon-url',
tokenSymbol: 'WETH',
tooltipText: 'tooltip',
isPrimaryTokenSymbolHidden: true,
}),
{},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ import React from 'react';
import { useSelector } from 'react-redux';
import { BigNumber } from 'bignumber.js';
import { getCurrentCurrency } from '../../../../ducks/metamask/metamask';
import {
getNetworkConfigurationIdByChainId,
getTokenList,
} from '../../../../selectors';
import { useTokenFiatAmount } from '../../../../hooks/useTokenFiatAmount';
import { TokenListItem } from '../../token-list-item';
import { isEqualCaseInsensitive } from '../../../../../shared/modules/string-utils';
import { formatAmount } from '../../../../pages/confirmations/components/simulation-details/formatAmount';
import { getIntlLocale } from '../../../../ducks/locale/locale';
import { formatCurrency } from '../../../../helpers/utils/confirm-tx.util';
import { getImageForChainId } from '../../../../selectors/multichain';
import {
getMultichainNetworkConfigurationsByChainId,
getImageForChainId,
} from '../../../../selectors/multichain';
import { selectERC20TokensByChain } from '../../../../selectors/selectors';
import { AssetWithDisplayData, ERC20Asset, NativeAsset } from './types';

type AssetProps = AssetWithDisplayData<NativeAsset | ERC20Asset> & {
Expand All @@ -36,21 +35,13 @@ export default function Asset({
const locale = useSelector(getIntlLocale);

const currency = useSelector(getCurrentCurrency);
const tokenList = useSelector(getTokenList);
const allNetworks = useSelector(getNetworkConfigurationIdByChainId);
const allNetworks = useSelector(getMultichainNetworkConfigurationsByChainId);
const isTokenChainIdInWallet = Boolean(
chainId ? allNetworks[chainId as keyof typeof allNetworks] : true,
);
const tokenData = address
? Object.values(tokenList).find(
(token) =>
isEqualCaseInsensitive(token.symbol, symbol) &&
isEqualCaseInsensitive(token.address, address),
)
: undefined;

const title = tokenData?.name || symbol;
const tokenImage = tokenData?.iconUrl || image;
const cachedTokens = useSelector(selectERC20TokensByChain);

const formattedFiat = useTokenFiatAmount(
address ?? undefined,
decimalTokenAmount,
Expand All @@ -73,10 +64,15 @@ export default function Asset({
key={`${chainId}-${symbol}-${address}`}
chainId={chainId}
tokenSymbol={symbol}
tokenImage={tokenImage}
tokenImage={
image ??
cachedTokens?.[chainId]?.data?.[
((address as string) ?? '').toLowerCase()
]?.iconUrl
}
secondary={isTokenChainIdInWallet ? formattedAmount : undefined}
primary={isTokenChainIdInWallet ? primaryAmountToUse : undefined}
title={title}
title={symbol}
tooltipText={tooltipText}
tokenChainImage={getImageForChainId(chainId)}
isPrimaryTokenSymbolHidden
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay';
import { AssetType } from '../../../../../shared/constants/transaction';
import { CHAIN_ID_TOKEN_IMAGE_MAP } from '../../../../../shared/constants/network';
import { getCurrentChainId } from '../../../../../shared/modules/selectors/networks';
import { useMultichainSelector } from '../../../../hooks/useMultichainSelector';
import {
getMultichainCurrentChainId,
getMultichainCurrentNetwork,
} from '../../../../selectors/multichain';
import AssetList from './AssetList';
import { AssetWithDisplayData, ERC20Asset, NativeAsset } from './types';

jest.mock('../../../../hooks/useMultichainSelector', () => ({
useMultichainSelector: jest.fn(),
}));

jest.mock('react-redux', () => ({
useSelector: jest.fn(),
}));
Expand Down Expand Up @@ -122,6 +131,14 @@ describe('AssetList', () => {
});

it('should render the token list', () => {
(useMultichainSelector as jest.Mock).mockImplementation((selector) => {
if (selector === getMultichainCurrentNetwork) {
return { chainId: '0x1' };
} else if (selector === getMultichainCurrentChainId) {
return '0x1';
}
return undefined;
});
(useSelector as jest.Mock).mockImplementation((selector) => {
if (selector === getCurrentChainId) {
return '0x1';
Expand Down Expand Up @@ -149,6 +166,12 @@ describe('AssetList', () => {
});

it('should call handleAssetChange when a token is clicked', () => {
(useMultichainSelector as jest.Mock).mockImplementation((selector) => {
if (selector === getMultichainCurrentNetwork) {
return { chainId: '0x1' };
}
return undefined;
});
render(
<AssetList
handleAssetChange={handleAssetChangeMock}
Expand All @@ -167,6 +190,12 @@ describe('AssetList', () => {
});

it('should disable the token if it is in the blocked tokens list', () => {
(useMultichainSelector as jest.Mock).mockImplementation((selector) => {
if (selector === getMultichainCurrentNetwork) {
return { chainId: '0x1' };
}
return undefined;
});
(useSelector as jest.Mock)
.mockImplementationOnce(() => ['0xToken1'])
.mockImplementation((selector) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import React from 'react';
import { useSelector } from 'react-redux';
import classnames from 'classnames';
import {
AddNetworkFields,
NetworkConfiguration,
} from '@metamask/network-controller';
import { getCurrentChainId } from '../../../../../shared/modules/selectors/networks';
import {
getCurrentNetwork,
getSelectedAccountCachedBalance,
} from '../../../../selectors';
import {
getCurrentCurrency,
getNativeCurrency,
} from '../../../../ducks/metamask/metamask';
import type { CaipChainId } from '@metamask/utils';
import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay';
import { AssetType } from '../../../../../shared/constants/transaction';
import { Box } from '../../../component-library';
Expand All @@ -27,7 +18,15 @@ import {
import { TokenListItem } from '../..';
import LoadingScreen from '../../../ui/loading-screen';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { getImageForChainId } from '../../../../selectors/multichain';
import {
getMultichainCurrentCurrency,
getMultichainCurrentChainId,
getImageForChainId,
getMultichainCurrentNetwork,
getMultichainNativeCurrency,
getMultichainSelectedAccountCachedBalance,
} from '../../../../selectors/multichain';
import { useMultichainSelector } from '../../../../hooks/useMultichainSelector';
import AssetComponent from './Asset';
import { AssetWithDisplayData, ERC20Asset, NFT, NativeAsset } from './types';

Expand All @@ -46,7 +45,10 @@ type AssetListProps = {
isTokenDisabled?: (
token: AssetWithDisplayData<ERC20Asset> | AssetWithDisplayData<NativeAsset>,
) => boolean;
network?: NetworkConfiguration | AddNetworkFields;
network?:
| NetworkConfiguration
| AddNetworkFields
| (Omit<NetworkConfiguration, 'chainId'> & { chainId: CaipChainId });
isTokenListLoading?: boolean;
assetItemProps?: Pick<
React.ComponentProps<typeof TokenListItem>,
Expand All @@ -65,18 +67,20 @@ export default function AssetList({
}: AssetListProps) {
const t = useI18nContext();

const currentNetwork = useSelector(getCurrentNetwork);
const currentNetwork = useMultichainSelector(getMultichainCurrentNetwork);
// If a network is provided, display tokens in that network
// Otherwise, assume tokens in the current network are displayed
const networkToUse = network ?? currentNetwork;
// This indicates whether tokens in the wallet's active network are displayed
const isSelectedNetworkActive =
networkToUse.chainId === currentNetwork.chainId;

const chainId = useSelector(getCurrentChainId);
const nativeCurrency = useSelector(getNativeCurrency);
const balanceValue = useSelector(getSelectedAccountCachedBalance);
const currentCurrency = useSelector(getCurrentCurrency);
const chainId = useMultichainSelector(getMultichainCurrentChainId);
const nativeCurrency = useMultichainSelector(getMultichainNativeCurrency);
const balanceValue = useMultichainSelector(
getMultichainSelectedAccountCachedBalance,
);
const currentCurrency = useMultichainSelector(getMultichainCurrentCurrency);

const [primaryCurrencyValue] = useCurrencyDisplay(balanceValue, {
currency: currentCurrency,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
NetworkConfiguration,
} from '@metamask/network-controller';
import { IconName } from '@metamask/snaps-sdk/jsx';
import type { CaipChainId } from '@metamask/utils';
import {
Display,
FlexDirection,
Expand Down Expand Up @@ -37,6 +38,12 @@ import { useMultichainBalances } from '../../../../hooks/useMultichainBalances';
import { NETWORK_TO_SHORT_NETWORK_NAME_MAP } from '../../../../../shared/constants/bridge';
import { getImageForChainId } from '../../../../selectors/multichain';

// TODO use MultichainNetworkConfiguration type
type NetworkOption =
| NetworkConfiguration
| AddNetworkFields
| (Omit<NetworkConfiguration, 'chainId'> & { chainId: CaipChainId });

/**
* AssetPickerModalNetwork component displays a modal for selecting a network in the asset picker.
*
Expand Down Expand Up @@ -68,12 +75,10 @@ export const AssetPickerModalNetwork = ({
selectedChainIds,
}: {
isOpen: boolean;
network?: NetworkConfiguration | AddNetworkFields;
networks?: (NetworkConfiguration | AddNetworkFields)[];
onNetworkChange: (network: NetworkConfiguration | AddNetworkFields) => void;
shouldDisableNetwork?: (
network: NetworkConfiguration | AddNetworkFields,
) => boolean;
network?: NetworkOption;
networks?: NetworkOption[];
onNetworkChange: (network: NetworkOption) => void;
shouldDisableNetwork?: (network: NetworkOption) => boolean;
onClose: () => void;
onBack: () => void;
header?: JSX.Element | string | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { AssetType } from '../../../../../shared/constants/transaction';
import {
getNativeCurrencyImage,
getSelectedAccountCachedBalance,
getSelectedInternalAccount,
getSelectedEvmInternalAccount,
getShouldHideZeroBalanceTokens,
getTokenExchangeRates,
getTokenList,
Expand All @@ -23,16 +23,16 @@ import {
getConversionRate,
getNativeCurrency,
getTokens,
getCurrentCurrency,
} from '../../../../ducks/metamask/metamask';
import { getTopAssets } from '../../../../ducks/swaps/swaps';
import { getRenderableTokenData } from '../../../../hooks/useTokensToSearch';
import * as actions from '../../../../store/actions';
import { getSwapsBlockedTokens } from '../../../../ducks/send';
import {
getCurrentChainId,
getNetworkConfigurationsByChainId,
} from '../../../../../shared/modules/selectors/networks';
getMultichainNetworkConfigurationsByChainId,
getMultichainCurrentChainId,
getMultichainCurrentCurrency,
} from '../../../../selectors/multichain';
import { AssetPickerModal } from './asset-picker-modal';
import AssetList from './AssetList';
import { ERC20Asset } from './types';
Expand Down Expand Up @@ -103,13 +103,13 @@ describe('AssetPickerModal', () => {

beforeEach(() => {
useSelectorMock.mockImplementation((selector) => {
if (selector === getNetworkConfigurationsByChainId) {
if (selector === getMultichainNetworkConfigurationsByChainId) {
return { '0x1': { chainId: '0x1' } };
}
if (selector === getCurrentChainId) {
if (selector === getMultichainCurrentChainId) {
return '0x1';
}
if (selector === getCurrentCurrency) {
if (selector === getMultichainCurrentCurrency) {
return 'USD';
}
if (selector === getNativeCurrencyImage) {
Expand All @@ -118,7 +118,7 @@ describe('AssetPickerModal', () => {
if (selector === getSelectedAccountCachedBalance) {
return '1000';
}
if (selector === getSelectedInternalAccount) {
if (selector === getSelectedEvmInternalAccount) {
return { address: '0xAddress' };
}
if (selector === getShouldHideZeroBalanceTokens) {
Expand Down Expand Up @@ -169,7 +169,7 @@ describe('AssetPickerModal', () => {
tokensWithBalances: [],
});
(getRenderableTokenData as jest.Mock).mockReturnValue({});
mockUseMultichainBalances.mockReturnValue({});
mockUseMultichainBalances.mockReturnValue({ assetsWithBalance: [] });
});

afterEach(() => {
Expand Down
Loading
Loading