Skip to content
Draft
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 packages/assets-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Hide native tokens on Tempo networks (testnet and mainnet) in `getAssets` method ([#7882](https://github.com/MetaMask/core/pull/7882))
- Bump `@metamask/assets-controllers` from `^100.0.1` to `^100.0.2` ([#8004](https://github.com/MetaMask/core/pull/8004))

## [2.0.2]
Expand Down
175 changes: 175 additions & 0 deletions packages/assets-controller/src/AssetsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,181 @@ describe('AssetsController', () => {
expect(assets).toBeDefined();
});
});

it('hides native tokens on Tempo testnet (eip155:42431)', async () => {
await withController(
{
state: {
assetsInfo: {
'eip155:42431/slip44:60': {
type: 'native',
symbol: 'ETH',
name: 'Ethereum',
decimals: 18,
},
'eip155:42431/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': {
type: 'erc20',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
},
},
assetsBalance: {
[MOCK_ACCOUNT_ID]: {
'eip155:42431/slip44:60': {
amount: '1',
unit: 'ETH',
},
'eip155:42431/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48':
{
amount: '100',
unit: 'USDC',
},
},
},
assetsPrice: {},
customAssets: {},
assetPreferences: {},
},
},
async ({ controller }) => {
const accounts = [createMockInternalAccount()];
const assets = await controller.getAssets(accounts, {
chainIds: ['eip155:42431'],
});

// Native token should be hidden
expect(
assets[MOCK_ACCOUNT_ID]['eip155:42431/slip44:60'],
).toBeUndefined();

// ERC20 token should still be visible
expect(
assets[MOCK_ACCOUNT_ID][
'eip155:42431/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
],
).toBeDefined();
},
);
});

it('hides native tokens on Tempo mainnet (eip155:4217)', async () => {
await withController(
{
state: {
assetsInfo: {
'eip155:4217/slip44:60': {
type: 'native',
symbol: 'ETH',
name: 'Ethereum',
decimals: 18,
},
},
assetsBalance: {
[MOCK_ACCOUNT_ID]: {
'eip155:4217/slip44:60': {
amount: '1',
unit: 'ETH',
},
},
},
assetsPrice: {},
customAssets: {},
assetPreferences: {},
},
},
async ({ controller }) => {
const accounts = [createMockInternalAccount()];
const assets = await controller.getAssets(accounts, {
chainIds: ['eip155:4217'],
});

// Native token should be hidden
expect(
assets[MOCK_ACCOUNT_ID]['eip155:4217/slip44:60'],
).toBeUndefined();
},
);
});

it('does not hide native tokens on non-Tempo networks', async () => {
await withController(
{
state: {
assetsInfo: {
'eip155:1/slip44:60': {
type: 'native',
symbol: 'ETH',
name: 'Ethereum',
decimals: 18,
},
},
assetsBalance: {
[MOCK_ACCOUNT_ID]: {
'eip155:1/slip44:60': {
amount: '1',
unit: 'ETH',
},
},
},
assetsPrice: {},
customAssets: {},
assetPreferences: {},
},
},
async ({ controller }) => {
const accounts = [createMockInternalAccount()];
const assets = await controller.getAssets(accounts, {
chainIds: ['eip155:1'],
});

// Native token should still be visible on Ethereum
expect(assets[MOCK_ACCOUNT_ID]['eip155:1/slip44:60']).toBeDefined();
expect(
assets[MOCK_ACCOUNT_ID]['eip155:1/slip44:60'].metadata.symbol,
).toBe('ETH');
},
);
});

it('hides native tokens identified by metadata type', async () => {
await withController(
{
state: {
assetsInfo: {
'eip155:42431/some:other': {
type: 'native',
symbol: 'ETH',
name: 'Ethereum',
decimals: 18,
},
},
assetsBalance: {
[MOCK_ACCOUNT_ID]: {
'eip155:42431/some:other': {
amount: '1',
unit: 'ETH',
},
},
},
assetsPrice: {},
customAssets: {},
assetPreferences: {},
},
},
async ({ controller }) => {
const accounts = [createMockInternalAccount()];
const assets = await controller.getAssets(accounts, {
chainIds: ['eip155:42431'],
});

// Native token should be hidden even if assetId doesn't have slip44
expect(
assets[MOCK_ACCOUNT_ID]['eip155:42431/some:other'],
).toBeUndefined();
},
);
});
});

describe('getAssetsBalance', () => {
Expand Down
40 changes: 40 additions & 0 deletions packages/assets-controller/src/AssetsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,11 @@ export class AssetsController extends BaseController<

const assetChainId = extractChainId(typedAssetId);

// Skip native tokens on Tempo networks
if (this.#shouldHideNativeToken(assetChainId, typedAssetId, metadata)) {
continue;
}

if (!chainIdSet.has(assetChainId)) {
continue;
}
Expand Down Expand Up @@ -1438,6 +1443,41 @@ export class AssetsController extends BaseController<
return result;
}

/**
* Determines if a native token should be hidden on specific networks.
*
* @param chainId - The CAIP-2 chain ID (e.g., "eip155:42431").
* @param assetId - The CAIP-19 asset ID (e.g., "eip155:42431/slip44:60").
* @param metadata - The asset metadata.
* @returns True if the token should be hidden, false otherwise.
*/
#shouldHideNativeToken(
chainId: ChainId,
assetId: Caip19AssetId,
metadata: AssetMetadata,
): boolean {
// Chain IDs where native tokens should be skipped (Tempo networks)
// These networks return arbitrary large numbers for native token balances via eth_getBalance
const CHAIN_IDS_TO_SKIP_NATIVE_TOKEN = [
'eip155:42431', // Tempo Testnet
'eip155:4217', // Tempo Mainnet
] as const;

// Check if it's a chain that should skip native tokens
if (
!CHAIN_IDS_TO_SKIP_NATIVE_TOKEN.includes(
chainId as (typeof CHAIN_IDS_TO_SKIP_NATIVE_TOKEN)[number],
)
) {
return false;
}

// Check if it's a native token (either by metadata type or assetId format)
const isNative = metadata.type === 'native' || assetId.includes('/slip44:');

return isNative;
}

/**
* Maps a token standard to its corresponding asset type.
*
Expand Down
1 change: 1 addition & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Hide native tokens on Tempo networks (testnet and mainnet) in asset selectors ([#7882](https://github.com/MetaMask/core/pull/7882))
- Blockaid token filtering in `MultichainAssetsController` now only removes tokens flagged as `Malicious` ([#8003](https://github.com/MetaMask/core/pull/8003))
- `Spam`, `Warning`, and `Benign` tokens are no longer filtered out

Expand Down
15 changes: 14 additions & 1 deletion packages/assets-controllers/src/AccountTrackerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import type {
AssetsContractController,
StakedBalance,
} from './AssetsContractController';
import { shouldIncludeNativeToken } from './constants';
import { AccountsApiBalanceFetcher } from './multi-chain-accounts-service/api-balance-fetcher';
import type {
BalanceFetcher,
Expand Down Expand Up @@ -855,7 +856,19 @@ export class AccountTrackerController extends StaticIntervalPollingController<Ac
return {};
}

const { ethQuery } = this.#getCorrectNetworkClient(networkClientId);
const { ethQuery, chainId } =
this.#getCorrectNetworkClient(networkClientId);

// Skip native token fetching for chains that return arbitrary large numbers
if (!shouldIncludeNativeToken(chainId)) {
// Return empty balances for chains that skip native token fetching
return addresses.reduce<
Record<string, { balance: string; stakedBalance?: StakedBalance }>
>((acc, address) => {
acc[address] = { balance: '0x0' };
return acc;
}, {});
}

// TODO: This should use multicall when enabled by the user.
return await Promise.all(
Expand Down
49 changes: 49 additions & 0 deletions packages/assets-controllers/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,52 @@ export const SUPPORTED_NETWORKS_ACCOUNTS_API_V4 = [
'0x8f', // 143
'0x3e7', // 999 HyperEVM
];

/**
* Chain IDs where native tokens should be skipped.
* These networks return arbitrary large numbers for native token balances via eth_getBalance.
* Currently includes: Tempo Testnet (eip155:42431) and Tempo Mainnet (eip155:4217).
*/
const CHAIN_IDS_TO_SKIP_NATIVE_TOKEN = [
'eip155:42431', // Tempo Testnet
'eip155:4217', // Tempo Mainnet
] as const;

/**
* Determines if native token fetching should be included for the given chain.
* Returns false for chains that return arbitrary large numbers (e.g., Tempo networks).
*
* @param chainId - Chain ID in hex format (e.g., "0xa5bf") or CAIP-2 format (e.g., "eip155:42431").
* @returns True if native token should be included, false if it should be skipped.
*/
export function shouldIncludeNativeToken(chainId: string): boolean {
// Convert hex format to CAIP-2 for comparison
if (chainId.startsWith('0x')) {
try {
const decimal = parseInt(chainId, 16);
const caipChainId = `eip155:${decimal}`;
if (
CHAIN_IDS_TO_SKIP_NATIVE_TOKEN.includes(
caipChainId as (typeof CHAIN_IDS_TO_SKIP_NATIVE_TOKEN)[number],
)
) {
return false;
}
} catch {
// If conversion fails, assume it should be included
return true;
}
return true;
}

// Check CAIP-2 format directly
if (
CHAIN_IDS_TO_SKIP_NATIVE_TOKEN.includes(
chainId as (typeof CHAIN_IDS_TO_SKIP_NATIVE_TOKEN)[number],
)
) {
return false;
}

return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Hex } from '@metamask/utils';
import BN from 'bn.js';

import { STAKING_CONTRACT_ADDRESS_BY_CHAINID } from '../AssetsContractController';
import { shouldIncludeNativeToken } from '../constants';
import { getTokenBalancesForMultipleAddresses } from '../multicall';
import type { TokensControllerState } from '../TokensController';

Expand Down Expand Up @@ -101,13 +102,16 @@ export class RpcBalanceFetcher implements BalanceFetcher {
const provider = this.#getProvider(chainId);
await this.#ensureFreshBlockData(chainId);

// Skip native token fetching for chains that return arbitrary large numbers
const includeNative = shouldIncludeNativeToken(chainId);

const balanceResult = await safelyExecuteWithTimeout(
async () => {
return await getTokenBalancesForMultipleAddresses(
accountTokenGroups,
chainId,
provider,
true, // include native
includeNative, // Skip native for Tempo chains
true, // include staked
);
},
Expand Down
Loading
Loading