diff --git a/app/core/Engine/Engine.ts b/app/core/Engine/Engine.ts index cde53327a7ec..ba912aa445ca 100644 --- a/app/core/Engine/Engine.ts +++ b/app/core/Engine/Engine.ts @@ -9,7 +9,10 @@ import { NativeEventSubscription, } from 'react-native'; ///: END:ONLY_INCLUDE_IF -import { CodefiTokenPricesServiceV2 } from '@metamask/assets-controllers'; +import { + CodefiTokenPricesServiceV2, + TokenListService, +} from '@metamask/assets-controllers'; import { AccountsController } from '@metamask/accounts-controller'; import { KeyringController, @@ -273,6 +276,12 @@ export class Engine { CaveatSpecificationConstraint >; + /** + * Shared token list service. Stored so destroyEngineInstance can call + * destroy() to abort in-flight requests and clear the TanStack Query cache. + */ + private tokenListService: TokenListService; + /** * Creates a CoreController instance */ @@ -287,6 +296,8 @@ export class Engine { this.controllerMessenger = getRootExtendedMessenger(); const codefiTokenApiV2 = new CodefiTokenPricesServiceV2(); + const tokenListService = new TokenListService(); + this.tokenListService = tokenListService; const initRequest = { getState: () => store.getState(), @@ -298,6 +309,7 @@ export class Engine { initialKeyringState: keyringState, qrKeyringScanner: this.qrKeyringScanner, codefiTokenApiV2, + tokenListService, }; const { messengerClientsByName } = initMessengerClients({ initFunctions: { @@ -1221,6 +1233,7 @@ export class Engine { controller.destroy(); } }); + this.tokenListService.destroy(); this.removeAllListeners(); await this.resetState(); diff --git a/app/core/Engine/controllers/token-detection-controller-init.test.ts b/app/core/Engine/controllers/token-detection-controller-init.test.ts index 1f9ddb68d2d6..61afbcaeec77 100644 --- a/app/core/Engine/controllers/token-detection-controller-init.test.ts +++ b/app/core/Engine/controllers/token-detection-controller-init.test.ts @@ -48,6 +48,7 @@ describe('TokenDetectionControllerInit', () => { messenger: expect.any(Object), disabled: false, getBalancesInSingleCall: expect.any(Function), + tokenListService: expect.any(Object), useTokenDetection: expect.any(Function), useExternalServices: expect.any(Function), trackMetaMetricsEvent: expect.any(Function), diff --git a/app/core/Engine/controllers/token-detection-controller-init.ts b/app/core/Engine/controllers/token-detection-controller-init.ts index e77d4ce7f9c5..defd161a4c52 100644 --- a/app/core/Engine/controllers/token-detection-controller-init.ts +++ b/app/core/Engine/controllers/token-detection-controller-init.ts @@ -23,7 +23,13 @@ export const tokenDetectionControllerInit: MessengerClientInitFunction< TokenDetectionController, TokenDetectionControllerMessenger, TokenDetectionControllerInitMessenger -> = ({ controllerMessenger, initMessenger, getMessengerClient, getState }) => { +> = ({ + controllerMessenger, + initMessenger, + getMessengerClient, + getState, + tokenListService, +}) => { const networkController = getMessengerClient('NetworkController'); const getBalancesInSingleCall = ( @@ -44,6 +50,7 @@ export const tokenDetectionControllerInit: MessengerClientInitFunction< messenger: controllerMessenger, disabled: false, getBalancesInSingleCall, + tokenListService, useTokenDetection: () => selectUseTokenDetection(getState()), useExternalServices: () => selectBasicFunctionalityEnabled(getState()), trackMetaMetricsEvent: () => { diff --git a/app/core/Engine/controllers/tokens-controller-init.test.ts b/app/core/Engine/controllers/tokens-controller-init.test.ts index f60280e9ce94..91e635d34dbd 100644 --- a/app/core/Engine/controllers/tokens-controller-init.test.ts +++ b/app/core/Engine/controllers/tokens-controller-init.test.ts @@ -84,6 +84,7 @@ describe('tokensControllerInit', () => { state: undefined, chainId: '0x1', provider: {}, + tokenListService: expect.any(Object), }); }); }); diff --git a/app/core/Engine/controllers/tokens-controller-init.ts b/app/core/Engine/controllers/tokens-controller-init.ts index d86ebcc397a1..47d7126c392e 100644 --- a/app/core/Engine/controllers/tokens-controller-init.ts +++ b/app/core/Engine/controllers/tokens-controller-init.ts @@ -23,6 +23,7 @@ export const tokensControllerInit: MessengerClientInitFunction< initMessenger, persistedState, getMessengerClient, + tokenListService, }) => { const networkController = getMessengerClient('NetworkController'); const { provider } = @@ -35,6 +36,7 @@ export const tokensControllerInit: MessengerClientInitFunction< messenger: controllerMessenger, chainId: getGlobalChainId(networkController), provider, + tokenListService, }); return { diff --git a/app/core/Engine/messengers/token-detection-controller-messenger.ts b/app/core/Engine/messengers/token-detection-controller-messenger.ts index 0df3ddf2eccc..2a5bd171c3f5 100644 --- a/app/core/Engine/messengers/token-detection-controller-messenger.ts +++ b/app/core/Engine/messengers/token-detection-controller-messenger.ts @@ -39,7 +39,6 @@ export function getTokenDetectionControllerMessenger( 'NetworkController:getState', 'TokensController:getState', 'TokensController:addDetectedTokens', - 'TokenListController:getState', 'PreferencesController:getState', 'TokensController:addTokens', 'NetworkController:findNetworkClientIdByChainId', @@ -49,7 +48,6 @@ export function getTokenDetectionControllerMessenger( 'KeyringController:lock', 'KeyringController:unlock', 'NetworkController:networkDidChange', - 'TokenListController:stateChange', 'PreferencesController:stateChange', 'TransactionController:transactionConfirmed', ], diff --git a/app/core/Engine/messengers/tokens-controller-messenger.ts b/app/core/Engine/messengers/tokens-controller-messenger.ts index d68e9b50e7f7..af935b13f985 100644 --- a/app/core/Engine/messengers/tokens-controller-messenger.ts +++ b/app/core/Engine/messengers/tokens-controller-messenger.ts @@ -38,7 +38,6 @@ export function getTokensControllerMessenger( events: [ 'NetworkController:networkDidChange', 'NetworkController:stateChange', - 'TokenListController:stateChange', 'AccountsController:selectedEvmAccountChange', 'KeyringController:accountRemoved', ], diff --git a/app/core/Engine/types.ts b/app/core/Engine/types.ts index 005899e80b19..39a8a4545a7b 100644 --- a/app/core/Engine/types.ts +++ b/app/core/Engine/types.ts @@ -25,6 +25,7 @@ import { TokenListControllerActions, TokenListControllerEvents, TokenListState, + TokenListService, TokensController, TokensControllerActions, TokensControllerEvents, @@ -1070,6 +1071,14 @@ export type MessengerClientInitRequest< */ codefiTokenApiV2: CodefiTokenPricesServiceV2; + /** + * Shared token list service instance. + * Owns a TanStack Query cache (4-hour stale time) so that both + * TokenDetectionController and TokensController share the same in-memory + * cache without redundant network requests. + */ + tokenListService: TokenListService; + /** * Controller messenger for the client. * Used to generate controller for each controller. @@ -1175,6 +1184,7 @@ export interface InitMessengerClientsFunctionRequest { initialKeyringState?: KeyringControllerState | null; qrKeyringScanner: QrKeyringDeferredPromiseBridge; codefiTokenApiV2: CodefiTokenPricesServiceV2; + tokenListService: TokenListService; ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) removeAccount: (address: string) => Promise; ///: END:ONLY_INCLUDE_IF diff --git a/app/core/Engine/utils/test-utils.ts b/app/core/Engine/utils/test-utils.ts index 5a0934117aba..8e1cc08da3be 100644 --- a/app/core/Engine/utils/test-utils.ts +++ b/app/core/Engine/utils/test-utils.ts @@ -7,7 +7,10 @@ import { MessengerClientInitRequest, } from '../types'; import { QrKeyringDeferredPromiseBridge } from '@metamask/eth-qr-keyring'; -import { CodefiTokenPricesServiceV2 } from '@metamask/assets-controllers'; +import { + CodefiTokenPricesServiceV2, + TokenListService, +} from '@metamask/assets-controllers'; /** * Build a mock for the MessengerClientInitRequest. @@ -20,6 +23,10 @@ export function buildMessengerClientInitRequestMock( ): jest.Mocked> { return { codefiTokenApiV2: jest.fn() as unknown as CodefiTokenPricesServiceV2, + tokenListService: { + fetchTokensByChainId: jest.fn(), + destroy: jest.fn(), + } as unknown as TokenListService, controllerMessenger: controllerMessenger as unknown as ControllerMessenger, getMessengerClient: jest.fn(), getGlobalChainId: jest.fn(), diff --git a/package.json b/package.json index bb478c6feabf..443acdbcca45 100644 --- a/package.json +++ b/package.json @@ -244,7 +244,7 @@ "@metamask/app-metadata-controller": "^2.0.0", "@metamask/approval-controller": "^9.0.0", "@metamask/assets-controller": "^6.2.1", - "@metamask/assets-controllers": "^105.0.0", + "@metamask/assets-controllers": "^106.0.0", "@metamask/authenticated-user-storage": "^1.0.0", "@metamask/base-controller": "^9.0.1", "@metamask/bitcoin-wallet-snap": "^1.10.1", diff --git a/yarn.lock b/yarn.lock index c969acab937d..0264c2239a60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35241,7 +35241,7 @@ __metadata: "@metamask/app-metadata-controller": "npm:^2.0.0" "@metamask/approval-controller": "npm:^9.0.0" "@metamask/assets-controller": "npm:^6.2.1" - "@metamask/assets-controllers": "npm:^105.0.0" + "@metamask/assets-controllers": "npm:^106.0.0" "@metamask/authenticated-user-storage": "npm:^1.0.0" "@metamask/auto-changelog": "npm:^5.3.0" "@metamask/base-controller": "npm:^9.0.1"