From 4607d1fe535c42d751b81ac6d91486b40d9fc90f Mon Sep 17 00:00:00 2001 From: Maxime OUAIRY Date: Wed, 5 Aug 2026 11:01:13 +0200 Subject: [PATCH 1/6] feat(assets-controllers): resolve native asset ids from config registry before SPOT_PRICES_SUPPORT_INFO getAssetId (and CodefiTokenPricesServiceV2.fetchTokenPrices internally) now checks ConfigRegistryController's assets.native.assetId first, falling back to the hardcoded SPOT_PRICES_SUPPORT_INFO map and then to NetworkEnablementController's nativeAssetIdentifiers. New chains get correct native-asset pricing without a SPOT_PRICES_SUPPORT_INFO release. getAssetId keeps its original two-arg signature so internal fetchTokenPrices and external callers get the same answer: the registry data lives in a module-level cache (mirroring the existing getSupportedNetworks/ getSupportedCurrencies pattern in this file), fed and kept current by TokenRatesController via ConfigRegistryController's state and stateChanged event. BREAKING: TokenRatesControllerMessenger now requires the ConfigRegistryController:getState action and ConfigRegistryController:stateChanged event to be delegated. Co-Authored-By: Claude Sonnet 5 --- packages/assets-controllers/CHANGELOG.md | 6 + packages/assets-controllers/package.json | 1 + .../src/TokenRatesController.test.ts | 105 +++++++++++++++- .../src/TokenRatesController.ts | 40 ++++++- .../token-prices-service/codefi-v2.test.ts | 112 ++++++++++++++++++ .../src/token-prices-service/codefi-v2.ts | 57 +++++++-- .../src/token-prices-service/index.test.ts | 2 + .../src/token-prices-service/index.ts | 2 + 8 files changed, 311 insertions(+), 14 deletions(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 49e72ccf9e3..4b267b5b086 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,8 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `@metamask/config-registry-controller` as a dependency + ### Changed +- **BREAKING:** `TokenRatesControllerMessenger` now requires the `ConfigRegistryController:getState` action and `ConfigRegistryController:stateChanged` event to be delegated + - `getAssetId`/`CodefiTokenPricesServiceV2` now resolve native asset CAIP-19 IDs from the config registry's `assets.native.assetId` before falling back to the hardcoded `SPOT_PRICES_SUPPORT_INFO` map, then to `NetworkEnablementController`'s `nativeAssetIdentifiers`. This lets new chains get correct native-asset pricing without a `SPOT_PRICES_SUPPORT_INFO` release. `TokenRatesController` keeps the cache current as `ConfigRegistryController` polls for updates. - Bump `@metamask/transaction-controller` from `^69.4.0` to `^69.5.0` ([#9780](https://github.com/MetaMask/core/pull/9780)) - Bump `@metamask/keyring-api` from `^23.7.0` to `^24.0.0` ([#9754](https://github.com/MetaMask/core/pull/9754)) diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 266f523c4e8..aad9c0f3eed 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -66,6 +66,7 @@ "@metamask/accounts-controller": "^39.0.6", "@metamask/approval-controller": "^9.0.2", "@metamask/base-controller": "^9.1.0", + "@metamask/config-registry-controller": "^2.0.1", "@metamask/contract-metadata": "^2.4.0", "@metamask/controller-utils": "^12.3.0", "@metamask/core-backend": "^8.1.1", diff --git a/packages/assets-controllers/src/TokenRatesController.test.ts b/packages/assets-controllers/src/TokenRatesController.test.ts index 818da8ac738..a5f44adf509 100644 --- a/packages/assets-controllers/src/TokenRatesController.test.ts +++ b/packages/assets-controllers/src/TokenRatesController.test.ts @@ -1,4 +1,5 @@ import { deriveStateFromMetadata } from '@metamask/base-controller'; +import type { ConfigRegistryControllerState } from '@metamask/config-registry-controller'; import { ChainId, toChecksumHexAddress } from '@metamask/controller-utils'; import { Messenger, MOCK_ANY_NAMESPACE } from '@metamask/messenger'; import type { @@ -23,7 +24,11 @@ import type { AbstractTokenPricesService, EvmAssetWithMarketData, } from './token-prices-service/abstract-token-prices-service.js'; -import { ZERO_ADDRESS } from './token-prices-service/codefi-v2.js'; +import { + getAssetId, + resetNetworkConfigsCache, + ZERO_ADDRESS, +} from './token-prices-service/codefi-v2.js'; import { controllerName, TokenRatesController, @@ -75,13 +80,25 @@ function buildTokenRatesControllerMessenger( 'TokensController:getState', 'NetworkController:getState', 'NetworkEnablementController:getState', + 'ConfigRegistryController:getState', + ], + events: [ + 'TokensController:stateChange', + 'NetworkController:stateChange', + 'ConfigRegistryController:stateChanged', ], - events: ['TokensController:stateChange', 'NetworkController:stateChange'], }); return tokenRatesControllerMessenger; } describe('TokenRatesController', () => { + afterEach(() => { + // getAssetId's config registry cache is a module-level singleton (shared + // across the whole process, like getSupportedNetworks); reset it so + // tests don't leak state into each other. + resetNetworkConfigsCache(); + }); + describe('constructor', () => { it('should set default state', async () => { await withController(async ({ controller }) => { @@ -133,6 +150,55 @@ describe('TokenRatesController', () => { ); }); + it('seeds the config registry cache used by getAssetId at construction', async () => { + await withController( + { + mockConfigRegistryState: { + configs: { + networks: { + 'eip155:1': { + assets: { native: { assetId: 'eip155:1/slip44:61' } }, + }, + }, + }, + }, + }, + async () => { + // getAssetId is the function extracted for client parity with + // fetchTokenPrices; it must reflect the same registry data the + // controller just seeded, with no params beyond chain/token. + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:61'); + }, + ); + }); + + it('keeps the config registry cache current as ConfigRegistryController state changes', async () => { + await withController(async ({ triggerConfigRegistryStateChange }) => { + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:60'); // falls back to SPOT_PRICES_SUPPORT_INFO + + triggerConfigRegistryStateChange({ + configs: { + networks: { + 'eip155:1': { + assets: { native: { assetId: 'eip155:1/slip44:61' } }, + }, + }, + }, + version: null, + lastFetched: null, + etag: null, + } as ConfigRegistryControllerState); + + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:61'); + }); + }); + it('clears persisted marketData at construction when isDeprecated() returns true', async () => { const initialMarketData = { '0x1': { @@ -1444,10 +1510,15 @@ type WithControllerCallback = ({ controller, triggerTokensStateChange, triggerNetworkStateChange, + triggerConfigRegistryStateChange, }: { controller: TokenRatesController; triggerTokensStateChange: (state: TokensControllerState) => void; triggerNetworkStateChange: (state: NetworkState, patches?: Patch[]) => void; + triggerConfigRegistryStateChange: ( + state: ConfigRegistryControllerState, + patches?: Patch[], + ) => void; }) => Promise | ReturnValue; type WithControllerOptions = { @@ -1458,6 +1529,7 @@ type WithControllerOptions = { >; mockTokensControllerState?: Partial; mockNetworkState?: Partial; + mockConfigRegistryState?: Partial; }; type WithControllerArgs = @@ -1477,7 +1549,12 @@ async function withController( ...args: WithControllerArgs ): Promise { const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]]; - const { options, mockTokensControllerState, mockNetworkState } = rest; + const { + options, + mockTokensControllerState, + mockNetworkState, + mockConfigRegistryState, + } = rest; const messenger: RootMessenger = new Messenger({ namespace: MOCK_ANY_NAMESPACE, }); @@ -1512,6 +1589,18 @@ async function withController( }), ); + // Register ConfigRegistryController:getState handler + messenger.registerActionHandler( + 'ConfigRegistryController:getState', + jest.fn().mockReturnValue({ + configs: { networks: {} }, + version: null, + lastFetched: null, + etag: null, + ...mockConfigRegistryState, + }), + ); + const controller = new TokenRatesController({ tokenPricesService: buildMockTokenPricesService(), messenger: buildTokenRatesControllerMessenger(messenger), @@ -1529,6 +1618,16 @@ async function withController( ) => { messenger.publish('NetworkController:stateChange', state, patches); }, + triggerConfigRegistryStateChange: ( + state: ConfigRegistryControllerState, + patches: Patch[] = [], + ) => { + messenger.publish( + 'ConfigRegistryController:stateChanged', + state, + patches, + ); + }, }); } finally { controller.stopAllPolling(); diff --git a/packages/assets-controllers/src/TokenRatesController.ts b/packages/assets-controllers/src/TokenRatesController.ts index a27105de668..1bd9d5bcd50 100644 --- a/packages/assets-controllers/src/TokenRatesController.ts +++ b/packages/assets-controllers/src/TokenRatesController.ts @@ -3,6 +3,10 @@ import type { ControllerStateChangeEvent, StateMetadata, } from '@metamask/base-controller'; +import type { + ConfigRegistryControllerEvents, + ConfigRegistryControllerGetStateAction, +} from '@metamask/config-registry-controller'; import { toChecksumHexAddress } from '@metamask/controller-utils'; import type { Messenger } from '@metamask/messenger'; import type { @@ -19,7 +23,7 @@ import { TOKEN_PRICES_BATCH_SIZE, } from './assetsUtil.js'; import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service.js'; -import { getNativeTokenAddress } from './token-prices-service/codefi-v2.js'; +import { getNativeTokenAddress, setNetworkConfigs } from './token-prices-service/codefi-v2.js'; import { TokenRwaData } from './token-service.js'; import type { TokensControllerGetStateAction, @@ -98,14 +102,16 @@ type ChainIdAndNativeCurrency = { export type AllowedActions = | TokensControllerGetStateAction | NetworkControllerGetStateAction - | NetworkEnablementControllerGetStateAction; + | NetworkEnablementControllerGetStateAction + | ConfigRegistryControllerGetStateAction; /** * The external events available to the {@link TokenRatesController}. */ export type AllowedEvents = | TokensControllerStateChangeEvent - | NetworkControllerStateChangeEvent; + | NetworkControllerStateChangeEvent + | ConfigRegistryControllerEvents; /** * The name of the {@link TokenRatesController}. @@ -257,6 +263,11 @@ export class TokenRatesController extends StaticIntervalPollingController { + setNetworkConfigs(state.configs.networks); + }, + ); + } + /** * Get the tokens for the given chain. * diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts index f81cf6879e7..414ddecf9ff 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts @@ -1,3 +1,4 @@ +import type { RegistryNetworkConfig } from '@metamask/config-registry-controller'; import { KnownCaipNamespace } from '@metamask/utils'; import type { Hex } from '@metamask/utils'; import nock, { isDone } from 'nock'; @@ -16,6 +17,8 @@ import { getSupportedNetworks, resetSupportedNetworksCache, getAssetId, + setNetworkConfigs, + resetNetworkConfigsCache, } from './codefi-v2.js'; // We're not customizing the default max delay @@ -2144,6 +2147,74 @@ describe('CodefiTokenPricesServiceV2', () => { }); }); + describe('setNetworkConfigs', () => { + afterEach(() => { + resetSupportedNetworksCache(); + resetNetworkConfigsCache(); + }); + + it('uses the config registry asset id in fetchTokenPrices, taking priority over the hardcoded entry', async () => { + const mockNetworksResponse = { + fullSupport: ['eip155:1'], + partialSupport: { + spotPricesV2: [], + spotPricesV3: ['eip155:1'], + }, + }; + nock('https://price.api.cx.metamask.io') + .get('/v2/supportedNetworks') + .reply(200, mockNetworksResponse) + .persist(); + + await fetchSupportedNetworks(); + + const registryAssetId = 'eip155:1/slip44:61'; + + nock('https://price.api.cx.metamask.io') + .get('/v3/spot-prices') + .query(true) + .reply(200, { + [registryAssetId]: { + price: 3000, + currency: 'USD', + pricePercentChange1d: 1, + priceChange1d: 1, + marketCap: 1000000, + allTimeHigh: 5000, + allTimeLow: 100, + totalVolume: 50000, + high1d: 2100, + low1d: 1900, + circulatingSupply: 1000000, + dilutedMarketCap: 2000000, + marketCapPercentChange1d: 0.5, + pricePercentChange1h: 0.1, + pricePercentChange7d: 5, + pricePercentChange14d: 10, + pricePercentChange30d: 15, + pricePercentChange200d: 50, + pricePercentChange1y: 100, + }, + }); + + setNetworkConfigs({ + 'eip155:1': { + assets: { native: { assetId: registryAssetId } }, + } as RegistryNetworkConfig, + }); + + const service = new CodefiTokenPricesServiceV2(); + + const result = await service.fetchTokenPrices({ + assets: [{ chainId: '0x1', tokenAddress: ZERO_ADDRESS }], + currency: 'USD', + }); + + expect(result).toHaveLength(1); + expect(result[0].price).toBe(3000); + }); + }); + describe('validateChainIdSupported with dynamic networks', () => { afterEach(() => { resetSupportedNetworksCache(); @@ -2186,6 +2257,10 @@ describe('CodefiTokenPricesServiceV2', () => { }); describe('getAssetId', () => { + afterEach(() => { + resetNetworkConfigsCache(); + }); + it('returns a CAIP-19 erc20 id with a lowercased address for ERC20 tokens', () => { expect(getAssetId({ chainId: '0x1', tokenAddress: '0xABCDEF' })).toBe( 'eip155:1/erc20:0xabcdef', @@ -2239,6 +2314,43 @@ describe('CodefiTokenPricesServiceV2', () => { ).toBe('eip155:1/slip44:60'); }); + it('prefers the config registry entry over the hardcoded entry', () => { + setNetworkConfigs({ + 'eip155:1': { + assets: { native: { assetId: 'eip155:1/slip44:61' } }, + } as RegistryNetworkConfig, + }); + + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:61'); + }); + + it('prefers the config registry entry over nativeAssetIdentifiers when there is no hardcoded entry', () => { + // 0x42 (OKXChain) is not in SPOT_PRICES_SUPPORT_INFO + setNetworkConfigs({ + 'eip155:66': { + assets: { native: { assetId: 'eip155:66/erc20:0xregistry' } }, + } as RegistryNetworkConfig, + }); + + expect( + getAssetId({ + chainId: '0x42', + tokenAddress: ZERO_ADDRESS, + nativeAssetIdentifiers: { 'eip155:66': 'eip155:66/slip44:996' }, + }), + ).toBe('eip155:66/erc20:0xregistry'); + }); + + it('falls back to the hardcoded entry when the config registry has no entry for the chain', () => { + setNetworkConfigs({}); + + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:60'); + }); + it('returns undefined for a native token with no hardcoded entry and no identifier', () => { expect( getAssetId({ chainId: '0x42', tokenAddress: ZERO_ADDRESS }), diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts index cd74cc5743f..6ab01052215 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts @@ -6,8 +6,9 @@ import { DEFAULT_MAX_RETRIES, handleFetch, } from '@metamask/controller-utils'; +import type { RegistryNetworkConfig } from '@metamask/config-registry-controller'; import type { ServicePolicy } from '@metamask/controller-utils'; -import type { CaipAssetType, Hex } from '@metamask/utils'; +import type { CaipAssetType, CaipChainId, Hex } from '@metamask/utils'; import { hexToNumber, KnownCaipNamespace, @@ -570,14 +571,52 @@ export function resetSupportedCurrenciesCache(): void { lastFetchedCurrencies = null; } +/** + * In-memory cache of the config registry's network configurations, keyed by + * CAIP-2 chain ID. Populated via {@link setNetworkConfigs}, typically by + * TokenRatesController from ConfigRegistryController's state. + * + * This is a module-level cache (like {@link lastFetchedSupportedNetworks}) + * rather than instance state so that {@link getAssetId} gives the same + * answer whether it's called internally by {@link fetchTokenPrices} or + * directly by external callers — there is only one source of truth for a + * given chain's native asset ID, not one per service instance. + */ +let cachedNetworkConfigs: Record | null = + null; + +/** + * Updates the config registry network configurations used by + * {@link getAssetId} to resolve native asset CAIP-19 IDs. Should be called + * with ConfigRegistryController's `state.configs.networks`, ideally on every + * state change so lookups stay current as the registry is polled. + * + * @param networks - Network configurations keyed by CAIP-2 chain ID. + */ +export function setNetworkConfigs( + networks: Record, +): void { + cachedNetworkConfigs = networks; +} + +/** + * Resets the config registry network configurations cache. + * This is primarily intended for testing purposes. + */ +export function resetNetworkConfigsCache(): void { + cachedNetworkConfigs = null; +} + /** * Derives the CAIP-19 asset ID used to query the Price API for a token on a * given chain. * - * For native tokens, uses the hardcoded {@link SPOT_PRICES_SUPPORT_INFO} entry - * when defined, otherwise falls back to the provided native asset identifiers - * (sourced from NetworkEnablementController). For ERC20 tokens, constructs the - * CAIP-19 ID dynamically. + * For native tokens, prefers the CAIP-19 ID from the config registry cache + * (`assets.native.assetId`, see {@link setNetworkConfigs}) when available, + * then the hardcoded {@link SPOT_PRICES_SUPPORT_INFO} entry, and finally + * falls back to the provided native asset identifiers (sourced from + * NetworkEnablementController). For ERC20 tokens, constructs the CAIP-19 ID + * dynamically. * * @param args - The arguments to this function. * @param args.chainId - The hexadecimal chain ID the token lives on. @@ -606,12 +645,14 @@ export function getAssetId({ nativeAddress.toLowerCase() === tokenAddress.toLowerCase(); if (isNativeToken) { + const registryAssetId = cachedNetworkConfigs?.[caipChainId]?.assets + ?.native?.assetId as CaipAssetType | undefined; const hardcodedId = ( SPOT_PRICES_SUPPORT_INFO as Partial> )[chainId]; - return (hardcodedId ?? nativeAssetIdentifiers[caipChainId]) as - | CaipAssetType - | undefined; + return (registryAssetId ?? + hardcodedId ?? + nativeAssetIdentifiers[caipChainId]) as CaipAssetType | undefined; } return `${caipChainId}/erc20:${tokenAddress.toLowerCase()}` as CaipAssetType; 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 250d755a260..8067e5c1503 100644 --- a/packages/assets-controllers/src/token-prices-service/index.test.ts +++ b/packages/assets-controllers/src/token-prices-service/index.test.ts @@ -12,6 +12,8 @@ describe('token-prices-service', () => { "resetSupportedNetworksCache", "SPOT_PRICES_SUPPORT_INFO", "getAssetId", + "setNetworkConfigs", + "resetNetworkConfigsCache", ] `); }); diff --git a/packages/assets-controllers/src/token-prices-service/index.ts b/packages/assets-controllers/src/token-prices-service/index.ts index 8361bb68afa..c438f06f830 100644 --- a/packages/assets-controllers/src/token-prices-service/index.ts +++ b/packages/assets-controllers/src/token-prices-service/index.ts @@ -11,4 +11,6 @@ export { resetSupportedNetworksCache, SPOT_PRICES_SUPPORT_INFO, getAssetId, + setNetworkConfigs, + resetNetworkConfigsCache, } from './codefi-v2.js'; From 207fd29d776ca688fcb41e66223459fb769d1a5d Mon Sep 17 00:00:00 2001 From: Maxime OUAIRY Date: Wed, 5 Aug 2026 11:10:10 +0200 Subject: [PATCH 2/6] chore(assets-controllers): add PR reference to changelog entry Co-Authored-By: Claude Sonnet 5 --- packages/assets-controllers/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index b4a372b1b78..7248b67a4b6 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -9,11 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `@metamask/config-registry-controller` as a dependency +- Add `@metamask/config-registry-controller` as a dependency ([#9789](https://github.com/MetaMask/core/pull/9789)) ### Changed -- **BREAKING:** `TokenRatesControllerMessenger` now requires the `ConfigRegistryController:getState` action and `ConfigRegistryController:stateChanged` event to be delegated +- **BREAKING:** `TokenRatesControllerMessenger` now requires the `ConfigRegistryController:getState` action and `ConfigRegistryController:stateChanged` event to be delegated ([#9789](https://github.com/MetaMask/core/pull/9789)) - `getAssetId`/`CodefiTokenPricesServiceV2` now resolve native asset CAIP-19 IDs from the config registry's `assets.native.assetId` before falling back to the hardcoded `SPOT_PRICES_SUPPORT_INFO` map, then to `NetworkEnablementController`'s `nativeAssetIdentifiers`. This lets new chains get correct native-asset pricing without a `SPOT_PRICES_SUPPORT_INFO` release. `TokenRatesController` keeps the cache current as `ConfigRegistryController` polls for updates. - **BREAKING:** `DeFiPositionsControllerV2.fetchDeFiPositions` now polls while any selected account has `processingDefiPositions: true`, updating state only when every account is ready, invalidating the balances cache between attempts, sharing one in-flight promise per selected-account + `vsCurrency` key (so fast switches can join an earlier matching poll), and stopping on request failure or the max attempt limit ([#9711](https://github.com/MetaMask/core/pull/9711)) - Clients must allow and delegate `RemoteFeatureFlagController:getState` on the `DeFiPositionsControllerV2` messenger. From cb747e1df3bee173c047c492033706d4fbf0e802 Mon Sep 17 00:00:00 2001 From: Maxime OUAIRY Date: Wed, 5 Aug 2026 11:20:55 +0200 Subject: [PATCH 3/6] chore: update README content and yarn.lock - Regenerate README.md via yarn readme-content:update (was failing the readme-content:check lint job). - Add the missing @metamask/config-registry-controller entry to yarn.lock for assets-controllers, which was never regenerated after the dependency was added to package.json. Co-Authored-By: Claude Sonnet 5 --- README.md | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index ed7f97862c6..e9031557fcf 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,7 @@ linkStyle default opacity:0.5 assets_controllers --> accounts_controller; assets_controllers --> approval_controller; assets_controllers --> base_controller; + assets_controllers --> config_registry_controller; assets_controllers --> controller_utils; assets_controllers --> core_backend; assets_controllers --> keyring_controller; diff --git a/yarn.lock b/yarn.lock index 15430524cc1..10666b93861 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6045,6 +6045,7 @@ __metadata: "@metamask/approval-controller": "npm:^9.0.2" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" + "@metamask/config-registry-controller": "npm:^2.0.1" "@metamask/contract-metadata": "npm:^2.4.0" "@metamask/controller-utils": "npm:^12.3.0" "@metamask/core-backend": "npm:^8.1.1" From 7ca17c683c4d743d18dda515a627fb3c17dde417 Mon Sep 17 00:00:00 2001 From: Maxime OUAIRY Date: Wed, 5 Aug 2026 11:41:47 +0200 Subject: [PATCH 4/6] refactor(assets-controllers): seed config registry cache per-chain instead of bulk getState Use ConfigRegistryController's direct getNetworkConfigByCaip2ChainId action instead of getState, called per chain right before TokenRatesController prices that chain's assets: - Avoids reaching into ConfigRegistryController's raw state shape (configs.networks) from outside the controller, going through its published per-chain lookup instead. - The cache only ever holds chains that have actually been resolved, never a bulk snapshot of the entire (possibly large) network map. - Always fresh on use, so the ConfigRegistryController:stateChanged subscription is no longer needed. setNetworkConfigs (bulk) is replaced by setNetworkConfig (single chain, upserts or clears one entry) in codefi-v2.ts. Co-Authored-By: Claude Sonnet 5 --- packages/assets-controllers/CHANGELOG.md | 4 +- .../src/TokenRatesController.test.ts | 142 ++++++++---------- .../src/TokenRatesController.ts | 63 ++++---- .../token-prices-service/codefi-v2.test.ts | 42 +++--- .../src/token-prices-service/codefi-v2.ts | 43 ++++-- .../src/token-prices-service/index.test.ts | 2 +- .../src/token-prices-service/index.ts | 2 +- 7 files changed, 147 insertions(+), 151 deletions(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 7248b67a4b6..b8e6db79ae0 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -13,8 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **BREAKING:** `TokenRatesControllerMessenger` now requires the `ConfigRegistryController:getState` action and `ConfigRegistryController:stateChanged` event to be delegated ([#9789](https://github.com/MetaMask/core/pull/9789)) - - `getAssetId`/`CodefiTokenPricesServiceV2` now resolve native asset CAIP-19 IDs from the config registry's `assets.native.assetId` before falling back to the hardcoded `SPOT_PRICES_SUPPORT_INFO` map, then to `NetworkEnablementController`'s `nativeAssetIdentifiers`. This lets new chains get correct native-asset pricing without a `SPOT_PRICES_SUPPORT_INFO` release. `TokenRatesController` keeps the cache current as `ConfigRegistryController` polls for updates. +- **BREAKING:** `TokenRatesControllerMessenger` now requires the `ConfigRegistryController:getNetworkConfigByCaip2ChainId` action to be delegated ([#9789](https://github.com/MetaMask/core/pull/9789)) + - `getAssetId`/`CodefiTokenPricesServiceV2` now resolve native asset CAIP-19 IDs from the config registry's `assets.native.assetId` before falling back to the hardcoded `SPOT_PRICES_SUPPORT_INFO` map, then to `NetworkEnablementController`'s `nativeAssetIdentifiers`. This lets new chains get correct native-asset pricing without a `SPOT_PRICES_SUPPORT_INFO` release. `TokenRatesController` seeds this per chain, right before pricing that chain's assets, via `ConfigRegistryController`'s per-chain lookup action, rather than mirroring its entire network map. - **BREAKING:** `DeFiPositionsControllerV2.fetchDeFiPositions` now polls while any selected account has `processingDefiPositions: true`, updating state only when every account is ready, invalidating the balances cache between attempts, sharing one in-flight promise per selected-account + `vsCurrency` key (so fast switches can join an earlier matching poll), and stopping on request failure or the max attempt limit ([#9711](https://github.com/MetaMask/core/pull/9711)) - Clients must allow and delegate `RemoteFeatureFlagController:getState` on the `DeFiPositionsControllerV2` messenger. - Bump `@metamask/transaction-controller` from `^69.4.0` to `^69.5.0` ([#9780](https://github.com/MetaMask/core/pull/9780)) diff --git a/packages/assets-controllers/src/TokenRatesController.test.ts b/packages/assets-controllers/src/TokenRatesController.test.ts index a5f44adf509..b6a8981de2d 100644 --- a/packages/assets-controllers/src/TokenRatesController.test.ts +++ b/packages/assets-controllers/src/TokenRatesController.test.ts @@ -1,5 +1,5 @@ import { deriveStateFromMetadata } from '@metamask/base-controller'; -import type { ConfigRegistryControllerState } from '@metamask/config-registry-controller'; +import type { RegistryNetworkConfig } from '@metamask/config-registry-controller'; import { ChainId, toChecksumHexAddress } from '@metamask/controller-utils'; import { Messenger, MOCK_ANY_NAMESPACE } from '@metamask/messenger'; import type { @@ -80,13 +80,9 @@ function buildTokenRatesControllerMessenger( 'TokensController:getState', 'NetworkController:getState', 'NetworkEnablementController:getState', - 'ConfigRegistryController:getState', - ], - events: [ - 'TokensController:stateChange', - 'NetworkController:stateChange', - 'ConfigRegistryController:stateChanged', + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', ], + events: ['TokensController:stateChange', 'NetworkController:stateChange'], }); return tokenRatesControllerMessenger; } @@ -150,55 +146,6 @@ describe('TokenRatesController', () => { ); }); - it('seeds the config registry cache used by getAssetId at construction', async () => { - await withController( - { - mockConfigRegistryState: { - configs: { - networks: { - 'eip155:1': { - assets: { native: { assetId: 'eip155:1/slip44:61' } }, - }, - }, - }, - }, - }, - async () => { - // getAssetId is the function extracted for client parity with - // fetchTokenPrices; it must reflect the same registry data the - // controller just seeded, with no params beyond chain/token. - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:61'); - }, - ); - }); - - it('keeps the config registry cache current as ConfigRegistryController state changes', async () => { - await withController(async ({ triggerConfigRegistryStateChange }) => { - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:60'); // falls back to SPOT_PRICES_SUPPORT_INFO - - triggerConfigRegistryStateChange({ - configs: { - networks: { - 'eip155:1': { - assets: { native: { assetId: 'eip155:1/slip44:61' } }, - }, - }, - }, - version: null, - lastFetched: null, - etag: null, - } as ConfigRegistryControllerState); - - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:61'); - }); - }); - it('clears persisted marketData at construction when isDeprecated() returns true', async () => { const initialMarketData = { '0x1': { @@ -298,6 +245,50 @@ describe('TokenRatesController', () => { ); }); + it('seeds the config registry cache used by getAssetId for the chains being priced', async () => { + const mockGetNetworkConfigByCaip2ChainId = jest + .fn() + .mockImplementation((caipChainId: string) => + caipChainId === 'eip155:1' + ? { assets: { native: { assetId: 'eip155:1/slip44:61' } } } + : undefined, + ); + + await withController( + { mockGetNetworkConfigByCaip2ChainId }, + async ({ controller }) => { + await controller.updateExchangeRates([ + { chainId: '0x1', nativeCurrency: 'ETH' }, + ]); + + expect(mockGetNetworkConfigByCaip2ChainId).toHaveBeenCalledWith( + 'eip155:1', + ); + // getAssetId is the function extracted for client parity with + // fetchTokenPrices; it must reflect the same registry data + // updateExchangeRates just seeded, with no params beyond chain/token. + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:61'); + }, + ); + }); + + it('does not seed the cache for chains outside the current batch', async () => { + await withController(async ({ controller }) => { + await controller.updateExchangeRates([ + { chainId: '0x89', nativeCurrency: 'MATIC' }, + ]); + + // 0x1 was never part of a priced batch, so it falls back to the + // hardcoded SPOT_PRICES_SUPPORT_INFO entry rather than picking up + // stale or unrelated registry data. + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:60'); + }); + }); + it('clears stale marketData when isDeprecated toggles to true at runtime', async () => { const tokenPricesService = buildMockTokenPricesService(); jest.spyOn(tokenPricesService, 'fetchTokenPrices'); @@ -1510,15 +1501,10 @@ type WithControllerCallback = ({ controller, triggerTokensStateChange, triggerNetworkStateChange, - triggerConfigRegistryStateChange, }: { controller: TokenRatesController; triggerTokensStateChange: (state: TokensControllerState) => void; triggerNetworkStateChange: (state: NetworkState, patches?: Patch[]) => void; - triggerConfigRegistryStateChange: ( - state: ConfigRegistryControllerState, - patches?: Patch[], - ) => void; }) => Promise | ReturnValue; type WithControllerOptions = { @@ -1529,7 +1515,9 @@ type WithControllerOptions = { >; mockTokensControllerState?: Partial; mockNetworkState?: Partial; - mockConfigRegistryState?: Partial; + mockGetNetworkConfigByCaip2ChainId?: ( + caipChainId: string, + ) => RegistryNetworkConfig | undefined; }; type WithControllerArgs = @@ -1553,7 +1541,7 @@ async function withController( options, mockTokensControllerState, mockNetworkState, - mockConfigRegistryState, + mockGetNetworkConfigByCaip2ChainId, } = rest; const messenger: RootMessenger = new Messenger({ namespace: MOCK_ANY_NAMESPACE, @@ -1589,16 +1577,16 @@ async function withController( }), ); - // Register ConfigRegistryController:getState handler + // Register ConfigRegistryController:getNetworkConfigByCaip2ChainId handler + const defaultGetNetworkConfigByCaip2ChainId = (): undefined => undefined; messenger.registerActionHandler( - 'ConfigRegistryController:getState', - jest.fn().mockReturnValue({ - configs: { networks: {} }, - version: null, - lastFetched: null, - etag: null, - ...mockConfigRegistryState, - }), + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', + jest + .fn() + .mockImplementation( + mockGetNetworkConfigByCaip2ChainId ?? + defaultGetNetworkConfigByCaip2ChainId, + ), ); const controller = new TokenRatesController({ @@ -1618,16 +1606,6 @@ async function withController( ) => { messenger.publish('NetworkController:stateChange', state, patches); }, - triggerConfigRegistryStateChange: ( - state: ConfigRegistryControllerState, - patches: Patch[] = [], - ) => { - messenger.publish( - 'ConfigRegistryController:stateChanged', - state, - patches, - ); - }, }); } finally { controller.stopAllPolling(); diff --git a/packages/assets-controllers/src/TokenRatesController.ts b/packages/assets-controllers/src/TokenRatesController.ts index 1bd9d5bcd50..9c63d8db219 100644 --- a/packages/assets-controllers/src/TokenRatesController.ts +++ b/packages/assets-controllers/src/TokenRatesController.ts @@ -3,10 +3,7 @@ import type { ControllerStateChangeEvent, StateMetadata, } from '@metamask/base-controller'; -import type { - ConfigRegistryControllerEvents, - ConfigRegistryControllerGetStateAction, -} from '@metamask/config-registry-controller'; +import type { ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction } from '@metamask/config-registry-controller'; import { toChecksumHexAddress } from '@metamask/controller-utils'; import type { Messenger } from '@metamask/messenger'; import type { @@ -16,6 +13,7 @@ import type { import type { NetworkEnablementControllerGetStateAction } from '@metamask/network-enablement-controller'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; import type { Hex } from '@metamask/utils'; +import { hexToNumber, KnownCaipNamespace, toCaipChainId } from '@metamask/utils'; import { isEqual } from 'lodash'; import { @@ -23,7 +21,7 @@ import { TOKEN_PRICES_BATCH_SIZE, } from './assetsUtil.js'; import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service.js'; -import { getNativeTokenAddress, setNetworkConfigs } from './token-prices-service/codefi-v2.js'; +import { getNativeTokenAddress, setNetworkConfig } from './token-prices-service/codefi-v2.js'; import { TokenRwaData } from './token-service.js'; import type { TokensControllerGetStateAction, @@ -103,15 +101,14 @@ export type AllowedActions = | TokensControllerGetStateAction | NetworkControllerGetStateAction | NetworkEnablementControllerGetStateAction - | ConfigRegistryControllerGetStateAction; + | ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction; /** * The external events available to the {@link TokenRatesController}. */ export type AllowedEvents = | TokensControllerStateChangeEvent - | NetworkControllerStateChangeEvent - | ConfigRegistryControllerEvents; + | NetworkControllerStateChangeEvent; /** * The name of the {@link TokenRatesController}. @@ -263,11 +260,6 @@ export class TokenRatesController extends StaticIntervalPollingController { - setNetworkConfigs(state.configs.networks); - }, - ); + #seedNetworkConfigs(chainIds: Iterable): void { + for (const chainId of new Set(chainIds)) { + const caipChainId = toCaipChainId( + KnownCaipNamespace.Eip155, + hexToNumber(chainId).toString(), + ); + setNetworkConfig( + caipChainId, + this.messenger.call( + 'ConfigRegistryController:getNetworkConfigByCaip2ChainId', + caipChainId, + ), + ); + } } /** @@ -482,6 +477,10 @@ export class TokenRatesController extends StaticIntervalPollingController chainId), + ); + const marketData: Record> = {}; const assetsByNativeCurrency: Record< string, diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts index 414ddecf9ff..65da8f8578e 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts @@ -17,7 +17,7 @@ import { getSupportedNetworks, resetSupportedNetworksCache, getAssetId, - setNetworkConfigs, + setNetworkConfig, resetNetworkConfigsCache, } from './codefi-v2.js'; @@ -2147,7 +2147,7 @@ describe('CodefiTokenPricesServiceV2', () => { }); }); - describe('setNetworkConfigs', () => { + describe('setNetworkConfig', () => { afterEach(() => { resetSupportedNetworksCache(); resetNetworkConfigsCache(); @@ -2197,11 +2197,9 @@ describe('CodefiTokenPricesServiceV2', () => { }, }); - setNetworkConfigs({ - 'eip155:1': { - assets: { native: { assetId: registryAssetId } }, - } as RegistryNetworkConfig, - }); + setNetworkConfig('eip155:1', { + assets: { native: { assetId: registryAssetId } }, + } as RegistryNetworkConfig); const service = new CodefiTokenPricesServiceV2(); @@ -2315,11 +2313,9 @@ describe('CodefiTokenPricesServiceV2', () => { }); it('prefers the config registry entry over the hardcoded entry', () => { - setNetworkConfigs({ - 'eip155:1': { - assets: { native: { assetId: 'eip155:1/slip44:61' } }, - } as RegistryNetworkConfig, - }); + setNetworkConfig('eip155:1', { + assets: { native: { assetId: 'eip155:1/slip44:61' } }, + } as RegistryNetworkConfig); expect( getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), @@ -2328,11 +2324,9 @@ describe('CodefiTokenPricesServiceV2', () => { it('prefers the config registry entry over nativeAssetIdentifiers when there is no hardcoded entry', () => { // 0x42 (OKXChain) is not in SPOT_PRICES_SUPPORT_INFO - setNetworkConfigs({ - 'eip155:66': { - assets: { native: { assetId: 'eip155:66/erc20:0xregistry' } }, - } as RegistryNetworkConfig, - }); + setNetworkConfig('eip155:66', { + assets: { native: { assetId: 'eip155:66/erc20:0xregistry' } }, + } as RegistryNetworkConfig); expect( getAssetId({ @@ -2344,8 +2338,20 @@ describe('CodefiTokenPricesServiceV2', () => { }); it('falls back to the hardcoded entry when the config registry has no entry for the chain', () => { - setNetworkConfigs({}); + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:60'); + }); + + it('clears a chain from the config registry cache when set to undefined', () => { + setNetworkConfig('eip155:1', { + assets: { native: { assetId: 'eip155:1/slip44:61' } }, + } as RegistryNetworkConfig); + expect( + getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), + ).toBe('eip155:1/slip44:61'); + setNetworkConfig('eip155:1', undefined); expect( getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), ).toBe('eip155:1/slip44:60'); diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts index 6ab01052215..897070a5dd7 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts @@ -573,8 +573,12 @@ export function resetSupportedCurrenciesCache(): void { /** * In-memory cache of the config registry's network configurations, keyed by - * CAIP-2 chain ID. Populated via {@link setNetworkConfigs}, typically by - * TokenRatesController from ConfigRegistryController's state. + * CAIP-2 chain ID. Populated per-chain via {@link setNetworkConfig}, typically + * by TokenRatesController using ConfigRegistryController's + * `getNetworkConfigByCaip2ChainId` action for whichever chains are about to + * be priced — so the cache never holds more than what's actually been + * resolved, and always reflects the registry's current state (no bulk + * snapshot to go stale between polls). * * This is a module-level cache (like {@link lastFetchedSupportedNetworks}) * rather than instance state so that {@link getAssetId} gives the same @@ -582,21 +586,30 @@ export function resetSupportedCurrenciesCache(): void { * directly by external callers — there is only one source of truth for a * given chain's native asset ID, not one per service instance. */ -let cachedNetworkConfigs: Record | null = - null; +let cachedNetworkConfigs: Record = {}; /** - * Updates the config registry network configurations used by - * {@link getAssetId} to resolve native asset CAIP-19 IDs. Should be called - * with ConfigRegistryController's `state.configs.networks`, ideally on every - * state change so lookups stay current as the registry is polled. + * Updates the config registry network configuration used by + * {@link getAssetId} to resolve a chain's native asset CAIP-19 ID. Should be + * called with the result of ConfigRegistryController's + * `getNetworkConfigByCaip2ChainId` action, typically right before pricing + * that chain's assets, so the cache stays current without needing to mirror + * the registry's entire network map. * - * @param networks - Network configurations keyed by CAIP-2 chain ID. + * @param caipChainId - The CAIP-2 chain ID the config belongs to. + * @param networkConfig - The registry's network configuration for this + * chain, or undefined if the registry has no entry for it (clears any + * previously cached entry). */ -export function setNetworkConfigs( - networks: Record, +export function setNetworkConfig( + caipChainId: CaipChainId, + networkConfig: RegistryNetworkConfig | undefined, ): void { - cachedNetworkConfigs = networks; + if (networkConfig) { + cachedNetworkConfigs[caipChainId] = networkConfig; + } else { + delete cachedNetworkConfigs[caipChainId]; + } } /** @@ -604,7 +617,7 @@ export function setNetworkConfigs( * This is primarily intended for testing purposes. */ export function resetNetworkConfigsCache(): void { - cachedNetworkConfigs = null; + cachedNetworkConfigs = {}; } /** @@ -612,7 +625,7 @@ export function resetNetworkConfigsCache(): void { * given chain. * * For native tokens, prefers the CAIP-19 ID from the config registry cache - * (`assets.native.assetId`, see {@link setNetworkConfigs}) when available, + * (`assets.native.assetId`, see {@link setNetworkConfig}) when available, * then the hardcoded {@link SPOT_PRICES_SUPPORT_INFO} entry, and finally * falls back to the provided native asset identifiers (sourced from * NetworkEnablementController). For ERC20 tokens, constructs the CAIP-19 ID @@ -645,7 +658,7 @@ export function getAssetId({ nativeAddress.toLowerCase() === tokenAddress.toLowerCase(); if (isNativeToken) { - const registryAssetId = cachedNetworkConfigs?.[caipChainId]?.assets + const registryAssetId = cachedNetworkConfigs[caipChainId]?.assets ?.native?.assetId as CaipAssetType | undefined; const hardcodedId = ( SPOT_PRICES_SUPPORT_INFO as Partial> 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 8067e5c1503..c9d626e2d61 100644 --- a/packages/assets-controllers/src/token-prices-service/index.test.ts +++ b/packages/assets-controllers/src/token-prices-service/index.test.ts @@ -12,7 +12,7 @@ describe('token-prices-service', () => { "resetSupportedNetworksCache", "SPOT_PRICES_SUPPORT_INFO", "getAssetId", - "setNetworkConfigs", + "setNetworkConfig", "resetNetworkConfigsCache", ] `); diff --git a/packages/assets-controllers/src/token-prices-service/index.ts b/packages/assets-controllers/src/token-prices-service/index.ts index c438f06f830..5853e08fe9b 100644 --- a/packages/assets-controllers/src/token-prices-service/index.ts +++ b/packages/assets-controllers/src/token-prices-service/index.ts @@ -11,6 +11,6 @@ export { resetSupportedNetworksCache, SPOT_PRICES_SUPPORT_INFO, getAssetId, - setNetworkConfigs, + setNetworkConfig, resetNetworkConfigsCache, } from './codefi-v2.js'; From 97bd4060b2d6fce3235fbe933489978dff12f246 Mon Sep 17 00:00:00 2001 From: Maxime OUAIRY Date: Wed, 5 Aug 2026 11:45:28 +0200 Subject: [PATCH 5/6] style(assets-controllers): fix formatting (yarn lint:misc) Co-Authored-By: Claude Sonnet 5 --- .../src/TokenRatesController.test.ts | 6 ++--- .../src/TokenRatesController.ts | 11 +++++++-- .../token-prices-service/codefi-v2.test.ts | 24 +++++++++---------- .../src/token-prices-service/codefi-v2.ts | 6 ++--- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/assets-controllers/src/TokenRatesController.test.ts b/packages/assets-controllers/src/TokenRatesController.test.ts index b6a8981de2d..6a0652ef5f1 100644 --- a/packages/assets-controllers/src/TokenRatesController.test.ts +++ b/packages/assets-controllers/src/TokenRatesController.test.ts @@ -283,9 +283,9 @@ describe('TokenRatesController', () => { // 0x1 was never part of a priced batch, so it falls back to the // hardcoded SPOT_PRICES_SUPPORT_INFO entry rather than picking up // stale or unrelated registry data. - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:60'); + expect(getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS })).toBe( + 'eip155:1/slip44:60', + ); }); }); diff --git a/packages/assets-controllers/src/TokenRatesController.ts b/packages/assets-controllers/src/TokenRatesController.ts index 9c63d8db219..4faf7b2e09c 100644 --- a/packages/assets-controllers/src/TokenRatesController.ts +++ b/packages/assets-controllers/src/TokenRatesController.ts @@ -13,7 +13,11 @@ import type { import type { NetworkEnablementControllerGetStateAction } from '@metamask/network-enablement-controller'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; import type { Hex } from '@metamask/utils'; -import { hexToNumber, KnownCaipNamespace, toCaipChainId } from '@metamask/utils'; +import { + hexToNumber, + KnownCaipNamespace, + toCaipChainId, +} from '@metamask/utils'; import { isEqual } from 'lodash'; import { @@ -21,7 +25,10 @@ import { TOKEN_PRICES_BATCH_SIZE, } from './assetsUtil.js'; import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service.js'; -import { getNativeTokenAddress, setNetworkConfig } from './token-prices-service/codefi-v2.js'; +import { + getNativeTokenAddress, + setNetworkConfig, +} from './token-prices-service/codefi-v2.js'; import { TokenRwaData } from './token-service.js'; import type { TokensControllerGetStateAction, diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts index 65da8f8578e..629a9e08a50 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts @@ -2317,9 +2317,9 @@ describe('CodefiTokenPricesServiceV2', () => { assets: { native: { assetId: 'eip155:1/slip44:61' } }, } as RegistryNetworkConfig); - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:61'); + expect(getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS })).toBe( + 'eip155:1/slip44:61', + ); }); it('prefers the config registry entry over nativeAssetIdentifiers when there is no hardcoded entry', () => { @@ -2338,23 +2338,23 @@ describe('CodefiTokenPricesServiceV2', () => { }); it('falls back to the hardcoded entry when the config registry has no entry for the chain', () => { - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:60'); + expect(getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS })).toBe( + 'eip155:1/slip44:60', + ); }); it('clears a chain from the config registry cache when set to undefined', () => { setNetworkConfig('eip155:1', { assets: { native: { assetId: 'eip155:1/slip44:61' } }, } as RegistryNetworkConfig); - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:61'); + expect(getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS })).toBe( + 'eip155:1/slip44:61', + ); setNetworkConfig('eip155:1', undefined); - expect( - getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS }), - ).toBe('eip155:1/slip44:60'); + expect(getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS })).toBe( + 'eip155:1/slip44:60', + ); }); it('returns undefined for a native token with no hardcoded entry and no identifier', () => { diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts index 897070a5dd7..2cbe05899b4 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts @@ -1,3 +1,4 @@ +import type { RegistryNetworkConfig } from '@metamask/config-registry-controller'; import { createServicePolicy, DEFAULT_CIRCUIT_BREAK_DURATION, @@ -6,7 +7,6 @@ import { DEFAULT_MAX_RETRIES, handleFetch, } from '@metamask/controller-utils'; -import type { RegistryNetworkConfig } from '@metamask/config-registry-controller'; import type { ServicePolicy } from '@metamask/controller-utils'; import type { CaipAssetType, CaipChainId, Hex } from '@metamask/utils'; import { @@ -658,8 +658,8 @@ export function getAssetId({ nativeAddress.toLowerCase() === tokenAddress.toLowerCase(); if (isNativeToken) { - const registryAssetId = cachedNetworkConfigs[caipChainId]?.assets - ?.native?.assetId as CaipAssetType | undefined; + const registryAssetId = cachedNetworkConfigs[caipChainId]?.assets?.native + ?.assetId as CaipAssetType | undefined; const hardcodedId = ( SPOT_PRICES_SUPPORT_INFO as Partial> )[chainId]; From ff7a51190a8df8905d66e09889c2779b693053e2 Mon Sep 17 00:00:00 2001 From: Maxime OUAIRY Date: Wed, 5 Aug 2026 11:55:36 +0200 Subject: [PATCH 6/6] chore(assets-controllers): add config-registry-controller tsconfig project reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes lint:tsconfigs:all — tsconfig.json/tsconfig.build.json references must mirror package.json dependencies, and the config-registry-controller dependency added earlier was missing its reference. Co-Authored-By: Claude Sonnet 5 --- packages/assets-controllers/tsconfig.build.json | 3 +++ packages/assets-controllers/tsconfig.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/assets-controllers/tsconfig.build.json b/packages/assets-controllers/tsconfig.build.json index 6e3978c1457..9df9b96ffba 100644 --- a/packages/assets-controllers/tsconfig.build.json +++ b/packages/assets-controllers/tsconfig.build.json @@ -62,6 +62,9 @@ }, { "path": "../remote-feature-flag-controller/tsconfig.build.json" + }, + { + "path": "../config-registry-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"], diff --git a/packages/assets-controllers/tsconfig.json b/packages/assets-controllers/tsconfig.json index 7a5d296a792..711ab71e8a9 100644 --- a/packages/assets-controllers/tsconfig.json +++ b/packages/assets-controllers/tsconfig.json @@ -61,6 +61,9 @@ }, { "path": "../remote-feature-flag-controller" + }, + { + "path": "../config-registry-controller" } ], "include": ["../../types", "./src", "../../tests"]