Skip to content

Commit 4f884c2

Browse files
authored
feat: asset-controller tempo adjustments before migration (MetaMask#8638)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This PR prevents native balance fetches on Tempo chains for the native asset for: - Allowing transition from multi-assets-controller to unified - long smoke-tested but will need proper QA with fresh+used states. - Future-proof the case if `getBalance` started to return an error from Tempo's RPC - currently returns `4242424242424242424242424242424242424242424242424242424242424242424242424242` but Tempo might change it to an error message in the future. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the RPC balance fetch pipeline to omit native-asset fetching (including fallback paths) for chains flagged as having no native token, which could affect displayed balances/metadata on those networks if misclassified. > > **Overview** > Prevents `RpcDataSource` from querying/returning native balances on Tempo chains by gating native asset inclusion (and the error-path default native `0` entry) behind a new `shouldSkipNativeForCaipChainId` helper. > > Updates `AssetsController` native-token hiding logic to use the same helper, adds targeted RPC data source tests to ensure native entries and `getBalance` fallbacks are skipped when configured, and adjusts token price support mappings so Tempo chains are treated as having *no native asset*. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit af970ec. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 3be3a08 commit 4f884c2

7 files changed

Lines changed: 87 additions & 16 deletions

File tree

packages/assets-controller/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Changed
1111

1212
- Bump `@metamask/network-controller` from `^31.0.0` to `^31.1.0` ([#8765](https://github.com/MetaMask/core/pull/8765))
13+
- Update `RpcDataSource` to prevent native `getEthBalance` fetching for Tempo chains ([#8638](https://github.com/MetaMask/core/pull/8638))
1314

1415
## [7.0.1]
1516

packages/assets-controller/src/AssetsController.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type {
1212
} from '@metamask/base-controller';
1313
import type { ClientControllerStateChangeEvent } from '@metamask/client-controller';
1414
import { clientControllerSelectors } from '@metamask/client-controller';
15-
import { CHAIN_IDS_WITH_NO_NATIVE_TOKEN } from '@metamask/controller-utils';
1615
import type { TraceCallback } from '@metamask/controller-utils';
1716
import type {
1817
ApiPlatformClient,
@@ -74,6 +73,7 @@ import type {
7473
import type { AccountsApiDataSourceConfig } from './data-sources/AccountsApiDataSource';
7574
import { AccountsApiDataSource } from './data-sources/AccountsApiDataSource';
7675
import { BackendWebsocketDataSource } from './data-sources/BackendWebsocketDataSource';
76+
import { shouldSkipNativeForCaipChainId } from './data-sources/evm-rpc-services/utils/assets';
7777
import type { PriceDataSourceConfig } from './data-sources/PriceDataSource';
7878
import { PriceDataSource } from './data-sources/PriceDataSource';
7979
import type { RpcDataSourceConfig } from './data-sources/RpcDataSource';
@@ -2393,11 +2393,7 @@ export class AssetsController extends BaseController<
23932393
*/
23942394
#shouldHideNativeToken(chainId: ChainId, metadata: AssetMetadata): boolean {
23952395
// Check if it's a chain that should skip native tokens
2396-
if (
2397-
!CHAIN_IDS_WITH_NO_NATIVE_TOKEN.includes(
2398-
chainId as (typeof CHAIN_IDS_WITH_NO_NATIVE_TOKEN)[number],
2399-
)
2400-
) {
2396+
if (!shouldSkipNativeForCaipChainId(chainId)) {
24012397
return false;
24022398
}
24032399

packages/assets-controller/src/data-sources/RpcDataSource.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
BalanceFetchResult,
2020
TokenDetectionResult,
2121
} from './evm-rpc-services';
22+
import { shouldSkipNativeForCaipChainId } from './evm-rpc-services/utils/assets';
2223
import type { RpcDataSourceOptions } from './RpcDataSource';
2324
import {
2425
RpcDataSource,
@@ -247,6 +248,10 @@ jest.mock('@ethersproject/providers', () => ({
247248
})),
248249
}));
249250

251+
jest.mock('./evm-rpc-services/utils/assets', () => ({
252+
shouldSkipNativeForCaipChainId: jest.fn().mockReturnValue(false),
253+
}));
254+
250255
describe('caipChainIdToHex', () => {
251256
it('returns hex unchanged when given hex string', () => {
252257
expect(caipChainIdToHex('0x1')).toBe('0x1');
@@ -469,6 +474,18 @@ describe('RpcDataSource', () => {
469474
});
470475
});
471476

477+
it('fetches balances for accounts except native for native skip chain', async () => {
478+
jest.mocked(shouldSkipNativeForCaipChainId).mockReturnValue(true);
479+
await withController(async ({ controller }) => {
480+
const response = await controller.fetch(createDataRequest());
481+
expect(response).toBeDefined();
482+
expect(response.assetsBalance).toBeDefined();
483+
expect(response.assetsBalance?.[MOCK_ACCOUNT_ID]).not.toHaveProperty(
484+
'eip155:1/slip44:60',
485+
);
486+
});
487+
});
488+
472489
it('converts fetched balances to human-readable and merges metadata', async () => {
473490
const nativeAssetId = 'eip155:1/slip44:60' as Caip19AssetId;
474491
await withController(async ({ controller }) => {
@@ -506,6 +523,30 @@ describe('RpcDataSource', () => {
506523
});
507524
});
508525

526+
it('skips native asset call even in the getBalance fallback when Multicall aggregate3 fails', async () => {
527+
const { Web3Provider } = jest.requireMock('@ethersproject/providers');
528+
jest.mocked(shouldSkipNativeForCaipChainId).mockReturnValue(true);
529+
530+
const mockCall = jest
531+
.fn()
532+
.mockRejectedValueOnce(new Error('aggregate3 unavailable'))
533+
.mockResolvedValue('0x0');
534+
const mockGetBalance = jest
535+
.fn()
536+
.mockResolvedValue({ toString: () => '1000000000000000000' });
537+
(Web3Provider as jest.Mock).mockImplementationOnce(() => ({
538+
call: mockCall,
539+
getBalance: mockGetBalance,
540+
}));
541+
542+
await withController(async ({ controller }) => {
543+
const response = await controller.fetch(createDataRequest());
544+
expect(response.assetsBalance).toBeDefined();
545+
expect(response.assetsBalance?.[MOCK_ACCOUNT_ID]).toStrictEqual({});
546+
expect(mockGetBalance).not.toHaveBeenCalled();
547+
});
548+
});
549+
509550
it('uses getBalance when Multicall aggregate3 fails (#getMulticallProvider getBalance)', async () => {
510551
const { Web3Provider } = jest.requireMock('@ethersproject/providers');
511552
const mockCall = jest
@@ -576,6 +617,25 @@ describe('RpcDataSource', () => {
576617
});
577618
});
578619

620+
it('initializes assetsBalance[accountId] with no native in catch when first fetch for account throws on native skip chain', async () => {
621+
await withController(async ({ controller }) => {
622+
jest
623+
.spyOn(BalanceFetcher.prototype, 'fetchBalancesForAssets')
624+
.mockRejectedValue(new Error('RPC unavailable'));
625+
// Indicates that we want to skip native for that chain
626+
jest.mocked(shouldSkipNativeForCaipChainId).mockReturnValue(true);
627+
const request = createDataRequest();
628+
const response = await controller.fetch(request);
629+
expect(response.errors).toBeDefined();
630+
expect(response.errors?.[MOCK_CHAIN_ID_CAIP]).toBe('RPC fetch failed');
631+
expect(response.assetsBalance).toBeDefined();
632+
expect(response.assetsBalance?.[MOCK_ACCOUNT_ID]).toBeDefined();
633+
expect(response.assetsBalance?.[MOCK_ACCOUNT_ID]).not.toHaveProperty(
634+
'eip155:1/slip44:60',
635+
);
636+
});
637+
});
638+
579639
it('returns undefined from #getProvider when network client has no provider', async () => {
580640
const networkState = createMockNetworkState(NetworkStatus.Available);
581641
(networkState.networkConfigurationsByChainId as Record<string, unknown>)[

packages/assets-controller/src/data-sources/RpcDataSource.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import type {
6161
BalanceFetchResult,
6262
TokenDetectionResult,
6363
} from './evm-rpc-services/types';
64+
import { shouldSkipNativeForCaipChainId } from './evm-rpc-services/utils/assets';
6465

6566
const CONTROLLER_NAME = 'RpcDataSource';
6667
const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds
@@ -941,12 +942,14 @@ export class RpcDataSource extends AbstractDataSource<
941942

942943
for (const chainId of chainsForAccount) {
943944
const hexChainId = caipChainIdToHex(chainId);
944-
945-
// Build a single AssetFetchEntry[] for native + custom ERC-20s
946945
const nativeAssetId = this.#getNativeAssetForChain(chainId);
947-
const assetsToFetch: AssetFetchEntry[] = [
948-
{ assetId: nativeAssetId, address: ZERO_ADDRESS },
949-
];
946+
947+
const shouldSkipNative = shouldSkipNativeForCaipChainId(chainId);
948+
const assetsToFetch: AssetFetchEntry[] = [];
949+
if (!shouldSkipNative) {
950+
// Build a single AssetFetchEntry[] for native + custom ERC-20s
951+
assetsToFetch.push({ assetId: nativeAssetId, address: ZERO_ADDRESS });
952+
}
950953

951954
if (request.customAssets) {
952955
const existingMetadata = this.#getExistingAssetsMetadata();
@@ -1041,8 +1044,10 @@ export class RpcDataSource extends AbstractDataSource<
10411044
if (!assetsBalance[accountId]) {
10421045
assetsBalance[accountId] = {};
10431046
}
1044-
assetsBalance[accountId][nativeAssetId] = { amount: '0' };
10451047

1048+
if (!shouldSkipNative) {
1049+
assetsBalance[accountId][nativeAssetId] = { amount: '0' };
1050+
}
10461051
// Even on error, include native token metadata. Prefer the richer
10471052
// metadata already in state (e.g. enriched with image/description
10481053
// by the price/info API) and fall back to a minimal stub only when
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { CHAIN_IDS_WITH_NO_NATIVE_TOKEN } from '@metamask/controller-utils';
2+
import { CaipChainId } from '@metamask/utils';
3+
4+
export function shouldSkipNativeForCaipChainId(
5+
caipChainId: CaipChainId,
6+
): boolean {
7+
return (CHAIN_IDS_WITH_NO_NATIVE_TOKEN as readonly string[]).includes(
8+
caipChainId,
9+
);
10+
}

packages/assets-controllers/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Changed
1111

1212
- Bump `@metamask/network-controller` from `^31.0.0` to `^31.1.0` ([#8765](https://github.com/MetaMask/core/pull/8765))
13+
- Modify `SPOT_PRICES_SUPPORT_INFO` entries for Tempo chains (`0x1079` and `0xa5bf`) ([#8638](https://github.com/MetaMask/core/pull/8638))
1314

1415
## [106.0.1]
1516

packages/assets-controllers/src/token-prices-service/codefi-v2.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,6 @@ const chainIdToNativeTokenAddress: Record<Hex, Hex> = {
233233
'0x64': '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', // Gnosis
234234
'0x1e': '0x542fda317318ebf1d3deaf76e0b632741a7e677d', // Rootstock Mainnet - Native symbol: RBTC
235235
'0x3dc': '0x779ded0c9e1022225f8e0630b35a9b54be713736', // Stable - Native symbol: USDT0
236-
'0x1079': '0x20c0000000000000000000000000000000000000', // Tempo Mainnet - Pseudo-Native symbol: pathUSD
237-
'0xa5bf': '0x20c0000000000000000000000000000000000000', // Tempo Moderato Testnet - Pseudo-Native symbol: pathUSD
238236
};
239237

240238
/**
@@ -289,7 +287,7 @@ export const SPOT_PRICES_SUPPORT_INFO = {
289287
'0x74c': 'eip155:1868/erc20:0x0000000000000000000000000000000000000000', // Soneium - Native symbol: ETH
290288
'0xa729': 'eip155:42793/erc20:0x0000000000000000000000000000000000000000', // Etherlink - Native symbol: XTZ (Tezos L2)
291289
'0xab5': 'eip155:2741/erc20:0x0000000000000000000000000000000000000000', // Abstract - Native symbol: ETH
292-
'0x1079': 'eip155:4217/erc20:0x20c0000000000000000000000000000000000000', // Tempo Mainnet - Pseudo-Native symbol: pathUSD
290+
'0x1079': 'eip155:4217/slip44:60', // Tempo Mainnet - No native asset
293291
'0x10e6': 'eip155:4326/erc20:0x0000000000000000000000000000000000000000', // MegaETH Mainnet - Native symbol: ETH
294292
'0x1388': 'eip155:5000/erc20:0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000', // Mantle - Native symbol: MNT
295293
'0x2105': 'eip155:8453/slip44:60', // Base - Native symbol: ETH
@@ -302,7 +300,7 @@ export const SPOT_PRICES_SUPPORT_INFO = {
302300
'0xa516': 'eip155:42262/slip44:474', // Oasis Emerald - Native symbol: ROSE
303301
'0xa867': 'eip155:43111/erc20:0x0000000000000000000000000000000000000000', // Hemi - Native symbol: ETH
304302
'0xa86a': 'eip155:43114/slip44:9005', // Avalanche C-Chain - Native symbol: AVAX
305-
'0xa5bf': 'eip155:42431/erc20:0x20c0000000000000000000000000000000000000', // Tempo Testnet Moderato - Pseudo-Native symbol: pathUSD
303+
'0xa5bf': 'eip155:42431/slip44:60', // Tempo Testnet Moderato - No native asset
306304
'0xe708': 'eip155:59144/slip44:60', // Linea Mainnet - Native symbol: ETH
307305
'0xed88': 'eip155:60808/erc20:0x0000000000000000000000000000000000000000', // BOB - Native symbol: ETH
308306
'0x138de': 'eip155:80094/erc20:0x0000000000000000000000000000000000000000', // Berachain - Native symbol: Bera',

0 commit comments

Comments
 (0)