From 86c963776f96eb095392574534a460882f5e7fd0 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 16 Apr 2026 11:16:59 +0100 Subject: [PATCH 01/12] fix native asset detection --- .../assets-controller/src/AssetsController.ts | 5 +++-- .../BackendWebsocketDataSource.ts | 3 ++- .../src/data-sources/RpcDataSource.ts | 7 +++++-- .../src/data-sources/TokenDataSource.ts | 20 +++++++++---------- .../services/BalanceFetcher.ts | 6 +++--- .../src/utils/formatExchangeRatesForBridge.ts | 17 +++++++++------- .../src/utils/formatStateForTransactionPay.ts | 14 ++++++------- .../src/utils/isNativeAsset.ts | 14 +++++++++++++ packages/assets-controllers/src/index.ts | 1 + .../src/token-prices-service/index.ts | 1 + 10 files changed, 56 insertions(+), 32 deletions(-) create mode 100644 packages/assets-controller/src/utils/isNativeAsset.ts diff --git a/packages/assets-controller/src/AssetsController.ts b/packages/assets-controller/src/AssetsController.ts index f7b4062960d..ab9b6664354 100644 --- a/packages/assets-controller/src/AssetsController.ts +++ b/packages/assets-controller/src/AssetsController.ts @@ -119,6 +119,7 @@ import type { BridgeExchangeRatesFormat, TransactionPayLegacyFormat, } from './utils'; +import { isNativeAsset } from './utils/isNativeAsset'; // ============================================================================ // PENDING TOKEN METADATA (UI input format for addCustomAsset) @@ -1520,7 +1521,7 @@ export class AssetsController extends BaseController< if (pendingMetadata) { const parsed = parseCaipAssetType(normalizedAssetId); let tokenType: FungibleAssetMetadata['type'] = 'erc20'; - if (parsed.assetNamespace === 'slip44') { + if (isNativeAsset(normalizedAssetId)) { tokenType = 'native'; } else if (parsed.assetNamespace === 'spl') { tokenType = 'spl'; @@ -2140,7 +2141,7 @@ export class AssetsController extends BaseController< } // Check if it's a native token (either by metadata type or assetId format) - const isNative = metadata.type === 'native' || assetId.includes('/slip44:'); + const isNative = metadata.type === 'native' || isNativeAsset(assetId); return isNative; } diff --git a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts index 1edfafe3183..7e4b3ad49b3 100644 --- a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts +++ b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts @@ -29,6 +29,7 @@ import type { DataSourceState, SubscriptionRequest, } from './AbstractDataSource'; +import { isNativeAsset } from 'src/utils/isNativeAsset'; // ============================================================================ // CONSTANTS @@ -639,7 +640,7 @@ export class BackendWebsocketDataSource extends AbstractDataSource< const assetId = asset.type as Caip19AssetId; // Determine token type from asset type string - const isNative = asset.type.includes('/slip44:'); + const isNative = isNativeAsset(assetId); const tokenType = isNative ? 'native' : 'erc20'; // We assume decimals are always present; skip malformed updates diff --git a/packages/assets-controller/src/data-sources/RpcDataSource.ts b/packages/assets-controller/src/data-sources/RpcDataSource.ts index 8094dd569d4..50626861b3c 100644 --- a/packages/assets-controller/src/data-sources/RpcDataSource.ts +++ b/packages/assets-controller/src/data-sources/RpcDataSource.ts @@ -61,6 +61,7 @@ import type { BalanceFetchResult, TokenDetectionResult, } from './evm-rpc-services/types'; +import { isNativeAsset } from 'src/utils/isNativeAsset'; const CONTROLLER_NAME = 'RpcDataSource'; const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds @@ -343,7 +344,7 @@ export class RpcDataSource extends AbstractDataSource< const existingMetadata = this.#getExistingAssetsMetadata(); for (const balance of balances) { - const isNative = balance.assetId.includes('/slip44:'); + const isNative = isNativeAsset(balance.assetId); if (isNative) { const chainStatus = this.#chainStatuses[chainId]; @@ -1367,7 +1368,9 @@ export class RpcDataSource extends AbstractDataSource< 'NetworkEnablementController:getState', ); - return nativeAssetIdentifiers[chainId] ?? `${chainId}/slip44:60`; + return ( + nativeAssetIdentifiers[chainId] ?? `${chainId}/erc20:${ZERO_ADDRESS}` + ); } /** diff --git a/packages/assets-controller/src/data-sources/TokenDataSource.ts b/packages/assets-controller/src/data-sources/TokenDataSource.ts index 9ee8041906c..337cb67157b 100644 --- a/packages/assets-controller/src/data-sources/TokenDataSource.ts +++ b/packages/assets-controller/src/data-sources/TokenDataSource.ts @@ -21,6 +21,7 @@ import { isStakingContractAssetId, reduceInBatchesSerially, } from './evm-rpc-services'; +import { isNativeAsset } from 'src/utils/isNativeAsset'; // ============================================================================ // CONSTANTS @@ -85,13 +86,13 @@ export type TokenDataSourceAllowedActions = * @returns FungibleAssetMetadata for state storage. */ function transformV3AssetResponseToMetadata( - assetId: string, + assetId: Caip19AssetId, assetData: V3AssetResponse, ): AssetMetadata { - const parsed = parseCaipAssetType(assetId as CaipAssetType); + const parsed = parseCaipAssetType(assetId); let tokenType: 'native' | 'erc20' | 'spl' = 'erc20'; - if (parsed.assetNamespace === 'slip44') { + if (isNativeAsset(assetId)) { tokenType = 'native'; } else if (parsed.assetNamespace === 'spl') { tokenType = 'spl'; @@ -396,18 +397,17 @@ export class TokenDataSource { const nonEvmTokenIds: string[] = []; for (const assetData of metadataResponse) { - const { assetNamespace, chain } = parseCaipAssetType( - assetData.assetId as CaipAssetType, - ); - if (assetNamespace === CaipAssetNamespace.Slip44) { + const assetId = assetData.assetId as Caip19AssetId; + const { assetNamespace, chain } = parseCaipAssetType(assetId); + if (isNativeAsset(assetId)) { // Native assets are always kept — no filtering. } else if ( assetNamespace === CaipAssetNamespace.Erc20 && chain.namespace === KnownCaipNamespace.Eip155 ) { - evmErc20Ids.push(assetData.assetId); + evmErc20Ids.push(assetId); } else if (assetNamespace === CaipAssetNamespace.Token) { - nonEvmTokenIds.push(assetData.assetId); + nonEvmTokenIds.push(assetId); } } @@ -461,7 +461,7 @@ export class TokenDataSource { const caipAssetId = assetData.assetId as Caip19AssetId; response.assetsInfo[caipAssetId] = transformV3AssetResponseToMetadata( - assetData.assetId, + caipAssetId, assetData, ); } diff --git a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts index 4c4a6a4b429..5de30641409 100644 --- a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts +++ b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts @@ -16,6 +16,7 @@ import type { ChainId, } from '../types'; import { reduceInBatchesSerially } from '../utils'; +import { isNativeAsset } from 'src/utils/isNativeAsset'; const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds @@ -139,7 +140,6 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly 0 ? usdPrice / nativeAssetUsdPrice : usdPrice; if (!marketData[chainIdHex]) { @@ -131,7 +134,7 @@ export function formatExchangeRatesForBridge(params: { } as MarketDataDetails; } - if (parsed.assetNamespace === 'slip44' && nativeAssetId) { + if (isNative) { currencyRates[nativeCurrencySymbol] = { conversionDate: lastUpdatedInSeconds, conversionRate: price, diff --git a/packages/assets-controller/src/utils/formatStateForTransactionPay.ts b/packages/assets-controller/src/utils/formatStateForTransactionPay.ts index 211c9b72a72..8fd64b5e852 100644 --- a/packages/assets-controller/src/utils/formatStateForTransactionPay.ts +++ b/packages/assets-controller/src/utils/formatStateForTransactionPay.ts @@ -10,6 +10,8 @@ import type { } from '../types'; import { formatExchangeRatesForBridge } from './formatExchangeRatesForBridge'; import type { BridgeExchangeRatesFormat } from './formatExchangeRatesForBridge'; +import { isNativeAsset } from './isNativeAsset'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; /** Account with id and address for mapping state to legacy format. */ export type AccountForLegacyFormat = { id: string; address: string }; @@ -115,9 +117,8 @@ export function formatStateForTransactionPay(params: { const amount = getAmountFromBalance(balance); const balanceHex = amountToHex(amount); - if (parsed.assetNamespace === 'slip44') { - const nativeAddress = - '0x0000000000000000000000000000000000000000' as const; + if (isNativeAsset(assetId as Caip19AssetId)) { + const nativeAddress = getNativeTokenAddress(chainIdHex); const checksumAddress = toChecksumAddress(account.address); tokenBalances[accountAddressLower] ??= {}; tokenBalances[accountAddressLower][chainIdHex] ??= {}; @@ -148,10 +149,9 @@ export function formatStateForTransactionPay(params: { continue; } const chainIdHex = numberToHex(parseInt(chainIdParsed.reference, 10)); - const address = - parsed.assetNamespace === 'slip44' - ? '0x0000000000000000000000000000000000000000' - : toChecksumAddress(String(parsed.assetReference)); + const address = isNativeAsset(assetId as Caip19AssetId) + ? getNativeTokenAddress(chainIdHex) + : toChecksumAddress(String(parsed.assetReference)); const token: LegacyToken = { address, decimals: metadata.decimals, diff --git a/packages/assets-controller/src/utils/isNativeAsset.ts b/packages/assets-controller/src/utils/isNativeAsset.ts new file mode 100644 index 00000000000..d10e82a9cfd --- /dev/null +++ b/packages/assets-controller/src/utils/isNativeAsset.ts @@ -0,0 +1,14 @@ +import { parseCaipAssetType } from '@metamask/utils'; +import type { Caip19AssetId } from '../types'; +import { SPOT_PRICES_SUPPORT_INFO } from '@metamask/assets-controllers'; + +export function isNativeAsset(assetId: Caip19AssetId): boolean { + const { assetNamespace } = parseCaipAssetType(assetId); + if (assetNamespace === 'slip44') { + return true; + } + + return Object.values(SPOT_PRICES_SUPPORT_INFO).some( + (nativeAssetId) => nativeAssetId.toLowerCase() === assetId.toLowerCase(), + ); +} diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 7bfd3752fd3..6eff95c1ee6 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -166,6 +166,7 @@ export { CodefiTokenPricesServiceV2, SUPPORTED_CHAIN_IDS, getNativeTokenAddress, + SPOT_PRICES_SUPPORT_INFO, } from './token-prices-service'; export { searchTokens, diff --git a/packages/assets-controllers/src/token-prices-service/index.ts b/packages/assets-controllers/src/token-prices-service/index.ts index cb788010df2..3d32fe36183 100644 --- a/packages/assets-controllers/src/token-prices-service/index.ts +++ b/packages/assets-controllers/src/token-prices-service/index.ts @@ -9,4 +9,5 @@ export { fetchSupportedNetworks, getSupportedNetworks, resetSupportedNetworksCache, + SPOT_PRICES_SUPPORT_INFO, } from './codefi-v2'; From 3427f78a1cd6974f69e7de59318d15047b5370e5 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 16 Apr 2026 11:26:49 +0100 Subject: [PATCH 02/12] references --- .../src/data-sources/BackendWebsocketDataSource.ts | 2 +- packages/assets-controller/src/data-sources/RpcDataSource.ts | 2 +- packages/assets-controller/src/data-sources/TokenDataSource.ts | 2 +- .../data-sources/evm-rpc-services/services/BalanceFetcher.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts index 7e4b3ad49b3..a4c82523308 100644 --- a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts +++ b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts @@ -29,7 +29,7 @@ import type { DataSourceState, SubscriptionRequest, } from './AbstractDataSource'; -import { isNativeAsset } from 'src/utils/isNativeAsset'; +import { isNativeAsset } from '../utils/isNativeAsset'; // ============================================================================ // CONSTANTS diff --git a/packages/assets-controller/src/data-sources/RpcDataSource.ts b/packages/assets-controller/src/data-sources/RpcDataSource.ts index 50626861b3c..c6bd3468e26 100644 --- a/packages/assets-controller/src/data-sources/RpcDataSource.ts +++ b/packages/assets-controller/src/data-sources/RpcDataSource.ts @@ -61,7 +61,7 @@ import type { BalanceFetchResult, TokenDetectionResult, } from './evm-rpc-services/types'; -import { isNativeAsset } from 'src/utils/isNativeAsset'; +import { isNativeAsset } from '../utils/isNativeAsset'; const CONTROLLER_NAME = 'RpcDataSource'; const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds diff --git a/packages/assets-controller/src/data-sources/TokenDataSource.ts b/packages/assets-controller/src/data-sources/TokenDataSource.ts index 337cb67157b..40f8227a642 100644 --- a/packages/assets-controller/src/data-sources/TokenDataSource.ts +++ b/packages/assets-controller/src/data-sources/TokenDataSource.ts @@ -21,7 +21,7 @@ import { isStakingContractAssetId, reduceInBatchesSerially, } from './evm-rpc-services'; -import { isNativeAsset } from 'src/utils/isNativeAsset'; +import { isNativeAsset } from '../utils/isNativeAsset'; // ============================================================================ // CONSTANTS diff --git a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts index 5de30641409..6b488ca508a 100644 --- a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts +++ b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts @@ -16,7 +16,7 @@ import type { ChainId, } from '../types'; import { reduceInBatchesSerially } from '../utils'; -import { isNativeAsset } from 'src/utils/isNativeAsset'; +import { isNativeAsset } from '../../../utils/isNativeAsset'; const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds From 73db2ea4b5a962ac2ea85b66646f6352f24308e9 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 16 Apr 2026 11:50:33 +0100 Subject: [PATCH 03/12] better detection --- .../src/utils/isNativeAsset.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/assets-controller/src/utils/isNativeAsset.ts b/packages/assets-controller/src/utils/isNativeAsset.ts index d10e82a9cfd..a4f02e0144c 100644 --- a/packages/assets-controller/src/utils/isNativeAsset.ts +++ b/packages/assets-controller/src/utils/isNativeAsset.ts @@ -1,14 +1,30 @@ +import { SPOT_PRICES_SUPPORT_INFO } from '@metamask/assets-controllers'; import { parseCaipAssetType } from '@metamask/utils'; import type { Caip19AssetId } from '../types'; -import { SPOT_PRICES_SUPPORT_INFO } from '@metamask/assets-controllers'; +import { ZERO_ADDRESS } from './constants'; export function isNativeAsset(assetId: Caip19AssetId): boolean { - const { assetNamespace } = parseCaipAssetType(assetId); + const { assetNamespace, assetReference } = parseCaipAssetType(assetId); + + // Consider all SLIP44 assets if (assetNamespace === 'slip44') { return true; } - return Object.values(SPOT_PRICES_SUPPORT_INFO).some( - (nativeAssetId) => nativeAssetId.toLowerCase() === assetId.toLowerCase(), - ); + // Consider assets in the list of native assets + if ( + Object.values(SPOT_PRICES_SUPPORT_INFO).some( + (nativeAssetId) => nativeAssetId.toLowerCase() === assetId.toLowerCase(), + ) + ) { + return true; + } + + // Consider assets with a zero address + if (assetReference === ZERO_ADDRESS) { + return true; + } + + // Otherwise, not a native asset + return false; } From 7a4bc445669fdee6385d0c3f5af32ec5786b544b Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 16 Apr 2026 13:13:43 +0100 Subject: [PATCH 04/12] better comments --- packages/assets-controller/src/utils/isNativeAsset.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/assets-controller/src/utils/isNativeAsset.ts b/packages/assets-controller/src/utils/isNativeAsset.ts index a4f02e0144c..9df1a201df2 100644 --- a/packages/assets-controller/src/utils/isNativeAsset.ts +++ b/packages/assets-controller/src/utils/isNativeAsset.ts @@ -6,12 +6,12 @@ import { ZERO_ADDRESS } from './constants'; export function isNativeAsset(assetId: Caip19AssetId): boolean { const { assetNamespace, assetReference } = parseCaipAssetType(assetId); - // Consider all SLIP44 assets + // All SLIP44 assets are native assets if (assetNamespace === 'slip44') { return true; } - // Consider assets in the list of native assets + // All assets in this list are native assets if ( Object.values(SPOT_PRICES_SUPPORT_INFO).some( (nativeAssetId) => nativeAssetId.toLowerCase() === assetId.toLowerCase(), @@ -20,11 +20,11 @@ export function isNativeAsset(assetId: Caip19AssetId): boolean { return true; } - // Consider assets with a zero address - if (assetReference === ZERO_ADDRESS) { + // ERC20 assets with a zero address are native assets + if (assetNamespace === 'erc20' && assetReference === ZERO_ADDRESS) { return true; } - // Otherwise, not a native asset + // Not a native asset return false; } From 890395e7d51ade752f06f805e5ad97de32616018 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 17 Apr 2026 09:37:41 +0100 Subject: [PATCH 05/12] checksum address --- .../src/utils/formatExchangeRatesForBridge.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts index 30ce8db6aae..9eeb6ddbb96 100644 --- a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts +++ b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts @@ -113,9 +113,9 @@ export function formatExchangeRatesForBridge(params: { let tokenAddress: Hex | undefined; if (parsed.assetNamespace === 'erc20') { - tokenAddress = toChecksumAddress(String(parsed.assetReference)); + tokenAddress = toChecksumAddress(parsed.assetReference); } else if (isNative) { - tokenAddress = getNativeTokenAddress(chainIdHex); + tokenAddress = toChecksumAddress(getNativeTokenAddress(chainIdHex)); } if (tokenAddress) { From eeb6950020f6369806d3d61cc6e83fc56dc9ae6e Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 17 Apr 2026 09:38:09 +0100 Subject: [PATCH 06/12] test --- .../src/utils/formatExchangeRatesForBridge.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts index ed66879a225..3dff65178a2 100644 --- a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts +++ b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts @@ -123,8 +123,8 @@ describe('formatExchangeRatesForBridge', () => { conversionRate: 0.5, usdConversionRate: 0.5, }); - const nativeAddress = '0x0000000000000000000000000000000000000000'; - expect(result.marketData['0x89']?.[nativeAddress]?.currency).toBe('POL'); + const nativeAddressPol = '0x0000000000000000000000000000000000001010'; + expect(result.marketData['0x89']?.[nativeAddressPol]?.currency).toBe('POL'); }); it('includes EVM native asset in marketData and currencyRates', () => { From 6accd03358c66fdfb78955b77213563c3adc379d Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 17 Apr 2026 09:45:46 +0100 Subject: [PATCH 07/12] missing export --- .../assets-controllers/src/token-prices-service/index.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/assets-controllers/src/token-prices-service/index.test.ts b/packages/assets-controllers/src/token-prices-service/index.test.ts index 28066404bd1..d67c48e53fe 100644 --- a/packages/assets-controllers/src/token-prices-service/index.test.ts +++ b/packages/assets-controllers/src/token-prices-service/index.test.ts @@ -10,6 +10,7 @@ describe('token-prices-service', () => { "fetchSupportedNetworks", "getSupportedNetworks", "resetSupportedNetworksCache", + "SPOT_PRICES_SUPPORT_INFO", ] `); }); From 3f0b46689b1d10aa0b34123474dee6e737faf990 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 17 Apr 2026 10:25:13 +0100 Subject: [PATCH 08/12] better comment --- .../data-sources/evm-rpc-services/services/BalanceFetcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts index 6b488ca508a..3fb9c28e8df 100644 --- a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts +++ b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts @@ -151,7 +151,7 @@ export class BalanceFetcher extends StaticIntervalPollingControllerOnly Date: Fri, 17 Apr 2026 10:34:42 +0100 Subject: [PATCH 09/12] linting --- .../src/data-sources/BackendWebsocketDataSource.ts | 3 ++- packages/assets-controller/src/data-sources/RpcDataSource.ts | 3 ++- packages/assets-controller/src/data-sources/TokenDataSource.ts | 3 ++- .../data-sources/evm-rpc-services/services/BalanceFetcher.ts | 3 ++- .../src/utils/formatStateForTransactionPay.ts | 2 +- packages/assets-controller/src/utils/isNativeAsset.ts | 1 + 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts index a4c82523308..c0a2f8cf0f3 100644 --- a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts +++ b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts @@ -24,12 +24,13 @@ import type { AssetBalance, DataResponse, } from '../types'; +import { isNativeAsset } from '../utils/isNativeAsset'; + import { AbstractDataSource } from './AbstractDataSource'; import type { DataSourceState, SubscriptionRequest, } from './AbstractDataSource'; -import { isNativeAsset } from '../utils/isNativeAsset'; // ============================================================================ // CONSTANTS diff --git a/packages/assets-controller/src/data-sources/RpcDataSource.ts b/packages/assets-controller/src/data-sources/RpcDataSource.ts index c6bd3468e26..aa2f5c517a4 100644 --- a/packages/assets-controller/src/data-sources/RpcDataSource.ts +++ b/packages/assets-controller/src/data-sources/RpcDataSource.ts @@ -39,6 +39,8 @@ import type { } from '../types'; import { normalizeAssetId } from '../utils'; import { ZERO_ADDRESS } from '../utils/constants'; +import { isNativeAsset } from '../utils/isNativeAsset'; + import { AbstractDataSource } from './AbstractDataSource'; import type { DataSourceState, @@ -61,7 +63,6 @@ import type { BalanceFetchResult, TokenDetectionResult, } from './evm-rpc-services/types'; -import { isNativeAsset } from '../utils/isNativeAsset'; const CONTROLLER_NAME = 'RpcDataSource'; const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds diff --git a/packages/assets-controller/src/data-sources/TokenDataSource.ts b/packages/assets-controller/src/data-sources/TokenDataSource.ts index 40f8227a642..31572e6f62f 100644 --- a/packages/assets-controller/src/data-sources/TokenDataSource.ts +++ b/packages/assets-controller/src/data-sources/TokenDataSource.ts @@ -17,11 +17,12 @@ import type { Middleware, FungibleAssetMetadata, } from '../types'; +import { isNativeAsset } from '../utils/isNativeAsset'; + import { isStakingContractAssetId, reduceInBatchesSerially, } from './evm-rpc-services'; -import { isNativeAsset } from '../utils/isNativeAsset'; // ============================================================================ // CONSTANTS diff --git a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts index 3fb9c28e8df..612ca4dd348 100644 --- a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts +++ b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts @@ -2,6 +2,8 @@ import { StaticIntervalPollingControllerOnly } from '@metamask/polling-controlle import { parseCaipAssetType } from '@metamask/utils'; import { ZERO_ADDRESS } from '../../../utils/constants'; +import { isNativeAsset } from '../../../utils/isNativeAsset'; + import type { MulticallClient } from '../clients'; import type { AccountId, @@ -16,7 +18,6 @@ import type { ChainId, } from '../types'; import { reduceInBatchesSerially } from '../utils'; -import { isNativeAsset } from '../../../utils/isNativeAsset'; const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds diff --git a/packages/assets-controller/src/utils/formatStateForTransactionPay.ts b/packages/assets-controller/src/utils/formatStateForTransactionPay.ts index 8fd64b5e852..567b28b1f88 100644 --- a/packages/assets-controller/src/utils/formatStateForTransactionPay.ts +++ b/packages/assets-controller/src/utils/formatStateForTransactionPay.ts @@ -1,4 +1,5 @@ import { toChecksumAddress } from '@ethereumjs/util'; +import { getNativeTokenAddress } from '@metamask/assets-controllers'; import { numberToHex } from '@metamask/utils'; import { parseCaipAssetType, parseCaipChainId } from '@metamask/utils'; @@ -11,7 +12,6 @@ import type { import { formatExchangeRatesForBridge } from './formatExchangeRatesForBridge'; import type { BridgeExchangeRatesFormat } from './formatExchangeRatesForBridge'; import { isNativeAsset } from './isNativeAsset'; -import { getNativeTokenAddress } from '@metamask/assets-controllers'; /** Account with id and address for mapping state to legacy format. */ export type AccountForLegacyFormat = { id: string; address: string }; diff --git a/packages/assets-controller/src/utils/isNativeAsset.ts b/packages/assets-controller/src/utils/isNativeAsset.ts index 9df1a201df2..04f1130745d 100644 --- a/packages/assets-controller/src/utils/isNativeAsset.ts +++ b/packages/assets-controller/src/utils/isNativeAsset.ts @@ -1,5 +1,6 @@ import { SPOT_PRICES_SUPPORT_INFO } from '@metamask/assets-controllers'; import { parseCaipAssetType } from '@metamask/utils'; + import type { Caip19AssetId } from '../types'; import { ZERO_ADDRESS } from './constants'; From 994d5ea373740e15782e8b7c20716112e478daba Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 17 Apr 2026 11:08:57 +0100 Subject: [PATCH 10/12] linting --- .../src/data-sources/BackendWebsocketDataSource.ts | 1 - packages/assets-controller/src/data-sources/RpcDataSource.ts | 1 - packages/assets-controller/src/data-sources/TokenDataSource.ts | 1 - .../src/data-sources/evm-rpc-services/services/BalanceFetcher.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts index c0a2f8cf0f3..279b9c44e7e 100644 --- a/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts +++ b/packages/assets-controller/src/data-sources/BackendWebsocketDataSource.ts @@ -25,7 +25,6 @@ import type { DataResponse, } from '../types'; import { isNativeAsset } from '../utils/isNativeAsset'; - import { AbstractDataSource } from './AbstractDataSource'; import type { DataSourceState, diff --git a/packages/assets-controller/src/data-sources/RpcDataSource.ts b/packages/assets-controller/src/data-sources/RpcDataSource.ts index aa2f5c517a4..ab7e4d5c0c7 100644 --- a/packages/assets-controller/src/data-sources/RpcDataSource.ts +++ b/packages/assets-controller/src/data-sources/RpcDataSource.ts @@ -40,7 +40,6 @@ import type { import { normalizeAssetId } from '../utils'; import { ZERO_ADDRESS } from '../utils/constants'; import { isNativeAsset } from '../utils/isNativeAsset'; - import { AbstractDataSource } from './AbstractDataSource'; import type { DataSourceState, diff --git a/packages/assets-controller/src/data-sources/TokenDataSource.ts b/packages/assets-controller/src/data-sources/TokenDataSource.ts index 31572e6f62f..807dda1e4b1 100644 --- a/packages/assets-controller/src/data-sources/TokenDataSource.ts +++ b/packages/assets-controller/src/data-sources/TokenDataSource.ts @@ -18,7 +18,6 @@ import type { FungibleAssetMetadata, } from '../types'; import { isNativeAsset } from '../utils/isNativeAsset'; - import { isStakingContractAssetId, reduceInBatchesSerially, diff --git a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts index 612ca4dd348..f11e5c83989 100644 --- a/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts +++ b/packages/assets-controller/src/data-sources/evm-rpc-services/services/BalanceFetcher.ts @@ -3,7 +3,6 @@ import { parseCaipAssetType } from '@metamask/utils'; import { ZERO_ADDRESS } from '../../../utils/constants'; import { isNativeAsset } from '../../../utils/isNativeAsset'; - import type { MulticallClient } from '../clients'; import type { AccountId, From ab5b9533a1ba4a6e6ddadbc219a591b709ae5084 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 17 Apr 2026 11:30:50 +0100 Subject: [PATCH 11/12] checksum --- .../src/utils/formatStateForTransactionPay.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/assets-controller/src/utils/formatStateForTransactionPay.ts b/packages/assets-controller/src/utils/formatStateForTransactionPay.ts index 567b28b1f88..04ae2845e4b 100644 --- a/packages/assets-controller/src/utils/formatStateForTransactionPay.ts +++ b/packages/assets-controller/src/utils/formatStateForTransactionPay.ts @@ -118,7 +118,9 @@ export function formatStateForTransactionPay(params: { const balanceHex = amountToHex(amount); if (isNativeAsset(assetId as Caip19AssetId)) { - const nativeAddress = getNativeTokenAddress(chainIdHex); + const nativeAddress = toChecksumAddress( + getNativeTokenAddress(chainIdHex), + ); const checksumAddress = toChecksumAddress(account.address); tokenBalances[accountAddressLower] ??= {}; tokenBalances[accountAddressLower][chainIdHex] ??= {}; From cf3f16d4a41d0d12e5bfb1781684cb802c2df466 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 17 Apr 2026 11:46:43 +0100 Subject: [PATCH 12/12] changelog entries --- packages/assets-controller/CHANGELOG.md | 10 ++++++++++ packages/assets-controllers/CHANGELOG.md | 1 + 2 files changed, 11 insertions(+) diff --git a/packages/assets-controller/CHANGELOG.md b/packages/assets-controller/CHANGELOG.md index 208d91cd77f..bdf961a6a2d 100644 --- a/packages/assets-controller/CHANGELOG.md +++ b/packages/assets-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `isNativeAsset` utility that centralizes native asset detection across all CAIP-19 representations (`slip44:` namespace, known native asset IDs from `SPOT_PRICES_SUPPORT_INFO`, and `erc20:` with zero address) ([#8483](https://github.com/MetaMask/core/pull/8483)) + ### Changed - Bump `@metamask/keyring-api` from `^21.6.0` to `^23.0.1` ([#8464](https://github.com/MetaMask/core/pull/8464)) @@ -14,6 +18,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/keyring-snap-client` from `^8.2.0` to `^9.0.1` ([#8464](https://github.com/MetaMask/core/pull/8464)) - Bump `@metamask/transaction-controller` from `^64.2.0` to `^64.3.0` ([#8482](https://github.com/MetaMask/core/pull/8482)) +### Fixed + +- Native asset detection now correctly identifies native assets across all CAIP-19 representations, not just `slip44:` namespace checks ([#TBD](https://github.com/MetaMask/core/pull/TBD)) + - Previously, native assets represented as ERC-20 tokens (e.g., Polygon's POL at `0x…1010`) were not recognized as native, causing incorrect token type classification, balance handling, and missing entries in bridge exchange rates and transaction pay legacy formats. +- Legacy format conversions (bridge exchange rates and transaction pay) now use the correct chain-specific native token address via `getNativeTokenAddress()` instead of always using the zero address ([#TBD](https://github.com/MetaMask/core/pull/TBD)) + ## [6.0.0] ### Added diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 1307f34f3b5..f30b9218cfe 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `MultichainAssetsController`: periodic Blockaid re-scan of stored SPL-style `token:` assets (default once per day) so tokens that become malicious after a prior scan are dropped; use constructor option `blockaidTokenRescanInterval` (ms), or `0` to disable. ([#8400](https://github.com/MetaMask/core/pull/8400)) +- Export `SPOT_PRICES_SUPPORT_INFO` from the token prices service ([#8483](https://github.com/MetaMask/core/pull/8483)) ### Changed