From 252ff9217e30b7f9fc7e4890f0fa36280be8ba60 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Thu, 3 Jul 2025 11:28:07 +0200 Subject: [PATCH 01/10] feat: add rate limiting support to controllers leveraging the accounts API --- packages/assets-controllers/CHANGELOG.md | 6 + packages/assets-controllers/package.json | 1 + .../src/TokenDetectionController.test.ts | 6 +- .../src/TokenDetectionController.ts | 95 ++++++++++---- .../multi-chain-accounts.test.ts | 81 +++++++++++- .../multi-chain-accounts.ts | 30 ++++- .../assets-controllers/tsconfig.build.json | 3 +- packages/assets-controllers/tsconfig.json | 3 +- .../CHANGELOG.md | 6 + .../package.json | 3 +- .../MultichainNetworkController.test.ts | 48 +++++-- .../MultichainNetworkController.ts | 18 ++- .../MultichainNetworkService.test.ts | 61 +++++++-- .../MultichainNetworkService.ts | 30 ++++- .../src/types.ts | 4 +- .../tsconfig.build.json | 3 +- .../tsconfig.json | 3 +- packages/transaction-controller/CHANGELOG.md | 6 + packages/transaction-controller/package.json | 1 + .../src/TransactionController.test.ts | 1 + .../src/TransactionController.ts | 23 +++- .../src/api/accounts-api.test.ts | 123 +++++++++++++++--- .../src/api/accounts-api.ts | 23 ++++ ...AccountsApiRemoteTransactionSource.test.ts | 65 +++++---- .../AccountsApiRemoteTransactionSource.ts | 32 ++++- .../tsconfig.build.json | 1 + packages/transaction-controller/tsconfig.json | 1 + yarn.lock | 3 + 28 files changed, 557 insertions(+), 123 deletions(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index c9571dfee6c..f48b9163e9d 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **BREAKING:** Add peer dependency `@metamask/profile-sync-controller` +- **BREAKING:** Add `profileId`-based rate limiting support for Accounts API calls + - Use `AuthenticationController:getBearerToken` in order to get a bearer token that gets attached to every request to the Accounts API + ## [69.0.0] ### Changed diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index d6a8526e392..2d9abf5e983 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -114,6 +114,7 @@ "@metamask/permission-controller": "^11.0.0", "@metamask/phishing-controller": "^12.5.0", "@metamask/preferences-controller": "^18.0.0", + "@metamask/profile-sync-controller": "^19.0.0", "@metamask/providers": "^22.0.0", "@metamask/snaps-controllers": "^12.0.0", "@metamask/transaction-controller": "^58.0.0", diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index 20a82fc6645..3983d9c420f 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -177,6 +177,7 @@ function buildTokenDetectionControllerMessenger( 'TokensController:addDetectedTokens', 'TokenListController:getState', 'PreferencesController:getState', + 'AuthenticationController:getBearerToken', ], allowedEvents: [ 'AccountsController:selectedEvmAccountChange', @@ -2616,10 +2617,10 @@ describe('TokenDetectionController', () => { properties: { tokens: [`${sampleTokenA.symbol} - ${sampleTokenA.address}`], // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/naming-convention + token_standard: 'ERC20', // TODO: Either fix this lint violation or explain why it's necessary to ignore. - // eslint-disable-next-line @typescript-eslint/naming-convention + asset_type: 'TOKEN', }, }); @@ -2759,6 +2760,7 @@ describe('TokenDetectionController', () => { /** * Test Utility - Arrange and Act `detectTokens()` with the Accounts API feature * RPC flow will return `sampleTokenA` and the Accounts API flow will use `sampleTokenB` + * * @param props - options to modify these tests * @param props.overrideMockTokensCache - change the tokens cache * @param props.mockMultiChainAPI - change the Accounts API responses diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index fb144ab806f..85b36c36a1e 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -55,6 +55,7 @@ import type { TokensControllerAddDetectedTokensAction, TokensControllerGetStateAction, } from './TokensController'; +import type { AuthenticationControllerGetBearerToken } from '../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; const DEFAULT_INTERVAL = 180000; @@ -93,6 +94,7 @@ export const STATIC_MAINNET_TOKEN_LIST = Object.entries( /** * Function that takes a TokensChainsCache object and maps chainId with TokenListMap. + * * @param tokensChainsCache - TokensChainsCache input object * @returns returns the map of chainId with TokenListMap */ @@ -129,7 +131,8 @@ export type AllowedActions = | KeyringControllerGetStateAction | PreferencesControllerGetStateAction | TokensControllerGetStateAction - | TokensControllerAddDetectedTokensAction; + | TokensControllerAddDetectedTokensAction + | AuthenticationControllerGetBearerToken; export type TokenDetectionControllerStateChangeEvent = ControllerStateChangeEvent; @@ -162,13 +165,20 @@ type TokenDetectionPollingInput = { /** * Controller that passively polls on a set interval for Tokens auto detection - * @property intervalId - Polling interval used to fetch new token rates - * @property selectedAddress - Vault selected address - * @property networkClientId - The network client ID of the current selected network - * @property disabled - Boolean to track if network requests are blocked - * @property isUnlocked - Boolean to track if the keyring state is unlocked - * @property isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController - * @property isDetectionEnabledForNetwork - Boolean to track if detected is enabled for current network + * + * intervalId - Polling interval used to fetch new token rates + * + * selectedAddress - Vault selected address + * + * networkClientId - The network client ID of the current selected network + * + * disabled - Boolean to track if network requests are blocked + * + * isUnlocked - Boolean to track if the keyring state is unlocked + * + * isDetectionEnabledFromPreferences - Boolean to track if detection is enabled from PreferencesController + * + * isDetectionEnabledForNetwork - Boolean to track if detected is enabled for current network */ export class TokenDetectionController extends StaticIntervalPollingController()< typeof controllerName, @@ -179,7 +189,7 @@ export class TokenDetectionController extends StaticIntervalPollingController void; - #accountsAPI = { + readonly #accountsAPI = { isAccountsAPIEnabled: true, supportedNetworksCache: null as number[] | null, platform: '' as 'extension' | 'mobile', - async getSupportedNetworks() { + async getSupportedNetworks(options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + }) { /* istanbul ignore next */ if (!this.isAccountsAPIEnabled) { throw new Error('Accounts API Feature Switch is disabled'); @@ -223,7 +237,11 @@ export class TokenDetectionController extends StaticIntervalPollingController null); + const { getAuthenticationControllerBearerToken } = options; + + const result = await fetchSupportedNetworks({ + getAuthenticationControllerBearerToken, + }).catch(() => null); this.supportedNetworksCache = result; return result; }, @@ -232,7 +250,13 @@ export class TokenDetectionController extends StaticIntervalPollingController ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + }, ) { + const { getAuthenticationControllerBearerToken } = options; const chainIdNumbers = chainIds.map((chainId) => hexToNumber(chainId)); if ( @@ -249,6 +273,7 @@ export class TokenDetectionController extends StaticIntervalPollingController void; @@ -439,7 +464,7 @@ export class TokenDetectionController extends StaticIntervalPollingController { // Fetch balances for multiple chain IDs at once const tokenBalancesByChain = await this.#accountsAPI - .getMultiNetworksBalances(selectedAddress, chainIds, supportedNetworks) + .getMultiNetworksBalances( + selectedAddress, + chainIds, + supportedNetworks, + { + getAuthenticationControllerBearerToken: + this.#getAuthenticationControllerBearerToken.bind(this), + }, + ) .catch(() => null); if (tokenBalancesByChain === null) { @@ -879,10 +917,10 @@ export class TokenDetectionController extends StaticIntervalPollingController { + return await this.messagingSystem.call( + 'AuthenticationController:getBearerToken', + ); + } } export default TokenDetectionController; diff --git a/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.test.ts b/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.test.ts index 06ebd7fffd1..6fc58263228 100644 --- a/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.test.ts +++ b/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.test.ts @@ -10,29 +10,59 @@ import { const MOCK_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'; +const MOCK_ACCESS_TOKEN = 'mock-access-token'; +const mockAuthenticationControllerGetBearerToken = jest.fn(); + describe('fetchSupportedNetworks()', () => { const createMockAPI = () => nock(MULTICHAIN_ACCOUNTS_DOMAIN).get('/v1/supportedNetworks'); + beforeEach(() => { + jest.clearAllMocks(); + mockAuthenticationControllerGetBearerToken.mockResolvedValue( + MOCK_ACCESS_TOKEN, + ); + }); + it('should successfully return supported networks array', async () => { const mockAPI = createMockAPI().reply( 200, MOCK_GET_SUPPORTED_NETWORKS_RESPONSE, ); - const result = await fetchSupportedNetworks(); + const result = await fetchSupportedNetworks({ + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }); expect(result).toStrictEqual( MOCK_GET_SUPPORTED_NETWORKS_RESPONSE.fullSupport, ); expect(mockAPI.isDone()).toBe(true); }); + it('should attach the correct Authorization header', async () => { + const mockAPI = createMockAPI() + .matchHeader('Authorization', `Bearer ${MOCK_ACCESS_TOKEN}`) + .reply(200, MOCK_GET_SUPPORTED_NETWORKS_RESPONSE); + + await fetchSupportedNetworks({ + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }); + expect(mockAuthenticationControllerGetBearerToken).toHaveBeenCalledTimes(1); + expect(mockAPI.isDone()).toBe(true); + }); + it('should throw error when fetch fails', async () => { const mockAPI = createMockAPI().reply(500); - await expect(async () => await fetchSupportedNetworks()).rejects.toThrow( - expect.any(Error), - ); + await expect( + async () => + await fetchSupportedNetworks({ + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), + ).rejects.toThrow(expect.any(Error)); expect(mockAPI.isDone()).toBe(true); }); }); @@ -43,15 +73,45 @@ describe('fetchMultiChainBalances()', () => { `/v2/accounts/${MOCK_ADDRESS}/balances`, ); + beforeEach(() => { + mockAuthenticationControllerGetBearerToken.mockResolvedValue( + MOCK_ACCESS_TOKEN, + ); + }); + it('should successfully return balances response', async () => { const mockAPI = createMockAPI().reply(200, MOCK_GET_BALANCES_RESPONSE); - const result = await fetchMultiChainBalances(MOCK_ADDRESS, {}, 'extension'); + const result = await fetchMultiChainBalances( + MOCK_ADDRESS, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, + 'extension', + ); expect(result).toBeDefined(); expect(result).toStrictEqual(MOCK_GET_BALANCES_RESPONSE); expect(mockAPI.isDone()).toBe(true); }); + it('should attach the correct Authorization header', async () => { + const mockAPI = createMockAPI() + .matchHeader('Authorization', `Bearer ${MOCK_ACCESS_TOKEN}`) + .reply(200, MOCK_GET_BALANCES_RESPONSE); + + await fetchMultiChainBalances( + MOCK_ADDRESS, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, + 'extension', + ); + expect(mockAuthenticationControllerGetBearerToken).toHaveBeenCalledTimes(1); + expect(mockAPI.isDone()).toBe(true); + }); + it('should successfully return balances response with query params to refine search', async () => { const mockAPI = createMockAPI() .query({ @@ -63,6 +123,8 @@ describe('fetchMultiChainBalances()', () => { MOCK_ADDRESS, { networks: [1, 10], + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, }, 'extension', ); @@ -84,7 +146,14 @@ describe('fetchMultiChainBalances()', () => { await expect( async () => - await fetchMultiChainBalances(MOCK_ADDRESS, {}, 'extension'), + await fetchMultiChainBalances( + MOCK_ADDRESS, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, + 'extension', + ), ).rejects.toThrow(expect.any(Error)); expect(mockAPI.isDone()).toBe(true); }, diff --git a/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts b/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts index 8723a7e9ead..72564473179 100644 --- a/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts +++ b/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts @@ -5,6 +5,7 @@ import type { GetBalancesResponse, GetSupportedNetworksResponse, } from './types'; +import type { AuthenticationControllerGetBearerToken } from '../../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; export const MULTICHAIN_ACCOUNTS_DOMAIN = 'https://accounts.api.cx.metamask.io'; @@ -25,33 +26,56 @@ const getBalancesUrl = ( /** * Fetches Supported Networks. + * + * @param options - options to pass down for a more refined search + * @param options.getAuthenticationControllerBearerToken - function to get the bearer token from the AuthenticationController * @returns supported networks (decimal) */ -export async function fetchSupportedNetworks(): Promise { +export async function fetchSupportedNetworks(options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; +}): Promise { const url = new URL(`${MULTICHAIN_ACCOUNTS_DOMAIN}/v1/supportedNetworks`); - const response: GetSupportedNetworksResponse = await handleFetch(url); + const authenticationControllerBearerToken = + await options.getAuthenticationControllerBearerToken(); + const response: GetSupportedNetworksResponse = await handleFetch(url, { + headers: { + Authorization: `Bearer ${authenticationControllerBearerToken}`, + }, + }); return response.fullSupport; } /** * Fetches Balances for multiple networks. + * * @param address - address to fetch balances from * @param options - params to pass down for a more refined search * @param options.networks - the networks (in decimal) that you want to filter by + * @param options.getAuthenticationControllerBearerToken - function to get the bearer token from the AuthenticationController * @param platform - indicates whether the platform is extension or mobile * @returns a Balances Response */ export async function fetchMultiChainBalances( address: string, - options: { networks?: number[] }, + options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + networks?: number[]; + }, platform: 'extension' | 'mobile', ) { const url = getBalancesUrl(address, { networks: options?.networks?.join(), }); + const authenticationControllerBearerToken = + await options.getAuthenticationControllerBearerToken(); const response: GetBalancesResponse = await handleFetch(url, { headers: { 'x-metamask-clientproduct': `metamask-${platform}`, + Authorization: `Bearer ${authenticationControllerBearerToken}`, }, }); return response; diff --git a/packages/assets-controllers/tsconfig.build.json b/packages/assets-controllers/tsconfig.build.json index da67830a2a2..8cb0a502051 100644 --- a/packages/assets-controllers/tsconfig.build.json +++ b/packages/assets-controllers/tsconfig.build.json @@ -16,7 +16,8 @@ { "path": "../polling-controller/tsconfig.build.json" }, { "path": "../permission-controller/tsconfig.build.json" }, { "path": "../transaction-controller/tsconfig.build.json" }, - { "path": "../phishing-controller/tsconfig.build.json" } + { "path": "../phishing-controller/tsconfig.build.json" }, + { "path": "../profile-sync-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"], "exclude": ["**/*.test.ts", "**/__fixtures__/"] diff --git a/packages/assets-controllers/tsconfig.json b/packages/assets-controllers/tsconfig.json index b0e7c0374e3..0f754f975b4 100644 --- a/packages/assets-controllers/tsconfig.json +++ b/packages/assets-controllers/tsconfig.json @@ -15,7 +15,8 @@ { "path": "../phishing-controller" }, { "path": "../polling-controller" }, { "path": "../permission-controller" }, - { "path": "../transaction-controller" } + { "path": "../transaction-controller" }, + { "path": "../profile-sync-controller" } ], "include": ["../../types", "./src", "../../tests"] } diff --git a/packages/multichain-network-controller/CHANGELOG.md b/packages/multichain-network-controller/CHANGELOG.md index b1323454ad7..f0a5dec2939 100644 --- a/packages/multichain-network-controller/CHANGELOG.md +++ b/packages/multichain-network-controller/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **BREAKING:** Add peer dependency `@metamask/profile-sync-controller` +- **BREAKING:** Add `profileId`-based rate limiting support for Accounts API calls + - Use `AuthenticationController:getBearerToken` in order to get a bearer token that gets attached to every request to the Accounts API + ## [0.9.0] ### Changed diff --git a/packages/multichain-network-controller/package.json b/packages/multichain-network-controller/package.json index 0200c9501b6..6f515737ba2 100644 --- a/packages/multichain-network-controller/package.json +++ b/packages/multichain-network-controller/package.json @@ -75,7 +75,8 @@ }, "peerDependencies": { "@metamask/accounts-controller": "^31.0.0", - "@metamask/network-controller": "^24.0.0" + "@metamask/network-controller": "^24.0.0", + "@metamask/profile-sync-controller": "^19.0.0" }, "engines": { "node": "^18.18 || >=20" diff --git a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts index 31099445612..b34a39759cf 100644 --- a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts +++ b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts @@ -20,6 +20,7 @@ import type { import { KnownCaipNamespace, type CaipAccountId } from '@metamask/utils'; import { MultichainNetworkController } from './MultichainNetworkController'; +import type { AuthenticationControllerGetBearerToken } from '../../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; import { createMockInternalAccount } from '../../tests/utils'; import { type ActiveNetworksResponse } from '../api/accounts-api'; import { getDefaultMultichainNetworkControllerState } from '../constants'; @@ -43,8 +44,19 @@ function createMockNetworkService( ): AbstractMultichainNetworkService { return { fetchNetworkActivity: jest - .fn, [CaipAccountId[]]>() - .mockResolvedValue(mockResponse), + .fn< + Promise, + [ + CaipAccountId[], + { getAuthenticationControllerBearerToken: () => Promise }, + ] + >() + .mockImplementation( + async (_, { getAuthenticationControllerBearerToken }) => { + await getAuthenticationControllerBearerToken(); + return mockResponse; + }, + ), }; } @@ -147,6 +159,15 @@ function setupController({ mockGetSelectedChainId, ); + const mockAuthenticationControllerGetBearerToken = jest.fn< + ReturnType, + Parameters + >(); + messenger.registerActionHandler( + 'AuthenticationController:getBearerToken', + mockAuthenticationControllerGetBearerToken, + ); + const mockFindNetworkClientIdByChainId = findNetworkClientIdByChainId ?? jest.fn< @@ -173,6 +194,7 @@ function setupController({ 'NetworkController:getSelectedChainId', 'NetworkController:findNetworkClientIdByChainId', 'AccountsController:listMultichainAccounts', + 'AuthenticationController:getBearerToken', ], allowedEvents: ['AccountsController:selectedAccountChange'], }); @@ -219,6 +241,7 @@ function setupController({ mockRemoveNetwork, mockGetSelectedChainId, mockFindNetworkClientIdByChainId, + mockAuthenticationControllerGetBearerToken, publishSpy, triggerSelectedAccountChange, networkService: mockNetworkService ?? defaultNetworkService, @@ -606,9 +629,15 @@ describe('MultichainNetworkController', () => { }; const mockNetworkService = createMockNetworkService(mockResponse); - await mockNetworkService.fetchNetworkActivity([ - `${KnownCaipNamespace.Eip155}:${MOCK_EVM_CHAIN_1}:${MOCK_EVM_ADDRESS}`, - ]); + await mockNetworkService.fetchNetworkActivity( + [ + `${KnownCaipNamespace.Eip155}:${MOCK_EVM_CHAIN_1}:${MOCK_EVM_ADDRESS}`, + ], + { + getAuthenticationControllerBearerToken: async () => + 'mock-access-token', + }, + ); const { controller, messenger } = setupController({ mockNetworkService, @@ -628,9 +657,12 @@ describe('MultichainNetworkController', () => { const result = await controller.getNetworksWithTransactionActivityByAccounts(); - expect(mockNetworkService.fetchNetworkActivity).toHaveBeenCalledWith([ - `${KnownCaipNamespace.Eip155}:0:${MOCK_EVM_ADDRESS}`, - ]); + expect(mockNetworkService.fetchNetworkActivity).toHaveBeenCalledWith( + [`${KnownCaipNamespace.Eip155}:0:${MOCK_EVM_ADDRESS}`], + expect.any(Function), + ); + + // expect(mockAuthenticationControllerGetBearerToken).toHaveBeenCalled(); expect(result).toStrictEqual({ [MOCK_EVM_ADDRESS]: { diff --git a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts index 14c87bec011..838b98bd164 100644 --- a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts +++ b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts @@ -4,6 +4,7 @@ import type { InternalAccount } from '@metamask/keyring-internal-api'; import type { NetworkClientId } from '@metamask/network-controller'; import { type CaipChainId, isCaipChainId } from '@metamask/utils'; +import type { AuthenticationControllerGetBearerToken } from '../../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; import { type ActiveNetworksByAddress, toAllowedCaipAccountIds, @@ -155,6 +156,14 @@ export class MultichainNetworkController extends BaseController< return await this.#setActiveEvmNetwork(id); } + async #getAuthenticationControllerBearerToken(): ReturnType< + AuthenticationControllerGetBearerToken['handler'] + > { + return await this.messagingSystem.call( + 'AuthenticationController:getBearerToken', + ); + } + /** * Returns the active networks for the available EVM addresses (non-EVM networks will be supported in the future). * Fetches the data from the API and caches it in state. @@ -176,8 +185,13 @@ export class MultichainNetworkController extends BaseController< .map((account: InternalAccount) => toAllowedCaipAccountIds(account)) .flat(); - const activeNetworks = - await this.#networkService.fetchNetworkActivity(formattedAccounts); + const activeNetworks = await this.#networkService.fetchNetworkActivity( + formattedAccounts, + { + getAuthenticationControllerBearerToken: + this.#getAuthenticationControllerBearerToken.bind(this), + }, + ); const formattedNetworks = toActiveNetworksByAddress(activeNetworks); this.update((state) => { diff --git a/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.test.ts b/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.test.ts index 9167a58768f..8d068e3729f 100644 --- a/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.test.ts +++ b/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.test.ts @@ -10,10 +10,6 @@ import { } from '../api/accounts-api'; describe('MultichainNetworkService', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - const mockFetch = jest.fn(); const MOCK_EVM_ADDRESS = '0x1234567890123456789012345678901234567890'; const MOCK_EVM_CHAIN_1 = '1'; @@ -24,6 +20,17 @@ describe('MultichainNetworkService', () => { `${KnownCaipNamespace.Eip155}:${MOCK_EVM_CHAIN_137}:${MOCK_EVM_ADDRESS}`, ]; + const MOCK_ACCESS_TOKEN = 'mock-access-token'; + const mockAuthenticationControllerGetBearerToken = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + + mockAuthenticationControllerGetBearerToken.mockResolvedValue( + MOCK_ACCESS_TOKEN, + ); + }); + describe('constructor', () => { it('creates an instance with the provided fetch implementation', () => { const service = new MultichainNetworkService({ @@ -48,7 +55,10 @@ describe('MultichainNetworkService', () => { fetch: mockFetch, }); - const result = await service.fetchNetworkActivity([]); + const result = await service.fetchNetworkActivity([], { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }); expect(mockFetch).not.toHaveBeenCalled(); expect(result).toStrictEqual({ activeNetworks: [] }); @@ -70,7 +80,10 @@ describe('MultichainNetworkService', () => { const service = new MultichainNetworkService({ fetch: mockFetch, }); - const result = await service.fetchNetworkActivity(validAccountIds); + const result = await service.fetchNetworkActivity(validAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }); expect(mockFetch).toHaveBeenCalledWith( `${MULTICHAIN_ACCOUNTS_BASE_URL}/v2/activeNetworks?accountIds=${encodeURIComponent(validAccountIds.join(','))}`, @@ -78,6 +91,7 @@ describe('MultichainNetworkService', () => { method: 'GET', headers: { [MULTICHAIN_ACCOUNTS_CLIENT_HEADER]: MULTICHAIN_ACCOUNTS_CLIENT_ID, + Authorization: `Bearer ${MOCK_ACCESS_TOKEN}`, Accept: 'application/json', }, }, @@ -116,7 +130,10 @@ describe('MultichainNetworkService', () => { fetch: mockFetch, }); - const result = await service.fetchNetworkActivity(manyAccountIds); + const result = await service.fetchNetworkActivity(manyAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }); expect(mockFetch).toHaveBeenCalledTimes(2); @@ -160,7 +177,10 @@ describe('MultichainNetworkService', () => { batchSize: customBatchSize, }); - const result = await service.fetchNetworkActivity(manyAccountIds); + const result = await service.fetchNetworkActivity(manyAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }); expect(mockFetch).toHaveBeenCalledTimes(3); @@ -180,7 +200,10 @@ describe('MultichainNetworkService', () => { }); await expect( - service.fetchNetworkActivity(validAccountIds), + service.fetchNetworkActivity(validAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), ).rejects.toThrow('HTTP error! status: 404'); }); @@ -195,7 +218,10 @@ describe('MultichainNetworkService', () => { }); await expect( - service.fetchNetworkActivity(validAccountIds), + service.fetchNetworkActivity(validAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), ).rejects.toThrow( 'At path: activeNetworks -- Expected an array value, but received: undefined', ); @@ -211,7 +237,10 @@ describe('MultichainNetworkService', () => { }); await expect( - service.fetchNetworkActivity(validAccountIds), + service.fetchNetworkActivity(validAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), ).rejects.toThrow('Request timeout: Failed to fetch active networks'); }); @@ -224,7 +253,10 @@ describe('MultichainNetworkService', () => { }); await expect( - service.fetchNetworkActivity(validAccountIds), + service.fetchNetworkActivity(validAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), ).rejects.toThrow(networkError.message); }); @@ -236,7 +268,10 @@ describe('MultichainNetworkService', () => { }); await expect( - service.fetchNetworkActivity(validAccountIds), + service.fetchNetworkActivity(validAccountIds, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), ).rejects.toThrow('Failed to fetch active networks: Unknown error'); }); }); diff --git a/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.ts b/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.ts index 806a583187c..79fe4768a35 100644 --- a/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.ts +++ b/packages/multichain-network-controller/src/MultichainNetworkService/MultichainNetworkService.ts @@ -1,3 +1,4 @@ +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import { assert } from '@metamask/superstruct'; import type { CaipAccountId } from '@metamask/utils'; import { chunk } from 'lodash'; @@ -34,23 +35,38 @@ export class MultichainNetworkService { * Automatically handles batching requests to comply with URL length limitations. * * @param accountIds - Array of CAIP-10 account IDs to fetch activity for. + * @param options - Options for the fetch operation. + * @param options.getAuthenticationControllerBearerToken - Function to get the bearer token from the AuthenticationController. * @returns Promise resolving to the combined active networks response. * @throws Error if the response format is invalid or the request fails. */ async fetchNetworkActivity( accountIds: CaipAccountId[], + options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + }, ): Promise { if (accountIds.length === 0) { return { activeNetworks: [] }; } + const { getAuthenticationControllerBearerToken } = options; + if (accountIds.length <= this.#batchSize) { - return this.#fetchNetworkActivityBatch(accountIds); + return this.#fetchNetworkActivityBatch(accountIds, { + getAuthenticationControllerBearerToken, + }); } const batches = chunk(accountIds, this.#batchSize); const batchResults = await Promise.all( - batches.map((batch) => this.#fetchNetworkActivityBatch(batch)), + batches.map((batch) => + this.#fetchNetworkActivityBatch(batch, { + getAuthenticationControllerBearerToken, + }), + ), ); const combinedResponse: ActiveNetworksResponse = { @@ -66,19 +82,29 @@ export class MultichainNetworkService { * Internal method to fetch a single batch of account IDs. * * @param accountIds - Batch of account IDs to fetch + * @param options - Options for the fetch operation + * @param options.getAuthenticationControllerBearerToken - Function to get the bearer token from the AuthenticationController * @returns Promise resolving to the active networks response for this batch * @throws Error if the response format is invalid or the request fails */ async #fetchNetworkActivityBatch( accountIds: CaipAccountId[], + options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + }, ): Promise { try { const url = buildActiveNetworksUrl(accountIds); + const authenticationControllerBearerToken = + await options.getAuthenticationControllerBearerToken(); const response = await this.#fetch(url.toString(), { method: 'GET', headers: { [MULTICHAIN_ACCOUNTS_CLIENT_HEADER]: MULTICHAIN_ACCOUNTS_CLIENT_ID, + Authorization: `Bearer ${authenticationControllerBearerToken}`, Accept: 'application/json', }, }); diff --git a/packages/multichain-network-controller/src/types.ts b/packages/multichain-network-controller/src/types.ts index 285fff5caac..316be0292ba 100644 --- a/packages/multichain-network-controller/src/types.ts +++ b/packages/multichain-network-controller/src/types.ts @@ -23,6 +23,7 @@ import type { import type { ActiveNetworksByAddress } from './api/accounts-api'; import type { MultichainNetworkController } from './MultichainNetworkController/MultichainNetworkController'; +import type { AuthenticationControllerGetBearerToken } from '../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; export const MULTICHAIN_NETWORK_CONTROLLER_NAME = 'MultichainNetworkController'; @@ -178,7 +179,8 @@ export type AllowedActions = | AccountsControllerListMultichainAccountsAction | NetworkControllerRemoveNetworkAction | NetworkControllerGetSelectedChainIdAction - | NetworkControllerFindNetworkClientIdByChainIdAction; + | NetworkControllerFindNetworkClientIdByChainIdAction + | AuthenticationControllerGetBearerToken; // Re-define event here to avoid circular dependency with AccountsController export type AccountsControllerSelectedAccountChangeEvent = { diff --git a/packages/multichain-network-controller/tsconfig.build.json b/packages/multichain-network-controller/tsconfig.build.json index e45f8c90f04..78184207ad6 100644 --- a/packages/multichain-network-controller/tsconfig.build.json +++ b/packages/multichain-network-controller/tsconfig.build.json @@ -10,7 +10,8 @@ { "path": "../base-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, { "path": "../network-controller/tsconfig.build.json" }, - { "path": "../keyring-controller/tsconfig.build.json" } + { "path": "../keyring-controller/tsconfig.build.json" }, + { "path": "../profile-sync-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"] } diff --git a/packages/multichain-network-controller/tsconfig.json b/packages/multichain-network-controller/tsconfig.json index 8726c490cc6..eb061d82282 100644 --- a/packages/multichain-network-controller/tsconfig.json +++ b/packages/multichain-network-controller/tsconfig.json @@ -8,7 +8,8 @@ { "path": "../base-controller" }, { "path": "../controller-utils" }, { "path": "../network-controller" }, - { "path": "../keyring-controller" } + { "path": "../keyring-controller" }, + { "path": "../profile-sync-controller" } ], "include": ["../../types", "./src", "./tests"] } diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 1e189e0e80d..e85c0bba7dc 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **BREAKING:** Add peer dependency `@metamask/profile-sync-controller` +- **BREAKING:** Add `profileId`-based rate limiting support for Accounts API calls + - Use `AuthenticationController:getBearerToken` in order to get a bearer token that gets attached to every request to the Accounts API + ## [58.1.0] ### Added diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 37e6a016c11..7134ffcf08a 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -99,6 +99,7 @@ "@metamask/eth-block-tracker": ">=9", "@metamask/gas-fee-controller": "^24.0.0", "@metamask/network-controller": "^24.0.0", + "@metamask/profile-sync-controller": "^19.0.0", "@metamask/remote-feature-flag-controller": "^1.5.0" }, "engines": { diff --git a/packages/transaction-controller/src/TransactionController.test.ts b/packages/transaction-controller/src/TransactionController.test.ts index 33be535fe5b..1f03404568c 100644 --- a/packages/transaction-controller/src/TransactionController.test.ts +++ b/packages/transaction-controller/src/TransactionController.test.ts @@ -703,6 +703,7 @@ describe('TransactionController', () => { 'NetworkController:getNetworkClientById', 'NetworkController:findNetworkClientIdByChainId', 'RemoteFeatureFlagController:getState', + 'AuthenticationController:getBearerToken', ], allowedEvents: [], }); diff --git a/packages/transaction-controller/src/TransactionController.ts b/packages/transaction-controller/src/TransactionController.ts index c04b13f5bee..7b60b8f469a 100644 --- a/packages/transaction-controller/src/TransactionController.ts +++ b/packages/transaction-controller/src/TransactionController.ts @@ -43,6 +43,7 @@ import type { Transaction as NonceTrackerTransaction, } from '@metamask/nonce-tracker'; import { NonceTracker } from '@metamask/nonce-tracker'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; import { errorCodes, @@ -465,7 +466,8 @@ export type AllowedActions = | KeyringControllerSignEip7702AuthorizationAction | NetworkControllerFindNetworkClientIdByChainIdAction | NetworkControllerGetNetworkClientByIdAction - | RemoteFeatureFlagControllerGetStateAction; + | RemoteFeatureFlagControllerGetStateAction + | AuthenticationControllerGetBearerToken; /** * The external events available to the {@link TransactionController}. @@ -984,7 +986,10 @@ export class TransactionController extends BaseController< this.#incomingTransactionOptions.includeTokenTransfers, isEnabled: this.#incomingTransactionOptions.isEnabled, messenger: this.messagingSystem, - remoteTransactionSource: new AccountsApiRemoteTransactionSource(), + remoteTransactionSource: new AccountsApiRemoteTransactionSource({ + getAuthenticationControllerBearerToken: + this.#getAuthenticationControllerBearerToken.bind(this), + }), trimTransactions: this.#trimTransactionsForState.bind(this), updateTransactions: this.#incomingTransactionOptions.updateTransactions, }); @@ -4036,6 +4041,14 @@ export class TransactionController extends BaseController< return transactionMeta; } + async #getAuthenticationControllerBearerToken(): ReturnType< + AuthenticationControllerGetBearerToken['handler'] + > { + return await this.messagingSystem.call( + 'AuthenticationController:getBearerToken', + ); + } + async #updateFirstTimeInteraction( transactionMeta: TransactionMeta, { @@ -4079,7 +4092,11 @@ export class TransactionController extends BaseController< try { const { count } = await this.#trace( { name: 'Account Address Relationship', parentContext: traceContext }, - () => getAccountAddressRelationship(request), + () => + getAccountAddressRelationship(request, { + getAuthenticationControllerBearerToken: + this.#getAuthenticationControllerBearerToken.bind(this), + }), ); const isFirstTimeInteraction = diff --git a/packages/transaction-controller/src/api/accounts-api.test.ts b/packages/transaction-controller/src/api/accounts-api.test.ts index 7174de355d1..ae7e74e8ae7 100644 --- a/packages/transaction-controller/src/api/accounts-api.test.ts +++ b/packages/transaction-controller/src/api/accounts-api.test.ts @@ -16,6 +16,9 @@ jest.mock('@metamask/controller-utils', () => ({ successfulFetch: jest.fn(), })); +const MOCK_ACCESS_TOKEN = 'mock-access-token'; +const mockAuthenticationControllerGetBearerToken = jest.fn(); + const ADDRESS_MOCK = '0x123'; const CHAIN_IDS_MOCK = ['0x1', '0x2'] as Hex[]; const CURSOR_MOCK = '0x456'; @@ -57,6 +60,9 @@ describe('Accounts API', () => { beforeEach(() => { jest.resetAllMocks(); + mockAuthenticationControllerGetBearerToken.mockResolvedValue( + MOCK_ACCESS_TOKEN, + ); }); describe('getAccountAddressRelationship', () => { @@ -70,6 +76,10 @@ describe('Accounts API', () => { const result = await getAccountAddressRelationship( FIRST_TIME_REQUEST_MOCK, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, ); expect(result).toStrictEqual({ @@ -82,6 +92,10 @@ describe('Accounts API', () => { const result = await getAccountAddressRelationship( FIRST_TIME_REQUEST_MOCK, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, ); expect(result).toStrictEqual({ @@ -95,11 +109,33 @@ describe('Accounts API', () => { const result = await getAccountAddressRelationship( FIRST_TIME_REQUEST_MOCK, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, ); expect(result).toStrictEqual(EXISTING_RELATIONSHIP_RESPONSE_MOCK); }); + it('includes the Authorization header', async () => { + mockFetch(EXISTING_RELATIONSHIP_RESPONSE_MOCK); + + await getAccountAddressRelationship(FIRST_TIME_REQUEST_MOCK, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }); + + expect(fetchMock).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: `Bearer ${MOCK_ACCESS_TOKEN}`, + }), + }), + ); + }); + describe('throws FirstTimeInteractionError', () => { it('for unsupported chains', async () => { const request = { @@ -108,9 +144,12 @@ describe('Accounts API', () => { to: TO_ADDRESS, }; - await expect(getAccountAddressRelationship(request)).rejects.toThrow( - FirstTimeInteractionError, - ); + await expect( + getAccountAddressRelationship(request, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), + ).rejects.toThrow(FirstTimeInteractionError); }); it('on error response', async () => { @@ -119,7 +158,10 @@ describe('Accounts API', () => { }); await expect( - getAccountAddressRelationship(FIRST_TIME_REQUEST_MOCK), + getAccountAddressRelationship(FIRST_TIME_REQUEST_MOCK, { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }), ).rejects.toThrow(FirstTimeInteractionError); }); }); @@ -129,13 +171,19 @@ describe('Accounts API', () => { it('queries the accounts API with the correct parameters', async () => { mockFetch(ACCOUNT_RESPONSE_MOCK); - const response = await getAccountTransactions({ - address: ADDRESS_MOCK, - chainIds: CHAIN_IDS_MOCK, - cursor: CURSOR_MOCK, - endTimestamp: END_TIMESTAMP_MOCK, - startTimestamp: START_TIMESTAMP_MOCK, - }); + const response = await getAccountTransactions( + { + address: ADDRESS_MOCK, + chainIds: CHAIN_IDS_MOCK, + cursor: CURSOR_MOCK, + endTimestamp: END_TIMESTAMP_MOCK, + startTimestamp: START_TIMESTAMP_MOCK, + }, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, + ); expect(response).toStrictEqual(ACCOUNT_RESPONSE_MOCK); @@ -149,21 +197,54 @@ describe('Accounts API', () => { it('includes the client header', async () => { mockFetch(ACCOUNT_RESPONSE_MOCK); - await getAccountTransactions({ - address: ADDRESS_MOCK, - chainIds: CHAIN_IDS_MOCK, - cursor: CURSOR_MOCK, - endTimestamp: END_TIMESTAMP_MOCK, - startTimestamp: START_TIMESTAMP_MOCK, - tags: [TAG_MOCK, TAG_2_MOCK], - }); + await getAccountTransactions( + { + address: ADDRESS_MOCK, + chainIds: CHAIN_IDS_MOCK, + cursor: CURSOR_MOCK, + endTimestamp: END_TIMESTAMP_MOCK, + startTimestamp: START_TIMESTAMP_MOCK, + tags: [TAG_MOCK, TAG_2_MOCK], + }, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, + ); expect(fetchMock).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ - headers: { + headers: expect.objectContaining({ 'x-metamask-clientproduct': `metamask-transaction-controller__${TAG_MOCK}__${TAG_2_MOCK}`, - }, + }), + }), + ); + }); + + it('includes the Authorization header', async () => { + mockFetch(ACCOUNT_RESPONSE_MOCK); + + await getAccountTransactions( + { + address: ADDRESS_MOCK, + chainIds: CHAIN_IDS_MOCK, + cursor: CURSOR_MOCK, + endTimestamp: END_TIMESTAMP_MOCK, + startTimestamp: START_TIMESTAMP_MOCK, + }, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, + ); + + expect(fetchMock).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: `Bearer ${MOCK_ACCESS_TOKEN}`, + }), }), ); }); diff --git a/packages/transaction-controller/src/api/accounts-api.ts b/packages/transaction-controller/src/api/accounts-api.ts index 026c0a6420c..ec86b0bb972 100644 --- a/packages/transaction-controller/src/api/accounts-api.ts +++ b/packages/transaction-controller/src/api/accounts-api.ts @@ -1,4 +1,5 @@ import { successfulFetch } from '@metamask/controller-utils'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import type { Hex } from '@metamask/utils'; import { createModuleLogger } from '@metamask/utils'; @@ -116,10 +117,17 @@ const log = createModuleLogger(projectLogger, 'accounts-api'); * Fetch account address relationship from the accounts API. * * @param request - The request object. + * @param options - Options for the request. + * @param options.getAuthenticationControllerBearerToken - A function that returns the bearer token from the AuthenticationController. * @returns The raw response object from the API. */ export async function getAccountAddressRelationship( request: GetAccountAddressRelationshipRequest, + options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + }, ): Promise { const { chainId, from, to } = request; @@ -128,12 +136,16 @@ export async function getAccountAddressRelationship( throw new FirstTimeInteractionError('Unsupported chain ID'); } + const authenticationControllerBearerToken = + await options.getAuthenticationControllerBearerToken(); + const url = `${BASE_URL}/v1/networks/${chainId}/accounts/${from}/relationships/${to}`; log('Getting account address relationship', { request, url }); const headers = { [CLIENT_HEADER]: CLIENT_ID, + Authorization: `Bearer ${authenticationControllerBearerToken}`, }; const response = await successfulFetch(url, { headers }); @@ -160,10 +172,17 @@ export async function getAccountAddressRelationship( * Fetch account transactions from the accounts API. * * @param request - The request object. + * @param options - Options for the request. + * @param options.getAuthenticationControllerBearerToken - A function that returns the bearer token from the AuthenticationController. * @returns The response object. */ export async function getAccountTransactions( request: GetAccountTransactionsRequest, + options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + }, ): Promise { const { address, @@ -207,8 +226,12 @@ export async function getAccountTransactions( const clientId = [CLIENT_ID, ...(tags || [])].join('__'); + const authenticationControllerBearerToken = + await options.getAuthenticationControllerBearerToken(); + const headers = { [CLIENT_HEADER]: clientId, + Authorization: `Bearer ${authenticationControllerBearerToken}`, }; const response = await successfulFetch(url, { headers }); diff --git a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts index e433c7e3def..94fb2a86b1f 100644 --- a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts +++ b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts @@ -15,6 +15,9 @@ jest.mock('../utils/transaction-type'); jest.useFakeTimers(); +const MOCK_ACCESS_TOKEN = 'mock-access-token'; +const mockAuthenticationControllerGetBearerToken = jest.fn(); + const ADDRESS_MOCK = '0x123'; const ONE_DAY_MS = 1000 * 60 * 60 * 24; const NOW_MOCK = 789000 + ONE_DAY_MS; @@ -101,11 +104,19 @@ const TRANSACTION_TOKEN_TRANSFER_MOCK = { describe('AccountsApiRemoteTransactionSource', () => { const getAccountTransactionsMock = jest.mocked(getAccountTransactions); const determineTransactionTypeMock = jest.mocked(determineTransactionType); + const baseOptions = { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }; beforeEach(() => { jest.resetAllMocks(); jest.setSystemTime(NOW_MOCK); + mockAuthenticationControllerGetBearerToken.mockResolvedValue( + MOCK_ACCESS_TOKEN, + ); + getAccountTransactionsMock.mockResolvedValue( {} as GetAccountTransactionsResponse, ); @@ -118,17 +129,18 @@ describe('AccountsApiRemoteTransactionSource', () => { describe('getSupportedChains', () => { it('returns supported chains', () => { - const supportedChains = - new AccountsApiRemoteTransactionSource().getSupportedChains(); + const supportedChains = new AccountsApiRemoteTransactionSource( + baseOptions, + ).getSupportedChains(); expect(supportedChains.length).toBeGreaterThan(0); }); }); describe('fetchTransactions', () => { it('queries accounts API', async () => { - await new AccountsApiRemoteTransactionSource().fetchTransactions( - REQUEST_MOCK, - ); + await new AccountsApiRemoteTransactionSource( + baseOptions, + ).fetchTransactions(REQUEST_MOCK); expect(getAccountTransactionsMock).toHaveBeenCalledTimes(1); expect(getAccountTransactionsMock).toHaveBeenCalledWith({ @@ -144,10 +156,9 @@ describe('AccountsApiRemoteTransactionSource', () => { pageInfo: { hasNextPage: false, count: 1 }, }); - const transactions = - await new AccountsApiRemoteTransactionSource().fetchTransactions( - REQUEST_MOCK, - ); + const transactions = await new AccountsApiRemoteTransactionSource( + baseOptions, + ).fetchTransactions(REQUEST_MOCK); expect(transactions).toStrictEqual([TRANSACTION_STANDARD_MOCK]); }); @@ -158,10 +169,9 @@ describe('AccountsApiRemoteTransactionSource', () => { pageInfo: { hasNextPage: false, count: 1 }, }); - const transactions = - await new AccountsApiRemoteTransactionSource().fetchTransactions( - REQUEST_MOCK, - ); + const transactions = await new AccountsApiRemoteTransactionSource( + baseOptions, + ).fetchTransactions(REQUEST_MOCK); expect(transactions).toStrictEqual([TRANSACTION_TOKEN_TRANSFER_MOCK]); }); @@ -172,11 +182,12 @@ describe('AccountsApiRemoteTransactionSource', () => { pageInfo: { hasNextPage: false, count: 1 }, }); - const transactions = - await new AccountsApiRemoteTransactionSource().fetchTransactions({ - ...REQUEST_MOCK, - updateTransactions: false, - }); + const transactions = await new AccountsApiRemoteTransactionSource( + baseOptions, + ).fetchTransactions({ + ...REQUEST_MOCK, + updateTransactions: false, + }); expect(transactions).toStrictEqual([]); }); @@ -187,11 +198,12 @@ describe('AccountsApiRemoteTransactionSource', () => { pageInfo: { hasNextPage: false, count: 1 }, }); - const transactions = - await new AccountsApiRemoteTransactionSource().fetchTransactions({ - ...REQUEST_MOCK, - includeTokenTransfers: false, - }); + const transactions = await new AccountsApiRemoteTransactionSource( + baseOptions, + ).fetchTransactions({ + ...REQUEST_MOCK, + includeTokenTransfers: false, + }); expect(transactions).toStrictEqual([]); }); @@ -202,10 +214,9 @@ describe('AccountsApiRemoteTransactionSource', () => { pageInfo: { hasNextPage: false, count: 1 }, }); - const transactions = - await new AccountsApiRemoteTransactionSource().fetchTransactions( - REQUEST_MOCK, - ); + const transactions = await new AccountsApiRemoteTransactionSource( + baseOptions, + ).fetchTransactions(REQUEST_MOCK); expect(transactions[0].type).toBe(TransactionType.tokenMethodTransfer); }); diff --git a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts index c739d06fca2..b67ba4331d8 100644 --- a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts +++ b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts @@ -1,4 +1,5 @@ import { BNToHex } from '@metamask/controller-utils'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import type { Hex } from '@metamask/utils'; import BN from 'bn.js'; import { v1 as random } from 'uuid'; @@ -42,6 +43,19 @@ const log = createModuleLogger( export class AccountsApiRemoteTransactionSource implements RemoteTransactionSource { + readonly #getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + + constructor(options: { + getAuthenticationControllerBearerToken: () => ReturnType< + AuthenticationControllerGetBearerToken['handler'] + >; + }) { + this.#getAuthenticationControllerBearerToken = + options.getAuthenticationControllerBearerToken; + } + getSupportedChains(): Hex[] { return SUPPORTED_CHAIN_IDS; } @@ -90,12 +104,18 @@ export class AccountsApiRemoteTransactionSource const transactions: TransactionResponse[] = []; try { - const response = await getAccountTransactions({ - address, - chainIds, - sortDirection: 'DESC', - tags, - }); + const response = await getAccountTransactions( + { + address, + chainIds, + sortDirection: 'DESC', + tags, + }, + { + getAuthenticationControllerBearerToken: + this.#getAuthenticationControllerBearerToken.bind(this), + }, + ); if (response?.data) { transactions.push(...response.data); diff --git a/packages/transaction-controller/tsconfig.build.json b/packages/transaction-controller/tsconfig.build.json index 716dda8820b..15cb8ac4dbc 100644 --- a/packages/transaction-controller/tsconfig.build.json +++ b/packages/transaction-controller/tsconfig.build.json @@ -12,6 +12,7 @@ { "path": "../controller-utils/tsconfig.build.json" }, { "path": "../gas-fee-controller/tsconfig.build.json" }, { "path": "../network-controller/tsconfig.build.json" }, + { "path": "../profile-sync-controller/tsconfig.build.json" }, { "path": "../remote-feature-flag-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"] diff --git a/packages/transaction-controller/tsconfig.json b/packages/transaction-controller/tsconfig.json index b839b37eed5..bceae3bd754 100644 --- a/packages/transaction-controller/tsconfig.json +++ b/packages/transaction-controller/tsconfig.json @@ -11,6 +11,7 @@ { "path": "../controller-utils" }, { "path": "../gas-fee-controller" }, { "path": "../network-controller" }, + { "path": "../profile-sync-controller" }, { "path": "../remote-feature-flag-controller" } ], "include": ["../../types", "./src", "./tests"] diff --git a/yarn.lock b/yarn.lock index ab1f3d8cc0b..c01bbc235d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2663,6 +2663,7 @@ __metadata: "@metamask/permission-controller": ^11.0.0 "@metamask/phishing-controller": ^12.5.0 "@metamask/preferences-controller": ^18.0.0 + "@metamask/profile-sync-controller": ^19.0.0 "@metamask/providers": ^22.0.0 "@metamask/snaps-controllers": ^12.0.0 "@metamask/transaction-controller": ^58.0.0 @@ -3830,6 +3831,7 @@ __metadata: peerDependencies: "@metamask/accounts-controller": ^31.0.0 "@metamask/network-controller": ^24.0.0 + "@metamask/profile-sync-controller": ^19.0.0 languageName: unknown linkType: soft @@ -4611,6 +4613,7 @@ __metadata: "@metamask/eth-block-tracker": ">=9" "@metamask/gas-fee-controller": ^24.0.0 "@metamask/network-controller": ^24.0.0 + "@metamask/profile-sync-controller": ^19.0.0 "@metamask/remote-feature-flag-controller": ^1.5.0 languageName: unknown linkType: soft From 0d1e082c800a89de8437c96e0364f2723e160000 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Thu, 3 Jul 2025 11:53:08 +0200 Subject: [PATCH 02/10] fix: profile-sync-controller imports --- packages/assets-controllers/src/TokenDetectionController.ts | 2 +- .../src/multi-chain-accounts-service/multi-chain-accounts.ts | 2 +- .../MultichainNetworkController.test.ts | 2 +- .../MultichainNetworkController/MultichainNetworkController.ts | 2 +- packages/multichain-network-controller/src/types.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 85b36c36a1e..671fc697028 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -33,6 +33,7 @@ import type { PreferencesControllerGetStateAction, PreferencesControllerStateChangeEvent, } from '@metamask/preferences-controller'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import type { TransactionControllerTransactionConfirmedEvent } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { hexToNumber } from '@metamask/utils'; @@ -55,7 +56,6 @@ import type { TokensControllerAddDetectedTokensAction, TokensControllerGetStateAction, } from './TokensController'; -import type { AuthenticationControllerGetBearerToken } from '../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; const DEFAULT_INTERVAL = 180000; diff --git a/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts b/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts index 72564473179..c2b954fc096 100644 --- a/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts +++ b/packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts @@ -1,11 +1,11 @@ import { handleFetch } from '@metamask/controller-utils'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import type { GetBalancesQueryParams, GetBalancesResponse, GetSupportedNetworksResponse, } from './types'; -import type { AuthenticationControllerGetBearerToken } from '../../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; export const MULTICHAIN_ACCOUNTS_DOMAIN = 'https://accounts.api.cx.metamask.io'; diff --git a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts index b34a39759cf..1734935b424 100644 --- a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts +++ b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts @@ -17,10 +17,10 @@ import type { NetworkControllerRemoveNetworkAction, NetworkControllerFindNetworkClientIdByChainIdAction, } from '@metamask/network-controller'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import { KnownCaipNamespace, type CaipAccountId } from '@metamask/utils'; import { MultichainNetworkController } from './MultichainNetworkController'; -import type { AuthenticationControllerGetBearerToken } from '../../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; import { createMockInternalAccount } from '../../tests/utils'; import { type ActiveNetworksResponse } from '../api/accounts-api'; import { getDefaultMultichainNetworkControllerState } from '../constants'; diff --git a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts index 838b98bd164..a06b73faa1f 100644 --- a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts +++ b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.ts @@ -2,9 +2,9 @@ import { BaseController } from '@metamask/base-controller'; import { isEvmAccountType } from '@metamask/keyring-api'; import type { InternalAccount } from '@metamask/keyring-internal-api'; import type { NetworkClientId } from '@metamask/network-controller'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import { type CaipChainId, isCaipChainId } from '@metamask/utils'; -import type { AuthenticationControllerGetBearerToken } from '../../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; import { type ActiveNetworksByAddress, toAllowedCaipAccountIds, diff --git a/packages/multichain-network-controller/src/types.ts b/packages/multichain-network-controller/src/types.ts index 316be0292ba..ed5c478f5d6 100644 --- a/packages/multichain-network-controller/src/types.ts +++ b/packages/multichain-network-controller/src/types.ts @@ -20,10 +20,10 @@ import type { NetworkControllerFindNetworkClientIdByChainIdAction, NetworkClientId, } from '@metamask/network-controller'; +import type { AuthenticationControllerGetBearerToken } from '@metamask/profile-sync-controller/auth'; import type { ActiveNetworksByAddress } from './api/accounts-api'; import type { MultichainNetworkController } from './MultichainNetworkController/MultichainNetworkController'; -import type { AuthenticationControllerGetBearerToken } from '../../profile-sync-controller/dist/controllers/authentication/AuthenticationController.cjs'; export const MULTICHAIN_NETWORK_CONTROLLER_NAME = 'MultichainNetworkController'; From 890aa7eff13e16a502d9941276056261d298c973 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Thu, 3 Jul 2025 12:04:09 +0200 Subject: [PATCH 03/10] fix: lint issue --- packages/assets-controllers/src/TokenDetectionController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 671fc697028..e5cffa7499f 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -465,6 +465,7 @@ export class TokenDetectionController extends StaticIntervalPollingController Date: Thu, 3 Jul 2025 12:09:44 +0200 Subject: [PATCH 04/10] fix: package.json files --- packages/assets-controllers/package.json | 1 + packages/multichain-network-controller/package.json | 1 + packages/transaction-controller/package.json | 1 + yarn.lock | 3 +++ 4 files changed, 6 insertions(+) diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 03998a1defe..1d709a28804 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -89,6 +89,7 @@ "@metamask/permission-controller": "^11.0.6", "@metamask/phishing-controller": "^12.6.0", "@metamask/preferences-controller": "^18.4.0", + "@metamask/profile-sync-controller": "^19.0.0", "@metamask/providers": "^22.1.0", "@metamask/snaps-controllers": "^14.0.1", "@metamask/transaction-controller": "^58.1.0", diff --git a/packages/multichain-network-controller/package.json b/packages/multichain-network-controller/package.json index 88e548c65b9..3289335bfc6 100644 --- a/packages/multichain-network-controller/package.json +++ b/packages/multichain-network-controller/package.json @@ -61,6 +61,7 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/keyring-controller": "^22.1.0", "@metamask/network-controller": "^24.0.0", + "@metamask/profile-sync-controller": "^19.0.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.191", "@types/uuid": "^8.3.0", diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 7134ffcf08a..f872510c75e 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -78,6 +78,7 @@ "@metamask/ethjs-provider-http": "^0.3.0", "@metamask/gas-fee-controller": "^24.0.0", "@metamask/network-controller": "^24.0.0", + "@metamask/profile-sync-controller": "^19.0.0", "@metamask/remote-feature-flag-controller": "^1.6.0", "@types/bn.js": "^5.1.5", "@types/jest": "^27.4.1", diff --git a/yarn.lock b/yarn.lock index b8655cecc01..283762dc594 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2632,6 +2632,7 @@ __metadata: "@metamask/phishing-controller": "npm:^12.6.0" "@metamask/polling-controller": "npm:^14.0.0" "@metamask/preferences-controller": "npm:^18.4.0" + "@metamask/profile-sync-controller": "npm:^19.0.0" "@metamask/providers": "npm:^22.1.0" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/snaps-controllers": "npm:^14.0.1" @@ -3819,6 +3820,7 @@ __metadata: "@metamask/keyring-controller": "npm:^22.1.0" "@metamask/keyring-internal-api": "npm:^6.2.0" "@metamask/network-controller": "npm:^24.0.0" + "@metamask/profile-sync-controller": "npm:^19.0.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^11.2.0" "@solana/addresses": "npm:^2.0.0" @@ -4590,6 +4592,7 @@ __metadata: "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^24.0.0" "@metamask/nonce-tracker": "npm:^6.0.0" + "@metamask/profile-sync-controller": "npm:^19.0.0" "@metamask/remote-feature-flag-controller": "npm:^1.6.0" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/utils": "npm:^11.2.0" From b17622014f51877f82a75982ff43ac9f214a6593 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Thu, 3 Jul 2025 12:22:00 +0200 Subject: [PATCH 05/10] fix: TransactionController UT --- .../AccountsApiRemoteTransactionSource.test.ts | 16 +++++++++++----- .../AccountsApiRemoteTransactionSource.ts | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts index 94fb2a86b1f..bea8c919fea 100644 --- a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts +++ b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.test.ts @@ -143,11 +143,17 @@ describe('AccountsApiRemoteTransactionSource', () => { ).fetchTransactions(REQUEST_MOCK); expect(getAccountTransactionsMock).toHaveBeenCalledTimes(1); - expect(getAccountTransactionsMock).toHaveBeenCalledWith({ - address: ADDRESS_MOCK, - chainIds: SUPPORTED_CHAIN_IDS, - sortDirection: 'DESC', - }); + expect(getAccountTransactionsMock).toHaveBeenCalledWith( + { + address: ADDRESS_MOCK, + chainIds: SUPPORTED_CHAIN_IDS, + sortDirection: 'DESC', + }, + { + getAuthenticationControllerBearerToken: + mockAuthenticationControllerGetBearerToken, + }, + ); }); it('returns normalized standard transaction', async () => { diff --git a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts index b67ba4331d8..3e0bbe55f23 100644 --- a/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts +++ b/packages/transaction-controller/src/helpers/AccountsApiRemoteTransactionSource.ts @@ -113,7 +113,7 @@ export class AccountsApiRemoteTransactionSource }, { getAuthenticationControllerBearerToken: - this.#getAuthenticationControllerBearerToken.bind(this), + this.#getAuthenticationControllerBearerToken, }, ); From 831be3d852291b5bfe35033f8ca6f3085a96e163 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Thu, 3 Jul 2025 12:24:30 +0200 Subject: [PATCH 06/10] fix: update eslint-warning-thresholds.json --- eslint-warning-thresholds.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/eslint-warning-thresholds.json b/eslint-warning-thresholds.json index aa3ee4ad14f..a0921a5b9d0 100644 --- a/eslint-warning-thresholds.json +++ b/eslint-warning-thresholds.json @@ -74,13 +74,9 @@ "jsdoc/tag-lines": 11 }, "packages/assets-controllers/src/TokenDetectionController.test.ts": { - "import-x/namespace": 11, - "jsdoc/tag-lines": 1 + "import-x/namespace": 11 }, "packages/assets-controllers/src/TokenDetectionController.ts": { - "@typescript-eslint/prefer-readonly": 3, - "jsdoc/check-tag-names": 8, - "jsdoc/tag-lines": 6, "no-unused-private-class-members": 2 }, "packages/assets-controllers/src/TokenListController.test.ts": { @@ -112,9 +108,6 @@ "packages/assets-controllers/src/assetsUtil.ts": { "jsdoc/tag-lines": 2 }, - "packages/assets-controllers/src/multi-chain-accounts-service/multi-chain-accounts.ts": { - "jsdoc/tag-lines": 2 - }, "packages/assets-controllers/src/multicall.test.ts": { "@typescript-eslint/prefer-promise-reject-errors": 2 }, From 6dd48118b55dfd9adeb9018bf7afd14ff7d8e11c Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Thu, 3 Jul 2025 15:54:11 +0200 Subject: [PATCH 07/10] fix: update tsconfig.packages.json to account for profile-sync-controller auth subpath exports --- tsconfig.packages.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 8d0d5aee5ed..99309fc418d 100644 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -13,7 +13,8 @@ * `jest.config.packages.js`. */ "paths": { - "@metamask/*": ["../*/src"] + "@metamask/*": ["../*/src"], + "@metamask/*/auth": ["../*/src/auth"] }, "strict": true, "target": "ES2020" From d9822daf3e46b24a5b88e222ec0648e4ccac3083 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Thu, 3 Jul 2025 16:16:44 +0200 Subject: [PATCH 08/10] fix: UT + tsconfig.packages.json --- .../MultichainNetworkController.test.ts | 6 +++--- tsconfig.packages.json | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts index 1734935b424..fdf626b840b 100644 --- a/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts +++ b/packages/multichain-network-controller/src/MultichainNetworkController/MultichainNetworkController.test.ts @@ -659,11 +659,11 @@ describe('MultichainNetworkController', () => { expect(mockNetworkService.fetchNetworkActivity).toHaveBeenCalledWith( [`${KnownCaipNamespace.Eip155}:0:${MOCK_EVM_ADDRESS}`], - expect.any(Function), + { + getAuthenticationControllerBearerToken: expect.any(Function), + }, ); - // expect(mockAuthenticationControllerGetBearerToken).toHaveBeenCalled(); - expect(result).toStrictEqual({ [MOCK_EVM_ADDRESS]: { namespace: KnownCaipNamespace.Eip155, diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 99309fc418d..30a5d7e86df 100644 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -14,7 +14,9 @@ */ "paths": { "@metamask/*": ["../*/src"], - "@metamask/*/auth": ["../*/src/auth"] + "@metamask/profile-sync-controller/auth": [ + "../profile-sync-controller/src/controllers/authentication" + ] }, "strict": true, "target": "ES2020" From b6c920f6cb4db81a85b152fea6e20b40107dc526 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Tue, 8 Jul 2025 14:17:58 +0200 Subject: [PATCH 09/10] chore: bump profile-sync-controller to 20.0.0 --- packages/assets-controllers/package.json | 4 ++-- packages/multichain-network-controller/package.json | 4 ++-- packages/transaction-controller/package.json | 4 ++-- yarn.lock | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index f63c4087537..306b746703a 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -89,7 +89,7 @@ "@metamask/permission-controller": "^11.0.6", "@metamask/phishing-controller": "^12.6.0", "@metamask/preferences-controller": "^18.4.1", - "@metamask/profile-sync-controller": "^19.0.0", + "@metamask/profile-sync-controller": "^20.0.0", "@metamask/providers": "^22.1.0", "@metamask/snaps-controllers": "^14.0.1", "@metamask/transaction-controller": "^58.1.1", @@ -115,7 +115,7 @@ "@metamask/permission-controller": "^11.0.0", "@metamask/phishing-controller": "^12.5.0", "@metamask/preferences-controller": "^18.0.0", - "@metamask/profile-sync-controller": "^19.0.0", + "@metamask/profile-sync-controller": "^20.0.0", "@metamask/providers": "^22.0.0", "@metamask/snaps-controllers": "^14.0.0", "@metamask/transaction-controller": "^58.0.0", diff --git a/packages/multichain-network-controller/package.json b/packages/multichain-network-controller/package.json index 013db80f5ab..daa6814a12e 100644 --- a/packages/multichain-network-controller/package.json +++ b/packages/multichain-network-controller/package.json @@ -61,7 +61,7 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/keyring-controller": "^22.1.0", "@metamask/network-controller": "^24.0.0", - "@metamask/profile-sync-controller": "^19.0.0", + "@metamask/profile-sync-controller": "^20.0.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.191", "@types/uuid": "^8.3.0", @@ -77,7 +77,7 @@ "peerDependencies": { "@metamask/accounts-controller": "^31.0.0", "@metamask/network-controller": "^24.0.0", - "@metamask/profile-sync-controller": "^19.0.0" + "@metamask/profile-sync-controller": "^20.0.0" }, "engines": { "node": "^18.18 || >=20" diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 20c5ae11bce..41d7f133019 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -78,7 +78,7 @@ "@metamask/ethjs-provider-http": "^0.3.0", "@metamask/gas-fee-controller": "^24.0.0", "@metamask/network-controller": "^24.0.0", - "@metamask/profile-sync-controller": "^19.0.0", + "@metamask/profile-sync-controller": "^20.0.0", "@metamask/remote-feature-flag-controller": "^1.6.0", "@types/bn.js": "^5.1.5", "@types/jest": "^27.4.1", @@ -100,7 +100,7 @@ "@metamask/eth-block-tracker": ">=9", "@metamask/gas-fee-controller": "^24.0.0", "@metamask/network-controller": "^24.0.0", - "@metamask/profile-sync-controller": "^19.0.0", + "@metamask/profile-sync-controller": "^20.0.0", "@metamask/remote-feature-flag-controller": "^1.5.0" }, "engines": { diff --git a/yarn.lock b/yarn.lock index b1bcecc5ec1..2fe8280ccc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2632,7 +2632,7 @@ __metadata: "@metamask/phishing-controller": "npm:^12.6.0" "@metamask/polling-controller": "npm:^14.0.0" "@metamask/preferences-controller": "npm:^18.4.1" - "@metamask/profile-sync-controller": "npm:^19.0.0" + "@metamask/profile-sync-controller": "npm:^20.0.0" "@metamask/providers": "npm:^22.1.0" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/snaps-controllers": "npm:^14.0.1" @@ -2671,7 +2671,7 @@ __metadata: "@metamask/permission-controller": ^11.0.0 "@metamask/phishing-controller": ^12.5.0 "@metamask/preferences-controller": ^18.0.0 - "@metamask/profile-sync-controller": ^19.0.0 + "@metamask/profile-sync-controller": ^20.0.0 "@metamask/providers": ^22.0.0 "@metamask/snaps-controllers": ^14.0.0 "@metamask/transaction-controller": ^58.0.0 @@ -3821,7 +3821,7 @@ __metadata: "@metamask/keyring-controller": "npm:^22.1.0" "@metamask/keyring-internal-api": "npm:^6.2.0" "@metamask/network-controller": "npm:^24.0.0" - "@metamask/profile-sync-controller": "npm:^19.0.0" + "@metamask/profile-sync-controller": "npm:^20.0.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^11.4.2" "@solana/addresses": "npm:^2.0.0" @@ -3840,7 +3840,7 @@ __metadata: peerDependencies: "@metamask/accounts-controller": ^31.0.0 "@metamask/network-controller": ^24.0.0 - "@metamask/profile-sync-controller": ^19.0.0 + "@metamask/profile-sync-controller": ^20.0.0 languageName: unknown linkType: soft @@ -4593,7 +4593,7 @@ __metadata: "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^24.0.0" "@metamask/nonce-tracker": "npm:^6.0.0" - "@metamask/profile-sync-controller": "npm:^19.0.0" + "@metamask/profile-sync-controller": "npm:^20.0.0" "@metamask/remote-feature-flag-controller": "npm:^1.6.0" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/utils": "npm:^11.4.2" @@ -4622,7 +4622,7 @@ __metadata: "@metamask/eth-block-tracker": ">=9" "@metamask/gas-fee-controller": ^24.0.0 "@metamask/network-controller": ^24.0.0 - "@metamask/profile-sync-controller": ^19.0.0 + "@metamask/profile-sync-controller": ^20.0.0 "@metamask/remote-feature-flag-controller": ^1.5.0 languageName: unknown linkType: soft From 0ce01be8bb091fa174d6f99ac942ffeb970632d8 Mon Sep 17 00:00:00 2001 From: Mathieu Artu Date: Tue, 8 Jul 2025 20:40:33 +0200 Subject: [PATCH 10/10] fix: remove outdated TODO statements --- .../assets-controllers/src/TokenDetectionController.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index 3983d9c420f..9b0148de881 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -2616,11 +2616,7 @@ describe('TokenDetectionController', () => { category: 'Wallet', properties: { tokens: [`${sampleTokenA.symbol} - ${sampleTokenA.address}`], - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - token_standard: 'ERC20', - // TODO: Either fix this lint violation or explain why it's necessary to ignore. - asset_type: 'TOKEN', }, });