From 2d31ccecb8584366913deda26d6fb701ed6556c2 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 11 Jan 2024 10:48:41 -0500 Subject: [PATCH 01/33] [assets-controllers] Install `{accounts,keyring}-controller` as deps and peerDeps --- packages/assets-controllers/package.json | 4 ++++ packages/assets-controllers/tsconfig.build.json | 2 ++ packages/assets-controllers/tsconfig.json | 2 ++ yarn.lock | 6 +++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 23b82e5dba1..5d94618938c 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -36,11 +36,13 @@ "@ethersproject/contracts": "^5.7.0", "@ethersproject/providers": "^5.7.0", "@metamask/abi-utils": "^2.0.2", + "@metamask/accounts-controller": "^10.0.0", "@metamask/approval-controller": "^5.1.2", "@metamask/base-controller": "^4.1.1", "@metamask/contract-metadata": "^2.4.0", "@metamask/controller-utils": "^8.0.2", "@metamask/eth-query": "^4.0.0", + "@metamask/keyring-controller": "^12.2.0", "@metamask/metamask-eth-abis": "3.0.0", "@metamask/network-controller": "^17.2.0", "@metamask/polling-controller": "^5.0.0", @@ -73,7 +75,9 @@ "typescript": "~4.8.4" }, "peerDependencies": { + "@metamask/accounts-controller": "^10.0.0", "@metamask/approval-controller": "^5.1.2", + "@metamask/keyring-controller": "^12.2.0", "@metamask/network-controller": "^17.2.0", "@metamask/preferences-controller": "^7.0.0" }, diff --git a/packages/assets-controllers/tsconfig.build.json b/packages/assets-controllers/tsconfig.build.json index 93737886c16..5d38b996867 100644 --- a/packages/assets-controllers/tsconfig.build.json +++ b/packages/assets-controllers/tsconfig.build.json @@ -6,9 +6,11 @@ "rootDir": "./src" }, "references": [ + { "path": "../accounts-controller/tsconfig.build.json" }, { "path": "../approval-controller/tsconfig.build.json" }, { "path": "../base-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, + { "path": "../keyring-controller/tsconfig.build.json" }, { "path": "../network-controller/tsconfig.build.json" }, { "path": "../preferences-controller/tsconfig.build.json" }, { "path": "../polling-controller/tsconfig.build.json" } diff --git a/packages/assets-controllers/tsconfig.json b/packages/assets-controllers/tsconfig.json index 0dba0b3a1de..05bd347469b 100644 --- a/packages/assets-controllers/tsconfig.json +++ b/packages/assets-controllers/tsconfig.json @@ -5,9 +5,11 @@ "rootDir": "../.." }, "references": [ + { "path": "../accounts-controller" }, { "path": "../approval-controller" }, { "path": "../base-controller" }, { "path": "../controller-utils" }, + { "path": "../keyring-controller" }, { "path": "../network-controller" }, { "path": "../preferences-controller" }, { "path": "../polling-controller" } diff --git a/yarn.lock b/yarn.lock index c006c6a6ef7..aadd83e764c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1468,7 +1468,7 @@ __metadata: languageName: node linkType: hard -"@metamask/accounts-controller@workspace:packages/accounts-controller": +"@metamask/accounts-controller@^10.0.0, @metamask/accounts-controller@workspace:packages/accounts-controller": version: 0.0.0-use.local resolution: "@metamask/accounts-controller@workspace:packages/accounts-controller" dependencies: @@ -1572,6 +1572,7 @@ __metadata: "@ethersproject/contracts": ^5.7.0 "@ethersproject/providers": ^5.7.0 "@metamask/abi-utils": ^2.0.2 + "@metamask/accounts-controller": ^10.0.0 "@metamask/approval-controller": ^5.1.2 "@metamask/auto-changelog": ^3.4.4 "@metamask/base-controller": ^4.1.1 @@ -1579,6 +1580,7 @@ __metadata: "@metamask/controller-utils": ^8.0.2 "@metamask/eth-query": ^4.0.0 "@metamask/ethjs-provider-http": ^0.2.0 + "@metamask/keyring-controller": ^12.2.0 "@metamask/metamask-eth-abis": 3.0.0 "@metamask/network-controller": ^17.2.0 "@metamask/polling-controller": ^5.0.0 @@ -1606,7 +1608,9 @@ __metadata: typescript: ~4.8.4 uuid: ^8.3.2 peerDependencies: + "@metamask/accounts-controller": ^10.0.0 "@metamask/approval-controller": ^5.1.2 + "@metamask/keyring-controller": ^12.2.0 "@metamask/network-controller": ^17.2.0 "@metamask/preferences-controller": ^7.0.0 languageName: unknown From ea9602512ca67caa1ece1e72c29069805f5c69b3 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 11 Jan 2024 10:54:47 -0500 Subject: [PATCH 02/33] Define and apply `#restartTokenDetection` method --- .../src/TokenDetectionController.ts | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 19c4d90871d..429a04f897e 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -170,7 +170,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< const hasTokens = Object.keys(tokenList).length; if (hasTokens) { - await this.detectTokens(); + await this.#restartTokenDetection(); } }, ); @@ -189,7 +189,12 @@ export class TokenDetectionController extends StaticIntervalPollingController< useTokenDetection && (isSelectedAddressChanged || isDetectionChangedFromPreferences) ) { - await this.detectTokens(); + await this.#restartTokenDetection({ + selectedAddress: this.#selectedAddress, + }); + } + }, + ); } }, ); @@ -206,7 +211,9 @@ export class TokenDetectionController extends StaticIntervalPollingController< isTokenDetectionSupportedForNetwork(newChainId); if (this.#isDetectionEnabledForNetwork && isChainIdChanged) { - await this.detectTokens(); + await this.#restartTokenDetection({ + chainId: this.#chainId, + }); } }, ); @@ -284,6 +291,25 @@ export class TokenDetectionController extends StaticIntervalPollingController< }); } + /** + * Restart token detection polling period and call detectNewTokens + * in case of address change or user session initialization. + * + * @param options - Options for restart token detection. + * @param options.selectedAddress - the selectedAddress against which to detect for token balances + * @param options.chainId - the chainId against which to detect for token balances + */ + async #restartTokenDetection({ + selectedAddress, + chainId, + }: Partial<{ selectedAddress: string; chainId: Hex }> = {}) { + await this.detectTokens({ + accountAddress: selectedAddress, + networkClientId: chainId, + }); + this.setIntervalLength(DEFAULT_INTERVAL); + } + /** * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet. * From 8e204e67fd6207ca3a2744bf030e1b8d8d9b458c Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 11 Jan 2024 10:56:20 -0500 Subject: [PATCH 03/33] Subscribe to `AccountsController:selectedAccountChange` event --- .../src/TokenDetectionController.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 429a04f897e..5af30e962ff 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -1,3 +1,4 @@ +import type { AccountsControllerSelectedAccountChangeEvent } from '@metamask/accounts-controller'; import type { RestrictedControllerMessenger, ControllerGetStateAction, @@ -51,6 +52,7 @@ export type TokenDetectionControllerEvents = TokenDetectionControllerStateChangeEvent; export type AllowedEvents = + | AccountsControllerSelectedAccountChangeEvent | NetworkControllerStateChangeEvent | NetworkControllerNetworkDidChangeEvent | TokenListStateChange; @@ -195,6 +197,18 @@ export class TokenDetectionController extends StaticIntervalPollingController< } }, ); + + this.messagingSystem.subscribe( + 'AccountsController:selectedAccountChange', + async (account) => { + if ( + this.#selectedAddress !== account.address && + this.#isDetectionEnabledFromPreferences + ) { + this.#selectedAddress = account.address; + await this.#restartTokenDetection({ + selectedAddress: this.#selectedAddress, + }); } }, ); From 2485341ce04784046434fb00efbf23b850dbee28 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 11 Jan 2024 11:28:33 -0500 Subject: [PATCH 04/33] Define `#registerKeyringListeners`, `isActive` methods --- .../src/TokenDetectionController.ts | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 5af30e962ff..29a24cd6864 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -8,6 +8,11 @@ import { safelyExecute, toChecksumHexAddress, } from '@metamask/controller-utils'; +import type { + KeyringControllerGetStateAction, + KeyringControllerLockEvent, + KeyringControllerUnlockEvent, +} from '@metamask/keyring-controller'; import type { NetworkClientId, NetworkControllerNetworkDidChangeEvent, @@ -43,7 +48,8 @@ export type TokenDetectionControllerActions = export type AllowedActions = | NetworkControllerGetNetworkConfigurationByNetworkClientId - | GetTokenListState; + | GetTokenListState + | KeyringControllerGetStateAction; export type TokenDetectionControllerStateChangeEvent = ControllerStateChangeEvent; @@ -55,7 +61,9 @@ export type AllowedEvents = | AccountsControllerSelectedAccountChangeEvent | NetworkControllerStateChangeEvent | NetworkControllerNetworkDidChangeEvent - | TokenListStateChange; + | TokenListStateChange + | KeyringControllerLockEvent + | KeyringControllerUnlockEvent; export type TokenDetectionControllerMessenger = RestrictedControllerMessenger< typeof controllerName, @@ -72,6 +80,7 @@ export type TokenDetectionControllerMessenger = RestrictedControllerMessenger< * @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 */ @@ -90,6 +99,8 @@ export class TokenDetectionController extends StaticIntervalPollingController< #disabled: boolean; + #isUnlocked?: boolean; + #isDetectionEnabledFromPreferences: boolean; #isDetectionEnabledForNetwork: boolean; @@ -231,6 +242,8 @@ export class TokenDetectionController extends StaticIntervalPollingController< } }, ); + + this.#registerKeyringListeners(); } /** @@ -247,6 +260,35 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.#disabled = true; } + /** + * Internal isActive state + * + * @type {object} + */ + get isActive() { + return !this.#disabled && this.#isUnlocked; + } + + /** + * Constructor helper for subscribing listeners + * to the keyring locked state changes + */ + async #registerKeyringListeners() { + const { isUnlocked } = this.messagingSystem.call( + 'KeyringController:getState', + ); + this.#isUnlocked = isUnlocked; + + this.messagingSystem.subscribe('KeyringController:unlock', async () => { + this.#isUnlocked = true; + await this.#restartTokenDetection(); + }); + + this.messagingSystem.subscribe('KeyringController:lock', () => { + this.#isUnlocked = false; + }); + } + /** * Start polling for detected tokens. */ @@ -273,7 +315,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< * Starts a new polling interval. */ async #startPolling(): Promise { - if (this.#disabled) { + if (!this.isActive) { return; } this.#stopPolling(); @@ -296,7 +338,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< networkClientId: string, options: { address: string }, ): Promise { - if (this.#disabled) { + if (!this.isActive) { return; } await this.detectTokens({ @@ -339,7 +381,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< accountAddress?: string; } = {}): Promise { if ( - this.#disabled || + !this.isActive || !this.#isDetectionEnabledForNetwork || !this.#isDetectionEnabledFromPreferences ) { From 0f46fdcaf9c3270518fb4a500b266e47fb7b7dab Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 11 Jan 2024 11:35:16 -0500 Subject: [PATCH 05/33] Add `#trackMetaMetricsEvent` tracker property and call --- .../src/TokenDetectionController.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 29a24cd6864..3d9084600e2 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -111,6 +111,16 @@ export class TokenDetectionController extends StaticIntervalPollingController< readonly #getTokensState: () => TokensState; + readonly #trackMetaMetricsEvent: (options: { + event: string; + category: string; + properties: { + tokens: string[]; + token_standard: string; + asset_type: string; + }; + }) => void; + /** * Creates a TokenDetectionController instance. * @@ -125,6 +135,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address. * @param options.getTokensState - Gets the current state of the Tokens controller. * @param options.getPreferencesState - Gets the state of the preferences controller. + * @param options.trackMetaMetricsEvent - Sets options for MetaMetrics event tracking. */ constructor({ networkClientId, @@ -136,6 +147,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< addDetectedTokens, getPreferencesState, getTokensState, + trackMetaMetricsEvent, messenger, }: { networkClientId: NetworkClientId; @@ -149,6 +161,15 @@ export class TokenDetectionController extends StaticIntervalPollingController< getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall']; getTokensState: () => TokensState; getPreferencesState: () => PreferencesState; + trackMetaMetricsEvent: (options: { + event: string; + category: string; + properties: { + tokens: string[]; + token_standard: string; + asset_type: string; + }; + }) => void; messenger: TokenDetectionControllerMessenger; }) { const { useTokenDetection: defaultUseTokenDetection } = @@ -177,6 +198,8 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.#getBalancesInSingleCall = getBalancesInSingleCall; this.#getTokensState = getTokensState; + this.#trackMetaMetricsEvent = trackMetaMetricsEvent; + this.messagingSystem.subscribe( 'TokenListController:stateChange', async ({ tokenList }) => { @@ -388,7 +411,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< return; } const { tokens } = this.#getTokensState(); - const selectedAddress = accountAddress || this.#selectedAddress; + const selectedAddress = accountAddress ?? this.#selectedAddress; const chainId = this.#getCorrectChainId(networkClientId); const tokensAddresses = tokens.map( @@ -426,6 +449,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< tokensSlice, ); const tokensToAdd: Token[] = []; + const eventTokensDetails = []; for (const tokenAddress of Object.keys(balances)) { let ignored; /* istanbul ignore else */ @@ -444,6 +468,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< if (ignored === undefined) { const { decimals, symbol, aggregators, iconUrl, name } = tokenList[caseInsensitiveTokenKey]; + eventTokensDetails.push(`${symbol} - ${tokenAddress}`); tokensToAdd.push({ address: tokenAddress, decimals, @@ -457,6 +482,15 @@ export class TokenDetectionController extends StaticIntervalPollingController< } if (tokensToAdd.length) { + this.#trackMetaMetricsEvent({ + event: 'Token Detected', + category: 'Wallet', + properties: { + tokens: eventTokensDetails, + token_standard: 'ERC20', + asset_type: 'TOKEN', + }, + }); await this.#addDetectedTokens(tokensToAdd, { selectedAddress, chainId, From 129c82eeefde33fd52097e5e880d58eab4dc1a7c Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 11 Jan 2024 18:46:43 -0500 Subject: [PATCH 06/33] Use legacy token list from contract-metadata repo to perform auto-detection if active network is mainnet and token detection is disabled in preferences --- .../src/TokenDetectionController.ts | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 3d9084600e2..80f422bad7a 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -4,7 +4,9 @@ import type { ControllerGetStateAction, ControllerStateChangeEvent, } from '@metamask/base-controller'; +import contractMap from '@metamask/contract-metadata'; import { + ChainId, safelyExecute, toChecksumHexAddress, } from '@metamask/controller-utils'; @@ -28,12 +30,38 @@ import { isTokenDetectionSupportedForNetwork } from './assetsUtil'; import type { GetTokenListState, TokenListStateChange, + TokenListToken, } from './TokenListController'; import type { Token } from './TokenRatesController'; import type { TokensController, TokensState } from './TokensController'; const DEFAULT_INTERVAL = 180000; +type LegacyToken = Omit< + Token, + 'aggregators' | 'image' | 'balanceError' | 'isERC721' +> & { + name: string; + logo: string; + erc20?: boolean; + erc721?: boolean; +}; + +export const STATIC_MAINNET_TOKEN_LIST = Object.entries( + contractMap, +).reduce>>((acc, [base, contract]) => { + const { logo, ...tokenMetadata } = contract; + return { + ...acc, + [base.toLowerCase()]: { + ...tokenMetadata, + address: base.toLowerCase(), + iconUrl: `images/contract/${logo}`, + aggregators: [], + }, + }; +}, {}); + export const controllerName = 'TokenDetectionController'; export type TokenDetectionState = Record; @@ -390,11 +418,12 @@ export class TokenDetectionController extends StaticIntervalPollingController< } /** - * Triggers asset ERC20 token auto detection for each contract address in contract metadata on mainnet. + * For each token in the token list provided by the TokenListController, checks the token's balance for the selected account address on the active network. + * On mainnet, if token detection is disabled in preferences, ERC20 token auto detection will be triggered for each contract address in the legacy token list from the @metamask/contract-metadata repo. * - * @param options - Options to detect tokens. + * @param options - Options for token detection. * @param options.networkClientId - The ID of the network client to use. - * @param options.accountAddress - The account address to use. + * @param options.accountAddress - the selectedAddress against which to detect for token balances. */ async detectTokens({ networkClientId, @@ -403,25 +432,28 @@ export class TokenDetectionController extends StaticIntervalPollingController< networkClientId?: NetworkClientId; accountAddress?: string; } = {}): Promise { - if ( - !this.isActive || - !this.#isDetectionEnabledForNetwork || - !this.#isDetectionEnabledFromPreferences - ) { + if (!this.isActive || !this.#isDetectionEnabledForNetwork) { return; } - const { tokens } = this.#getTokensState(); const selectedAddress = accountAddress ?? this.#selectedAddress; const chainId = this.#getCorrectChainId(networkClientId); - const tokensAddresses = tokens.map( - /* istanbul ignore next*/ (token) => token.address.toLowerCase(), - ); + if ( + !this.#isDetectionEnabledFromPreferences && + chainId !== ChainId.mainnet + ) { + return; + } + const isTokenDetectionInactiveInMainnet = + !this.#isDetectionEnabledFromPreferences && chainId === ChainId.mainnet; const { tokenList } = this.messagingSystem.call( 'TokenListController:getState', ); + const tokenListUsed = isTokenDetectionInactiveInMainnet + ? STATIC_MAINNET_TOKEN_LIST + : tokenList; const tokensToDetect: string[] = []; - for (const address of Object.keys(tokenList)) { + for (const address of Object.keys(tokenListUsed)) { if (!tokensAddresses.includes(address)) { tokensToDetect.push(address); } @@ -461,7 +493,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< ); } const caseInsensitiveTokenKey = - Object.keys(tokenList).find( + Object.keys(tokenListUsed).find( (i) => i.toLowerCase() === tokenAddress.toLowerCase(), ) ?? ''; From 33818aa5f7501b2c925cea3db862a97aef356ddf Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 11 Jan 2024 18:53:02 -0500 Subject: [PATCH 07/33] Exclude `detectedTokens` already stored in tokens-controller state from `tokensToDetect` --- .../src/TokenDetectionController.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 80f422bad7a..99e1f0fe7e9 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -452,10 +452,25 @@ export class TokenDetectionController extends StaticIntervalPollingController< const tokenListUsed = isTokenDetectionInactiveInMainnet ? STATIC_MAINNET_TOKEN_LIST : tokenList; + + const { tokens, detectedTokens } = this.#getTokensState(); const tokensToDetect: string[] = []; - for (const address of Object.keys(tokenListUsed)) { - if (!tokensAddresses.includes(address)) { - tokensToDetect.push(address); + + const findCaseInsensitiveMatch = (source: string[], target: string) => + source.find((e: string) => e.toLowerCase() === target.toLowerCase()); + + for (const tokenAddress of Object.keys(tokenListUsed)) { + if ( + !findCaseInsensitiveMatch( + tokens.map(({ address }) => address), + tokenAddress, + ) && + !findCaseInsensitiveMatch( + detectedTokens.map(({ address }) => address), + tokenAddress, + ) + ) { + tokensToDetect.push(tokenAddress); } } const sliceOfTokensToDetect = []; @@ -493,8 +508,9 @@ export class TokenDetectionController extends StaticIntervalPollingController< ); } const caseInsensitiveTokenKey = - Object.keys(tokenListUsed).find( - (i) => i.toLowerCase() === tokenAddress.toLowerCase(), + findCaseInsensitiveMatch( + Object.keys(tokenListUsed), + tokenAddress, ) ?? ''; if (ignored === undefined) { From 5df6b8101d4a7c705abf10ffc315279c890c41bb Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 12 Jan 2024 14:33:07 -0500 Subject: [PATCH 08/33] test: add actions, events, callbacks to test messenger --- .../src/TokenDetectionController.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index 2f68b71b903..afbc42854a5 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -133,10 +133,14 @@ function buildTokenDetectionControllerMessenger( return controllerMessenger.getRestricted({ name: controllerName, allowedActions: [ + 'KeyringController:getState', 'NetworkController:getNetworkConfigurationByNetworkClientId', 'TokenListController:getState', ], allowedEvents: [ + 'AccountsController:selectedAccountChange', + 'KeyringController:lock', + 'KeyringController:unlock', 'NetworkController:stateChange', 'NetworkController:networkDidChange', 'TokenListController:stateChange', @@ -1297,6 +1301,12 @@ async function withController( .mockImplementation((networkClientId: string) => { return mockNetworkConfigurations[networkClientId]; }); + controllerMessenger.registerActionHandler( + 'KeyringController:getState', + jest.fn().mockReturnValue({ + isActive: true, + }), + ); controllerMessenger.registerActionHandler( 'NetworkController:getNetworkConfigurationByNetworkClientId', mockGetNetworkConfigurationByNetworkClientId, @@ -1323,6 +1333,7 @@ async function withController( ...getDefaultPreferencesState(), useTokenDetection: true, }), + trackMetaMetricsEvent: jest.fn(), messenger: buildTokenDetectionControllerMessenger(controllerMessenger), ...options, }); From 3fd21385bdc0a68d4fb6ab8285bcea17d7c62884 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 16 Jan 2024 19:39:20 -0500 Subject: [PATCH 09/33] test: Fix incorrect mock for `KeyringController:getState` --- .../src/TokenDetectionController.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index afbc42854a5..1a70fe7d714 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -6,6 +6,7 @@ import { convertHexToDecimal, BUILT_IN_NETWORKS, } from '@metamask/controller-utils'; +import type { KeyringControllerState } from '@metamask/keyring-controller'; import { defaultState as defaultNetworkState, type NetworkConfiguration, @@ -1303,9 +1304,9 @@ async function withController( }); controllerMessenger.registerActionHandler( 'KeyringController:getState', - jest.fn().mockReturnValue({ - isActive: true, - }), + jest.fn().mockReturnValue({ + isUnlocked: true, + } as unknown as KeyringControllerState), ); controllerMessenger.registerActionHandler( 'NetworkController:getNetworkConfigurationByNetworkClientId', From 78e9fde10578da301ea9566b64902c6175a1f9c7 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 16 Jan 2024 19:56:10 -0500 Subject: [PATCH 10/33] test: adjust coverage thresholds --- packages/assets-controllers/jest.config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/jest.config.js b/packages/assets-controllers/jest.config.js index 6d35a70f3ac..a01442c1db5 100644 --- a/packages/assets-controllers/jest.config.js +++ b/packages/assets-controllers/jest.config.js @@ -18,9 +18,9 @@ module.exports = merge(baseConfig, { coverageThreshold: { global: { branches: 88.36, - functions: 97.08, - lines: 97.23, - statements: 97.28, + functions: 95.32, + lines: 96.82, + statements: 96.83, }, }, From adc8dca3397742b3c01b5d5154286a5f7fae9ae7 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Wed, 17 Jan 2024 16:09:45 -0500 Subject: [PATCH 11/33] Make `#isUnlocked` property non-optional --- .../src/TokenDetectionController.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 99e1f0fe7e9..c57fe03cb00 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -127,7 +127,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< #disabled: boolean; - #isUnlocked?: boolean; + #isUnlocked: boolean; #isDetectionEnabledFromPreferences: boolean; @@ -228,6 +228,11 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.#trackMetaMetricsEvent = trackMetaMetricsEvent; + const { isUnlocked } = this.messagingSystem.call( + 'KeyringController:getState', + ); + this.#isUnlocked = isUnlocked; + this.messagingSystem.subscribe( 'TokenListController:stateChange', async ({ tokenList }) => { @@ -325,11 +330,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< * to the keyring locked state changes */ async #registerKeyringListeners() { - const { isUnlocked } = this.messagingSystem.call( - 'KeyringController:getState', - ); - this.#isUnlocked = isUnlocked; - this.messagingSystem.subscribe('KeyringController:unlock', async () => { this.#isUnlocked = true; await this.#restartTokenDetection(); From 7016835516bdb72600597acf62a93d4738b82036 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Wed, 17 Jan 2024 17:04:04 -0500 Subject: [PATCH 12/33] Define `#registerEventListeners` constructor helper private method --- .../src/TokenDetectionController.ts | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index c57fe03cb00..0aae7de2e60 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -133,6 +133,10 @@ export class TokenDetectionController extends StaticIntervalPollingController< #isDetectionEnabledForNetwork: boolean; + readonly #onPreferencesStateChange: ( + listener: (preferencesState: PreferencesState) => Promise, + ) => void; + readonly #addDetectedTokens: TokensController['addDetectedTokens']; readonly #getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall']; @@ -158,11 +162,11 @@ export class TokenDetectionController extends StaticIntervalPollingController< * @param options.interval - Polling interval used to fetch new token rates * @param options.networkClientId - The selected network client ID of the current network * @param options.selectedAddress - Vault selected address + * @param options.getPreferencesState - Gets the state of the preferences controller. * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. * @param options.addDetectedTokens - Add a list of detected tokens. * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address. * @param options.getTokensState - Gets the current state of the Tokens controller. - * @param options.getPreferencesState - Gets the state of the preferences controller. * @param options.trackMetaMetricsEvent - Sets options for MetaMetrics event tracking. */ constructor({ @@ -182,13 +186,13 @@ export class TokenDetectionController extends StaticIntervalPollingController< selectedAddress?: string; interval?: number; disabled?: boolean; + getPreferencesState: () => PreferencesState; onPreferencesStateChange: ( - listener: (preferencesState: PreferencesState) => void, + listener: (preferencesState: PreferencesState) => Promise, ) => void; addDetectedTokens: TokensController['addDetectedTokens']; getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall']; getTokensState: () => TokensState; - getPreferencesState: () => PreferencesState; trackMetaMetricsEvent: (options: { event: string; category: string; @@ -222,6 +226,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.#chainId, ); + this.#onPreferencesStateChange = onPreferencesStateChange; this.#addDetectedTokens = addDetectedTokens; this.#getBalancesInSingleCall = getBalancesInSingleCall; this.#getTokensState = getTokensState; @@ -233,6 +238,22 @@ export class TokenDetectionController extends StaticIntervalPollingController< ); this.#isUnlocked = isUnlocked; + this.#registerEventListeners(); + } + + /** + * Constructor helper for registering this controller's messaging system subscriptions to controller events. + */ + #registerEventListeners() { + this.messagingSystem.subscribe('KeyringController:unlock', async () => { + this.#isUnlocked = true; + await this.#restartTokenDetection(); + }); + + this.messagingSystem.subscribe('KeyringController:lock', () => { + this.#isUnlocked = false; + }); + this.messagingSystem.subscribe( 'TokenListController:stateChange', async ({ tokenList }) => { @@ -244,7 +265,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< }, ); - onPreferencesStateChange( + this.#onPreferencesStateChange( async ({ selectedAddress: newSelectedAddress, useTokenDetection }) => { const isSelectedAddressChanged = this.#selectedAddress !== newSelectedAddress; @@ -298,8 +319,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< } }, ); - - this.#registerKeyringListeners(); } /** @@ -325,21 +344,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< return !this.#disabled && this.#isUnlocked; } - /** - * Constructor helper for subscribing listeners - * to the keyring locked state changes - */ - async #registerKeyringListeners() { - this.messagingSystem.subscribe('KeyringController:unlock', async () => { - this.#isUnlocked = true; - await this.#restartTokenDetection(); - }); - - this.messagingSystem.subscribe('KeyringController:lock', () => { - this.#isUnlocked = false; - }); - } - /** * Start polling for detected tokens. */ From a44ccf72e108404d1599be2dd85de8d344234dc3 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 18 Jan 2024 20:31:40 -0500 Subject: [PATCH 13/33] Add yarn resolutions entry to resolve conflicting `@metamask/providers` versions used by `@metamask/accounts-controller` --- package.json | 3 +++ yarn.lock | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 53882704999..d97910e824c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,9 @@ "simple-git-hooks": { "pre-push": "yarn lint" }, + "resolutions": { + "@metamask/providers": "^14.0.2" + }, "devDependencies": { "@babel/core": "^7.23.5", "@babel/plugin-transform-modules-commonjs": "^7.23.3", diff --git a/yarn.lock b/yarn.lock index aadd83e764c..e8b43b20d1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2541,7 +2541,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/providers@npm:^14.0.1, @metamask/providers@npm:^14.0.2": +"@metamask/providers@npm:^14.0.2": version: 14.0.2 resolution: "@metamask/providers@npm:14.0.2" dependencies: @@ -3808,6 +3808,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: ^5.0.0 + checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75 + languageName: node + linkType: hard + "acorn-globals@npm:^6.0.0": version: 6.0.0 resolution: "acorn-globals@npm:6.0.0" @@ -6075,6 +6084,20 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166 + languageName: node + linkType: hard + +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 + languageName: node + linkType: hard + "evp_bytestokey@npm:^1.0.3": version: 1.0.3 resolution: "evp_bytestokey@npm:1.0.3" @@ -9499,6 +9522,13 @@ __metadata: languageName: node linkType: hard +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: bfcce49814f7d172a6e6a14d5fa3ac92cc3d0c3b9feb1279774708a719e19acd673995226351a082a9ae99978254e320ccda4240ddc474ba31a76c79491ca7c3 + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -9618,7 +9648,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3.6.2, readable-stream@npm:^3.0.2, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2, readable-stream@npm:^3.6.2 || ^4.4.2": +"readable-stream@npm:3.6.2, readable-stream@npm:^3.0.2, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -9629,6 +9659,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^3.6.2 || ^4.4.2": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + string_decoder: ^1.3.0 + checksum: c4030ccff010b83e4f33289c535f7830190773e274b3fcb6e2541475070bdfd69c98001c3b0cb78763fc00c8b62f514d96c2b10a8bd35d5ce45203a25fa1d33a + languageName: node + linkType: hard + "readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" @@ -10368,7 +10411,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: From 3fbdffab8cf64c0eb028745037996f1503bccfa3 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 19 Jan 2024 21:25:37 -0500 Subject: [PATCH 14/33] Add `@metamask/keyring-api` as devDep (for `InternalAccount` type in tests) --- packages/assets-controllers/package.json | 1 + yarn.lock | 184 ++++++++++++++++++++++- 2 files changed, 182 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 5d94618938c..18459a9443b 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -61,6 +61,7 @@ "devDependencies": { "@metamask/auto-changelog": "^3.4.4", "@metamask/ethjs-provider-http": "^0.2.0", + "@metamask/keyring-api": "^1.1.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.191", "@types/node": "^16.18.54", diff --git a/yarn.lock b/yarn.lock index e8b43b20d1c..9c058746682 100644 --- a/yarn.lock +++ b/yarn.lock @@ -550,6 +550,13 @@ __metadata: languageName: node linkType: hard +"@endo/env-options@npm:^0.1.4": + version: 0.1.4 + resolution: "@endo/env-options@npm:0.1.4" + checksum: 6099f0a6b700a60bee7b226aa2a39bb5748e22f25e9606d70e5a66a8e62cbd8c972b0fe578735a658f80bf2ebece62e28c20aa3f16417cbfe6c19a8689966dd3 + languageName: node + linkType: hard + "@endo/env-options@npm:^1.1.0": version: 1.1.0 resolution: "@endo/env-options@npm:1.1.0" @@ -1563,6 +1570,19 @@ __metadata: languageName: unknown linkType: soft +"@metamask/approval-controller@npm:^4.1.0": + version: 4.1.0 + resolution: "@metamask/approval-controller@npm:4.1.0" + dependencies: + "@metamask/base-controller": ^3.2.3 + "@metamask/rpc-errors": ^6.1.0 + "@metamask/utils": ^8.1.0 + immer: ^9.0.6 + nanoid: ^3.1.31 + checksum: b75c900fc656cfc141f8954ccb48346970d561ba83852ec1d27cecddb6606033e03ea560d7253847bd09dfb8317548c9be9cb92c50d906e55134d892d3785806 + languageName: node + linkType: hard + "@metamask/assets-controllers@workspace:packages/assets-controllers": version: 0.0.0-use.local resolution: "@metamask/assets-controllers@workspace:packages/assets-controllers" @@ -1580,6 +1600,7 @@ __metadata: "@metamask/controller-utils": ^8.0.2 "@metamask/eth-query": ^4.0.0 "@metamask/ethjs-provider-http": ^0.2.0 + "@metamask/keyring-api": ^1.1.0 "@metamask/keyring-controller": ^12.2.0 "@metamask/metamask-eth-abis": 3.0.0 "@metamask/network-controller": ^17.2.0 @@ -1665,6 +1686,16 @@ __metadata: languageName: unknown linkType: soft +"@metamask/base-controller@npm:^3.2.0, @metamask/base-controller@npm:^3.2.3": + version: 3.2.3 + resolution: "@metamask/base-controller@npm:3.2.3" + dependencies: + "@metamask/utils": ^8.1.0 + immer: ^9.0.6 + checksum: f49fcf2bf892ec25657c2d72a50b3c4f3cad59acb1b74d9fdcdf564107b8f38f73647c696aaa9699d94828b5797d8f1479dab44a2dbcda987c268b0088bb3b76 + languageName: node + linkType: hard + "@metamask/browser-passworder@npm:^4.3.0": version: 4.3.0 resolution: "@metamask/browser-passworder@npm:4.3.0" @@ -1741,6 +1772,21 @@ __metadata: languageName: unknown linkType: soft +"@metamask/controller-utils@npm:^5.0.2": + version: 5.0.2 + resolution: "@metamask/controller-utils@npm:5.0.2" + dependencies: + "@metamask/eth-query": ^3.0.1 + "@metamask/utils": ^8.1.0 + "@spruceid/siwe-parser": 1.1.3 + eth-ens-namehash: ^2.0.8 + ethereumjs-util: ^7.0.10 + ethjs-unit: ^0.1.6 + fast-deep-equal: ^3.1.3 + checksum: 2345ab9ee0ba900fe2249d80009acfcf458bc60b30418234d00f5f04247b1182a585050572237f8ab09aa23032a24b99ad96399fc0798a0e9a114a29c3bf90d6 + languageName: node + linkType: hard + "@metamask/core-monorepo@workspace:.": version: 0.0.0-use.local resolution: "@metamask/core-monorepo@workspace:." @@ -1946,6 +1992,16 @@ __metadata: languageName: unknown linkType: soft +"@metamask/eth-query@npm:^3.0.1": + version: 3.0.1 + resolution: "@metamask/eth-query@npm:3.0.1" + dependencies: + json-rpc-random-id: ^1.0.0 + xtend: ^4.0.1 + checksum: b9a323dff67328eace7d54fc8b0bc4dd763bf15760870656cbd5aad5380d1ee4489fb5c59506290d5f77cf55e74e530ee97b52702a329f1090ec03a6158434b7 + languageName: node + linkType: hard + "@metamask/eth-query@npm:^4.0.0": version: 4.0.0 resolution: "@metamask/eth-query@npm:4.0.0" @@ -2134,7 +2190,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/json-rpc-engine@^7.1.1, @metamask/json-rpc-engine@^7.3.1, @metamask/json-rpc-engine@^7.3.2, @metamask/json-rpc-engine@workspace:packages/json-rpc-engine": +"@metamask/json-rpc-engine@^7.1.1, @metamask/json-rpc-engine@^7.3.0, @metamask/json-rpc-engine@^7.3.1, @metamask/json-rpc-engine@^7.3.2, @metamask/json-rpc-engine@workspace:packages/json-rpc-engine": version: 0.0.0-use.local resolution: "@metamask/json-rpc-engine@workspace:packages/json-rpc-engine" dependencies: @@ -2190,6 +2246,22 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-api@npm:^1.1.0": + version: 1.1.0 + resolution: "@metamask/keyring-api@npm:1.1.0" + dependencies: + "@metamask/providers": ^13.0.0 + "@metamask/snaps-controllers": ^3.1.0 + "@metamask/snaps-rpc-methods": ^3.1.0 + "@metamask/snaps-utils": ^3.0.0 + "@metamask/utils": ^8.1.0 + "@types/uuid": ^9.0.1 + superstruct: ^1.0.3 + uuid: ^9.0.0 + checksum: dd07db768861a4c4b9b5168dedac104c7e9a8fdd1de5520f81bf276f9923ea55f649d57e8eee1aa5dda06b6b82f163ec5447e32c614e920063baea06e130ecea + languageName: node + linkType: hard + "@metamask/keyring-api@npm:^2.0.0": version: 2.0.0 resolution: "@metamask/keyring-api@npm:2.0.0" @@ -2396,6 +2468,26 @@ __metadata: languageName: node linkType: hard +"@metamask/permission-controller@npm:^5.0.0": + version: 5.0.1 + resolution: "@metamask/permission-controller@npm:5.0.1" + dependencies: + "@metamask/approval-controller": ^4.1.0 + "@metamask/base-controller": ^3.2.3 + "@metamask/controller-utils": ^5.0.2 + "@metamask/json-rpc-engine": ^7.3.0 + "@metamask/rpc-errors": ^6.1.0 + "@metamask/utils": ^8.2.0 + "@types/deep-freeze-strict": ^1.1.0 + deep-freeze-strict: ^1.1.1 + immer: ^9.0.6 + nanoid: ^3.1.31 + peerDependencies: + "@metamask/approval-controller": ^4.1.0 + checksum: fc61df3f5532b35b9ec26ca712848d680d616103e3d06470691412ee8b5a4b70e27d530065f601b64e0a5c2022aa129b8e6ddcc7c3e8325720aa0f639e3e10ba + languageName: node + linkType: hard + "@metamask/permission-controller@npm:^7.0.0, @metamask/permission-controller@npm:^7.1.0": version: 7.1.0 resolution: "@metamask/permission-controller@npm:7.1.0" @@ -2706,7 +2798,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^3.4.1": +"@metamask/snaps-controllers@npm:^3.1.0, @metamask/snaps-controllers@npm:^3.4.1": version: 3.6.0 resolution: "@metamask/snaps-controllers@npm:3.6.0" dependencies: @@ -2778,6 +2870,17 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-registry@npm:^2.1.0": + version: 2.1.1 + resolution: "@metamask/snaps-registry@npm:2.1.1" + dependencies: + "@metamask/utils": ^8.1.0 + "@noble/secp256k1": ^1.7.1 + superstruct: ^1.0.3 + checksum: 274002c44f0fe028740c19d1014f844aa4b534862abc530f872baaad8b7b3b2445ffaefbd0a5a957b5102a5b04fe51e6f03a03b1236c8818abc4c748076d6475 + languageName: node + linkType: hard + "@metamask/snaps-registry@npm:^3.0.0": version: 3.0.0 resolution: "@metamask/snaps-registry@npm:3.0.0" @@ -2790,6 +2893,22 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-rpc-methods@npm:^3.1.0": + version: 3.3.0 + resolution: "@metamask/snaps-rpc-methods@npm:3.3.0" + dependencies: + "@metamask/key-tree": ^9.0.0 + "@metamask/permission-controller": ^5.0.0 + "@metamask/rpc-errors": ^6.1.0 + "@metamask/snaps-ui": ^3.1.0 + "@metamask/snaps-utils": ^3.3.0 + "@metamask/utils": ^8.1.0 + "@noble/hashes": ^1.3.1 + superstruct: ^1.0.3 + checksum: 5f830b22db427b4109411632fdd5108ff9c151a819bba35b9bfd0ac6cc70ff581407c91b2ffb34ee0f624869d65054bfde88396e2df13d3052262e29dabbaa05 + languageName: node + linkType: hard + "@metamask/snaps-rpc-methods@npm:^4.1.0": version: 4.1.0 resolution: "@metamask/snaps-rpc-methods@npm:4.1.0" @@ -2836,6 +2955,46 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-ui@npm:^3.1.0": + version: 3.1.0 + resolution: "@metamask/snaps-ui@npm:3.1.0" + dependencies: + "@metamask/utils": ^8.1.0 + is-svg: ^4.4.0 + superstruct: ^1.0.3 + checksum: a234217e961a103b89708d46732481e82c0778b3ebbecddabc9351eee48d082cdc231102442c3ae5c76aa33b24c24aad70e84fee9d7b492641f290a10c3211c1 + languageName: node + linkType: hard + +"@metamask/snaps-utils@npm:^3.0.0, @metamask/snaps-utils@npm:^3.3.0": + version: 3.3.0 + resolution: "@metamask/snaps-utils@npm:3.3.0" + dependencies: + "@babel/core": ^7.23.2 + "@babel/types": ^7.23.0 + "@metamask/base-controller": ^3.2.0 + "@metamask/key-tree": ^9.0.0 + "@metamask/permission-controller": ^5.0.0 + "@metamask/rpc-errors": ^6.1.0 + "@metamask/snaps-registry": ^2.1.0 + "@metamask/snaps-ui": ^3.1.0 + "@metamask/utils": ^8.1.0 + "@noble/hashes": ^1.3.1 + "@scure/base": ^1.1.1 + chalk: ^4.1.2 + cron-parser: ^4.5.0 + fast-deep-equal: ^3.1.3 + fast-json-stable-stringify: ^2.1.0 + is-svg: ^4.4.0 + rfdc: ^1.3.0 + semver: ^7.5.4 + ses: ^0.18.8 + superstruct: ^1.0.3 + validate-npm-package-name: ^5.0.0 + checksum: 3f106d8e290bc260ec0b7f8bcaff5077bd0505f586d3961ad953bae53bc12bc1dc3c4c666c15b58135489ff0b1b47d9630848842e0deee754527652a7ab77bbb + languageName: node + linkType: hard + "@metamask/snaps-utils@npm:^5.0.0, @metamask/snaps-utils@npm:^5.1.1, @metamask/snaps-utils@npm:^5.1.2, @metamask/snaps-utils@npm:^5.2.0": version: 5.2.0 resolution: "@metamask/snaps-utils@npm:5.2.0" @@ -3050,7 +3209,7 @@ __metadata: languageName: node linkType: hard -"@noble/secp256k1@npm:^1.5.5": +"@noble/secp256k1@npm:^1.5.5, @noble/secp256k1@npm:^1.7.1": version: 1.7.1 resolution: "@noble/secp256k1@npm:1.7.1" checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb @@ -6084,6 +6243,16 @@ __metadata: languageName: node linkType: hard +"ethjs-unit@npm:^0.1.6": + version: 0.1.6 + resolution: "ethjs-unit@npm:0.1.6" + dependencies: + bn.js: 4.11.6 + number-to-bn: 1.7.0 + checksum: df6b4752ff7461a59a20219f4b1684c631ea601241c39660e3f6c6bd63c950189723841c22b3c6c0ebeb3c9fc99e0e803e3c613101206132603705fcbcf4def5 + languageName: node + linkType: hard + "event-target-shim@npm:^5.0.0": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -10027,6 +10196,15 @@ __metadata: languageName: node linkType: hard +"ses@npm:^0.18.8": + version: 0.18.8 + resolution: "ses@npm:0.18.8" + dependencies: + "@endo/env-options": ^0.1.4 + checksum: d7976d2ee218baec021c5cfdfb193d63b52bf2b6cbdbbb90c19d835915a1872b6924910f7fd42bc849eb2de78fc7bdd6e7b4667e1df3c79244cc92d4ede48aa6 + languageName: node + linkType: hard + "ses@npm:^1.1.0": version: 1.1.0 resolution: "ses@npm:1.1.0" From b5bd0e823cd750ace131c31692eaa20a42d5a1d3 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 19 Jan 2024 21:29:42 -0500 Subject: [PATCH 15/33] Remove unused `NetworkControllerStateChange` event - **BREAKING**: use `NetworkControllerNetworkDidChangeEvent` instead --- packages/assets-controllers/src/TokenDetectionController.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 0aae7de2e60..9d0306dabfd 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -18,7 +18,6 @@ import type { import type { NetworkClientId, NetworkControllerNetworkDidChangeEvent, - NetworkControllerStateChangeEvent, NetworkControllerGetNetworkConfigurationByNetworkClientId, } from '@metamask/network-controller'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; @@ -87,7 +86,6 @@ export type TokenDetectionControllerEvents = export type AllowedEvents = | AccountsControllerSelectedAccountChangeEvent - | NetworkControllerStateChangeEvent | NetworkControllerNetworkDidChangeEvent | TokenListStateChange | KeyringControllerLockEvent From beacf9b97b7a3ee8c484ea07093d2d6eb1da32a7 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 19 Jan 2024 21:31:23 -0500 Subject: [PATCH 16/33] Replace preferences controller callbacks in constructor with `getState` action and `stateChange` event --- .../src/TokenDetectionController.ts | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 9d0306dabfd..301aac0ac68 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -21,7 +21,10 @@ import type { NetworkControllerGetNetworkConfigurationByNetworkClientId, } from '@metamask/network-controller'; import { StaticIntervalPollingController } from '@metamask/polling-controller'; -import type { PreferencesState } from '@metamask/preferences-controller'; +import type { + PreferencesControllerGetStateAction, + PreferencesControllerStateChangeEvent, +} from '@metamask/preferences-controller'; import type { Hex } from '@metamask/utils'; import type { AssetsContractController } from './AssetsContractController'; @@ -76,7 +79,8 @@ export type TokenDetectionControllerActions = export type AllowedActions = | NetworkControllerGetNetworkConfigurationByNetworkClientId | GetTokenListState - | KeyringControllerGetStateAction; + | KeyringControllerGetStateAction + | PreferencesControllerGetStateAction; export type TokenDetectionControllerStateChangeEvent = ControllerStateChangeEvent; @@ -89,7 +93,8 @@ export type AllowedEvents = | NetworkControllerNetworkDidChangeEvent | TokenListStateChange | KeyringControllerLockEvent - | KeyringControllerUnlockEvent; + | KeyringControllerUnlockEvent + | PreferencesControllerStateChangeEvent; export type TokenDetectionControllerMessenger = RestrictedControllerMessenger< typeof controllerName, @@ -131,10 +136,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< #isDetectionEnabledForNetwork: boolean; - readonly #onPreferencesStateChange: ( - listener: (preferencesState: PreferencesState) => Promise, - ) => void; - readonly #addDetectedTokens: TokensController['addDetectedTokens']; readonly #getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall']; @@ -160,8 +161,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< * @param options.interval - Polling interval used to fetch new token rates * @param options.networkClientId - The selected network client ID of the current network * @param options.selectedAddress - Vault selected address - * @param options.getPreferencesState - Gets the state of the preferences controller. - * @param options.onPreferencesStateChange - Allows subscribing to preferences controller state changes. * @param options.addDetectedTokens - Add a list of detected tokens. * @param options.getBalancesInSingleCall - Gets the balances of a list of tokens for the given address. * @param options.getTokensState - Gets the current state of the Tokens controller. @@ -172,10 +171,8 @@ export class TokenDetectionController extends StaticIntervalPollingController< selectedAddress = '', interval = DEFAULT_INTERVAL, disabled = true, - onPreferencesStateChange, getBalancesInSingleCall, addDetectedTokens, - getPreferencesState, getTokensState, trackMetaMetricsEvent, messenger, @@ -184,10 +181,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< selectedAddress?: string; interval?: number; disabled?: boolean; - getPreferencesState: () => PreferencesState; - onPreferencesStateChange: ( - listener: (preferencesState: PreferencesState) => Promise, - ) => void; addDetectedTokens: TokensController['addDetectedTokens']; getBalancesInSingleCall: AssetsContractController['getBalancesInSingleCall']; getTokensState: () => TokensState; @@ -202,9 +195,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< }) => void; messenger: TokenDetectionControllerMessenger; }) { - const { useTokenDetection: defaultUseTokenDetection } = - getPreferencesState(); - super({ name: controllerName, messenger, @@ -219,12 +209,13 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.#selectedAddress = selectedAddress; this.#chainId = this.#getCorrectChainId(networkClientId); + const { useTokenDetection: defaultUseTokenDetection } = + this.messagingSystem.call('PreferencesController:getState'); this.#isDetectionEnabledFromPreferences = defaultUseTokenDetection; this.#isDetectionEnabledForNetwork = isTokenDetectionSupportedForNetwork( this.#chainId, ); - this.#onPreferencesStateChange = onPreferencesStateChange; this.#addDetectedTokens = addDetectedTokens; this.#getBalancesInSingleCall = getBalancesInSingleCall; this.#getTokensState = getTokensState; @@ -263,7 +254,8 @@ export class TokenDetectionController extends StaticIntervalPollingController< }, ); - this.#onPreferencesStateChange( + this.messagingSystem.subscribe( + 'PreferencesController:stateChange', async ({ selectedAddress: newSelectedAddress, useTokenDetection }) => { const isSelectedAddressChanged = this.#selectedAddress !== newSelectedAddress; From 4b303b4b8a0bd7e4aa936ae984638cc52f433db6 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 19 Jan 2024 21:32:46 -0500 Subject: [PATCH 17/33] Minor refactors to event listeners for consistency --- .../src/TokenDetectionController.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 301aac0ac68..ad770521e0f 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -278,12 +278,14 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.messagingSystem.subscribe( 'AccountsController:selectedAccountChange', - async (account) => { + async ({ address: newSelectedAddress }) => { + const isSelectedAddressChanged = + this.#selectedAddress !== newSelectedAddress; if ( - this.#selectedAddress !== account.address && + isSelectedAddressChanged && this.#isDetectionEnabledFromPreferences ) { - this.#selectedAddress = account.address; + this.#selectedAddress = newSelectedAddress; await this.#restartTokenDetection({ selectedAddress: this.#selectedAddress, }); @@ -297,12 +299,12 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.#networkClientId = selectedNetworkClientId; const newChainId = this.#getCorrectChainId(selectedNetworkClientId); const isChainIdChanged = this.#chainId !== newChainId; - this.#chainId = newChainId; this.#isDetectionEnabledForNetwork = isTokenDetectionSupportedForNetwork(newChainId); - if (this.#isDetectionEnabledForNetwork && isChainIdChanged) { + if (isChainIdChanged && this.#isDetectionEnabledForNetwork) { + this.#chainId = newChainId; await this.#restartTokenDetection({ chainId: this.#chainId, }); From ad11ac7c384168dd2731b01c97fe541dc3c9bd5a Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 19 Jan 2024 21:36:59 -0500 Subject: [PATCH 18/33] test: adjust action,event allowlist in `buildTokenDetectionControllerMessenger` --- .../assets-controllers/src/TokenDetectionController.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index 1a70fe7d714..a0fe8d5ee36 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -137,14 +137,15 @@ function buildTokenDetectionControllerMessenger( 'KeyringController:getState', 'NetworkController:getNetworkConfigurationByNetworkClientId', 'TokenListController:getState', + 'PreferencesController:getState', ], allowedEvents: [ 'AccountsController:selectedAccountChange', 'KeyringController:lock', 'KeyringController:unlock', - 'NetworkController:stateChange', 'NetworkController:networkDidChange', 'TokenListController:stateChange', + 'PreferencesController:stateChange', ], }); } From c90de3ecd24528a9c5c0013da9eaf0e2292f29f9 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 19 Jan 2024 21:38:58 -0500 Subject: [PATCH 19/33] test: add callbacks for all allowed actions, events as `WithControllerCallback` arguments --- .../src/TokenDetectionController.test.ts | 180 +++++++++++++----- 1 file changed, 131 insertions(+), 49 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index a0fe8d5ee36..6b329565177 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -6,9 +6,11 @@ import { convertHexToDecimal, BUILT_IN_NETWORKS, } from '@metamask/controller-utils'; +import type { InternalAccount } from '@metamask/keyring-api'; import type { KeyringControllerState } from '@metamask/keyring-controller'; import { defaultState as defaultNetworkState, + type NetworkState, type NetworkConfiguration, type NetworkController, } from '@metamask/network-controller'; @@ -232,7 +234,7 @@ describe('TokenDetectionController', () => { }, }, async ({ controller, mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -273,7 +275,7 @@ describe('TokenDetectionController', () => { }, }, async ({ controller, mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -331,7 +333,7 @@ describe('TokenDetectionController', () => { }, }, }; - mockTokenListGetState.mockReturnValue(tokenListState); + mockTokenListGetState(tokenListState); await controller.start(); mockAddDetectedTokens.mockReset(); @@ -344,7 +346,7 @@ describe('TokenDetectionController', () => { aggregators: sampleTokenB.aggregators, iconUrl: sampleTokenB.image, }; - mockTokenListGetState.mockReturnValue(tokenListState); + mockTokenListGetState(tokenListState); await advanceTime({ clock, duration: interval }); expect(mockAddDetectedTokens).toHaveBeenCalledWith( @@ -379,7 +381,7 @@ describe('TokenDetectionController', () => { }, }, async ({ controller, mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -416,7 +418,7 @@ describe('TokenDetectionController', () => { }, }, async ({ controller, mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -470,7 +472,7 @@ describe('TokenDetectionController', () => { }, }, async ({ mockTokenListGetState, triggerPreferencesStateChange }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -522,7 +524,7 @@ describe('TokenDetectionController', () => { }, }, async ({ mockTokenListGetState, triggerPreferencesStateChange }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -572,7 +574,7 @@ describe('TokenDetectionController', () => { }, }, async ({ mockTokenListGetState, triggerPreferencesStateChange }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -616,7 +618,7 @@ describe('TokenDetectionController', () => { }, }, async ({ mockTokenListGetState, triggerPreferencesStateChange }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -665,7 +667,7 @@ describe('TokenDetectionController', () => { }, }, async ({ mockTokenListGetState, triggerPreferencesStateChange }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -714,7 +716,7 @@ describe('TokenDetectionController', () => { }, }, async ({ mockTokenListGetState, triggerPreferencesStateChange }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -776,7 +778,7 @@ describe('TokenDetectionController', () => { messenger, }, async ({ mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -827,7 +829,7 @@ describe('TokenDetectionController', () => { messenger, }, async ({ mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -875,7 +877,7 @@ describe('TokenDetectionController', () => { messenger, }, async ({ mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -925,7 +927,7 @@ describe('TokenDetectionController', () => { messenger, }, async ({ mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -1000,7 +1002,7 @@ describe('TokenDetectionController', () => { }, }, }; - mockTokenListGetState.mockReturnValue(tokenListState); + mockTokenListGetState(tokenListState); messenger.publish( 'TokenListController:stateChange', @@ -1043,7 +1045,7 @@ describe('TokenDetectionController', () => { ...getDefaultTokenListState(), tokenList: {}, }; - mockTokenListGetState.mockReturnValue(tokenListState); + mockTokenListGetState(tokenListState); messenger.publish( 'TokenListController:stateChange', @@ -1095,7 +1097,7 @@ describe('TokenDetectionController', () => { }, }, }; - mockTokenListGetState.mockReturnValue(tokenListState); + mockTokenListGetState(tokenListState); messenger.publish( 'TokenListController:stateChange', @@ -1143,7 +1145,7 @@ describe('TokenDetectionController', () => { messenger, }, async ({ controller, mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -1217,7 +1219,7 @@ describe('TokenDetectionController', () => { messenger, }, async ({ controller, mockTokenListGetState }) => { - mockTokenListGetState.mockReturnValue({ + mockTokenListGetState({ ...getDefaultTokenListState(), tokenList: { [sampleTokenA.address]: { @@ -1261,12 +1263,29 @@ function getTokensPath(chainId: Hex) { type WithControllerCallback = ({ controller, + mockKeyringGetState, mockTokenListGetState, + mockPreferencesGetState, + triggerKeyringUnlock, + triggerKeyringLock, + triggerTokenListStateChange, triggerPreferencesStateChange, + triggerSelectedAccountChange, + triggerNetworkDidChange, }: { controller: TokenDetectionController; - mockTokenListGetState: jest.Mock; + mockKeyringGetState: (state: KeyringControllerState) => void; + mockTokenListGetState: (state: TokenListState) => void; + mockPreferencesGetState: (state: PreferencesState) => void; + mockGetNetworkConfigurationByNetworkClientId: ( + handler: (networkClientId: string) => NetworkConfiguration, + ) => void; + triggerKeyringUnlock: () => void; + triggerKeyringLock: () => void; + triggerTokenListStateChange: (state: TokenListState) => void; triggerPreferencesStateChange: (state: PreferencesState) => void; + triggerSelectedAccountChange: (account: InternalAccount) => void; + triggerNetworkDidChange: (state: NetworkState) => void; }) => Promise | ReturnValue; type WithControllerOptions = { @@ -1295,46 +1314,43 @@ async function withController( const controllerMessenger = messenger ?? new ControllerMessenger(); - const mockGetNetworkConfigurationByNetworkClientId = jest - .fn< - ReturnType, - Parameters - >() - .mockImplementation((networkClientId: string) => { - return mockNetworkConfigurations[networkClientId]; - }); + const mockKeyringState = jest.fn(); controllerMessenger.registerActionHandler( 'KeyringController:getState', - jest.fn().mockReturnValue({ + mockKeyringState.mockReturnValue({ isUnlocked: true, } as unknown as KeyringControllerState), ); + const mockGetNetworkConfigurationByNetworkClientId = jest.fn< + ReturnType, + Parameters + >(); controllerMessenger.registerActionHandler( 'NetworkController:getNetworkConfigurationByNetworkClientId', - mockGetNetworkConfigurationByNetworkClientId, + mockGetNetworkConfigurationByNetworkClientId.mockImplementation( + (networkClientId: string) => { + return mockNetworkConfigurations[networkClientId]; + }, + ), ); - const mockTokenListGetState = jest - .fn() - .mockReturnValue({ ...getDefaultTokenListState() }); + const mockTokenListState = jest.fn(); controllerMessenger.registerActionHandler( 'TokenListController:getState', - mockTokenListGetState, + mockTokenListState.mockReturnValue({ ...getDefaultTokenListState() }), + ); + const mockPreferencesState = jest.fn(); + controllerMessenger.registerActionHandler( + 'PreferencesController:getState', + mockPreferencesState.mockReturnValue({ + ...getDefaultPreferencesState(), + }), ); - const preferencesStateChangeListeners: ((state: PreferencesState) => void)[] = - []; const controller = new TokenDetectionController({ networkClientId: NetworkType.mainnet, - onPreferencesStateChange: (listener) => { - preferencesStateChangeListeners.push(listener); - }, getBalancesInSingleCall: jest.fn(), addDetectedTokens: jest.fn(), getTokensState: jest.fn().mockReturnValue(getDefaultTokensState()), - getPreferencesState: jest.fn().mockReturnValue({ - ...getDefaultPreferencesState(), - useTokenDetection: true, - }), trackMetaMetricsEvent: jest.fn(), messenger: buildTokenDetectionControllerMessenger(controllerMessenger), ...options, @@ -1342,11 +1358,77 @@ async function withController( try { return await fn({ controller, - mockTokenListGetState, + mockKeyringGetState: (state: KeyringControllerState) => { + controllerMessenger.unregisterActionHandler( + 'KeyringController:getState', + ); + controllerMessenger.registerActionHandler( + 'KeyringController:getState', + mockKeyringState.mockReturnValue(state), + ); + }, + mockPreferencesGetState: (state: PreferencesState) => { + controllerMessenger.unregisterActionHandler( + 'PreferencesController:getState', + ); + controllerMessenger.registerActionHandler( + 'PreferencesController:getState', + mockPreferencesState.mockReturnValue(state), + ); + }, + mockTokenListGetState: (state: TokenListState) => { + controllerMessenger.unregisterActionHandler( + 'TokenListController:getState', + ); + controllerMessenger.registerActionHandler( + 'TokenListController:getState', + mockTokenListState.mockReturnValue(state), + ); + }, + mockGetNetworkConfigurationByNetworkClientId: ( + handler: (networkClientId: string) => NetworkConfiguration, + ) => { + controllerMessenger.unregisterActionHandler( + 'NetworkController:getNetworkConfigurationByNetworkClientId', + ); + controllerMessenger.registerActionHandler( + 'NetworkController:getNetworkConfigurationByNetworkClientId', + mockGetNetworkConfigurationByNetworkClientId.mockImplementation( + handler, + ), + ); + }, + triggerKeyringUnlock: () => { + controllerMessenger.publish('KeyringController:unlock'); + }, + triggerKeyringLock: () => { + controllerMessenger.publish('KeyringController:lock'); + }, + triggerTokenListStateChange: (state: TokenListState) => { + controllerMessenger.publish( + 'TokenListController:stateChange', + state, + [], + ); + }, triggerPreferencesStateChange: (state: PreferencesState) => { - for (const listener of preferencesStateChangeListeners) { - listener(state); - } + controllerMessenger.publish( + 'PreferencesController:stateChange', + state, + [], + ); + }, + triggerSelectedAccountChange: (account: InternalAccount) => { + controllerMessenger.publish( + 'AccountsController:selectedAccountChange', + account, + ); + }, + triggerNetworkDidChange: (state: NetworkState) => { + controllerMessenger.publish( + 'NetworkController:networkDidChange', + state, + ); }, }); } finally { From 1173eb95b8ac2a8a739adb793008faa6c5b89afe Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 19 Jan 2024 21:39:47 -0500 Subject: [PATCH 20/33] test: adjust for removal of `getPreferencesState` callback --- packages/assets-controllers/jest.config.js | 6 ++--- .../src/TokenDetectionController.test.ts | 24 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/assets-controllers/jest.config.js b/packages/assets-controllers/jest.config.js index a01442c1db5..0d6e2be3898 100644 --- a/packages/assets-controllers/jest.config.js +++ b/packages/assets-controllers/jest.config.js @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 88.36, + branches: 88.5, functions: 95.32, - lines: 96.82, - statements: 96.83, + lines: 96.76, + statements: 96.76, }, }, diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index 6b329565177..e875462af23 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -514,11 +514,6 @@ describe('TokenDetectionController', () => { addDetectedTokens: mockAddDetectedTokens, disabled: false, getBalancesInSingleCall: mockGetBalancesInSingleCall, - getPreferencesState: jest.fn().mockReturnValue({ - ...getDefaultPreferencesState(), - selectedAddress, - useTokenDetection: false, - }), networkClientId: NetworkType.mainnet, selectedAddress, }, @@ -539,6 +534,13 @@ describe('TokenDetectionController', () => { }, }); + triggerPreferencesStateChange({ + ...getDefaultPreferencesState(), + selectedAddress, + useTokenDetection: false, + }); + await advanceTime({ clock, duration: 1 }); + triggerPreferencesStateChange({ ...getDefaultPreferencesState(), selectedAddress, @@ -706,11 +708,6 @@ describe('TokenDetectionController', () => { addDetectedTokens: mockAddDetectedTokens, disabled: true, getBalancesInSingleCall: mockGetBalancesInSingleCall, - getPreferencesState: jest.fn().mockReturnValue({ - ...getDefaultPreferencesState(), - selectedAddress, - useTokenDetection: false, - }), networkClientId: NetworkType.mainnet, selectedAddress, }, @@ -731,6 +728,13 @@ describe('TokenDetectionController', () => { }, }); + triggerPreferencesStateChange({ + ...getDefaultPreferencesState(), + selectedAddress, + useTokenDetection: false, + }); + await advanceTime({ clock, duration: 1 }); + triggerPreferencesStateChange({ ...getDefaultPreferencesState(), selectedAddress, From 2be2e244a3e4f91c2a2768ba5437d49c9b867385 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Sun, 21 Jan 2024 20:14:09 -0500 Subject: [PATCH 21/33] Add CHANGELOG entries --- packages/assets-controllers/CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 452ee5ba9e2..8caf380fcff 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- **BREAKING:** Adds `@metamask/accounts-controller` ^8.0.0 and `@metamask/keyring-controller` ^12.0.0 as dependencies and peer dependencies. ([#3775](https://github.com/MetaMask/core/pull/3775/)). + - Adds `@metamask/keyring-api` as a devDependency. + - Fixes `@metamask/providers` version to ^14.0.2 in the `resolutions` field in the root package.json file. + +### Changed +- **BREAKING:** `TokenDetectionController` is merged with `DetectTokensController` from the `metamask-extension` repo. ([#3775](https://github.com/MetaMask/core/pull/3775/)) + - **BREAKING:** `TokenDetectionController` now resets its polling interval to the default value of 3 minutes when token detection is triggered by external controller events. + - **BREAKING:** `TokenDetectionController` cannot initiate polling or token detection if `KeyringController` state is locked. It also subscribes to the `KeyringController:lock` and `KeyringController:unlock` events. + - **BREAKING:** The `detectTokens` method now excludes tokens that are already included in the `TokensController`'s `detectedTokens` list from the batch of incoming tokens it sends to the `TokensController` `addDetectedTokens` method. + - **BREAKING:** The constructor for `TokenDetectionController` expects a new required proprerty `trackMetaMetricsEvent`, which defines the callback that is called in the `detectTokens` method. + - **BREAKING:** In Mainnet, even if the `PreferenceController`'s `useTokenDetection` option is set to false, automatic token detection is performed on the legacy token list (token data from the contract-metadata repo). + - `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange` events and allows the `PreferencesController:getState` messenger action. + +## Removed +- **BREAKING:** `TokenDetectionController` constructor no longer accepts options `onPreferencesStateChange`, `getPreferencesState`. ([#3775](https://github.com/MetaMask/core/pull/3775/)) +- **BREAKING:** `TokenDetectionController` no longer allows the `NetworkController:stateChange` event. The `NetworkController:networkDidChange` event can be used instead. ([#3775](https://github.com/MetaMask/core/pull/3775/)) ## [25.0.0] From 968c0c079d83909b79fb3b9f8ec3ee5b78a97e1f Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Mon, 22 Jan 2024 10:05:04 -0500 Subject: [PATCH 22/33] [assets-controllers] Bump `@metamask/kerying-api` to ^3.0.0 and remove yarn resolutions entry for `@metamask/providers` --- package.json | 3 - packages/assets-controllers/package.json | 2 +- yarn.lock | 187 +---------------------- 3 files changed, 6 insertions(+), 186 deletions(-) diff --git a/package.json b/package.json index d97910e824c..53882704999 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,6 @@ "simple-git-hooks": { "pre-push": "yarn lint" }, - "resolutions": { - "@metamask/providers": "^14.0.2" - }, "devDependencies": { "@babel/core": "^7.23.5", "@babel/plugin-transform-modules-commonjs": "^7.23.3", diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 18459a9443b..89972fcff61 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -61,7 +61,7 @@ "devDependencies": { "@metamask/auto-changelog": "^3.4.4", "@metamask/ethjs-provider-http": "^0.2.0", - "@metamask/keyring-api": "^1.1.0", + "@metamask/keyring-api": "^3.0.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.191", "@types/node": "^16.18.54", diff --git a/yarn.lock b/yarn.lock index 9c058746682..5f01fe8ac27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -550,13 +550,6 @@ __metadata: languageName: node linkType: hard -"@endo/env-options@npm:^0.1.4": - version: 0.1.4 - resolution: "@endo/env-options@npm:0.1.4" - checksum: 6099f0a6b700a60bee7b226aa2a39bb5748e22f25e9606d70e5a66a8e62cbd8c972b0fe578735a658f80bf2ebece62e28c20aa3f16417cbfe6c19a8689966dd3 - languageName: node - linkType: hard - "@endo/env-options@npm:^1.1.0": version: 1.1.0 resolution: "@endo/env-options@npm:1.1.0" @@ -1570,19 +1563,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/approval-controller@npm:^4.1.0": - version: 4.1.0 - resolution: "@metamask/approval-controller@npm:4.1.0" - dependencies: - "@metamask/base-controller": ^3.2.3 - "@metamask/rpc-errors": ^6.1.0 - "@metamask/utils": ^8.1.0 - immer: ^9.0.6 - nanoid: ^3.1.31 - checksum: b75c900fc656cfc141f8954ccb48346970d561ba83852ec1d27cecddb6606033e03ea560d7253847bd09dfb8317548c9be9cb92c50d906e55134d892d3785806 - languageName: node - linkType: hard - "@metamask/assets-controllers@workspace:packages/assets-controllers": version: 0.0.0-use.local resolution: "@metamask/assets-controllers@workspace:packages/assets-controllers" @@ -1600,7 +1580,7 @@ __metadata: "@metamask/controller-utils": ^8.0.2 "@metamask/eth-query": ^4.0.0 "@metamask/ethjs-provider-http": ^0.2.0 - "@metamask/keyring-api": ^1.1.0 + "@metamask/keyring-api": ^3.0.0 "@metamask/keyring-controller": ^12.2.0 "@metamask/metamask-eth-abis": 3.0.0 "@metamask/network-controller": ^17.2.0 @@ -1686,16 +1666,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/base-controller@npm:^3.2.0, @metamask/base-controller@npm:^3.2.3": - version: 3.2.3 - resolution: "@metamask/base-controller@npm:3.2.3" - dependencies: - "@metamask/utils": ^8.1.0 - immer: ^9.0.6 - checksum: f49fcf2bf892ec25657c2d72a50b3c4f3cad59acb1b74d9fdcdf564107b8f38f73647c696aaa9699d94828b5797d8f1479dab44a2dbcda987c268b0088bb3b76 - languageName: node - linkType: hard - "@metamask/browser-passworder@npm:^4.3.0": version: 4.3.0 resolution: "@metamask/browser-passworder@npm:4.3.0" @@ -1772,21 +1742,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/controller-utils@npm:^5.0.2": - version: 5.0.2 - resolution: "@metamask/controller-utils@npm:5.0.2" - dependencies: - "@metamask/eth-query": ^3.0.1 - "@metamask/utils": ^8.1.0 - "@spruceid/siwe-parser": 1.1.3 - eth-ens-namehash: ^2.0.8 - ethereumjs-util: ^7.0.10 - ethjs-unit: ^0.1.6 - fast-deep-equal: ^3.1.3 - checksum: 2345ab9ee0ba900fe2249d80009acfcf458bc60b30418234d00f5f04247b1182a585050572237f8ab09aa23032a24b99ad96399fc0798a0e9a114a29c3bf90d6 - languageName: node - linkType: hard - "@metamask/core-monorepo@workspace:.": version: 0.0.0-use.local resolution: "@metamask/core-monorepo@workspace:." @@ -1992,16 +1947,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/eth-query@npm:^3.0.1": - version: 3.0.1 - resolution: "@metamask/eth-query@npm:3.0.1" - dependencies: - json-rpc-random-id: ^1.0.0 - xtend: ^4.0.1 - checksum: b9a323dff67328eace7d54fc8b0bc4dd763bf15760870656cbd5aad5380d1ee4489fb5c59506290d5f77cf55e74e530ee97b52702a329f1090ec03a6158434b7 - languageName: node - linkType: hard - "@metamask/eth-query@npm:^4.0.0": version: 4.0.0 resolution: "@metamask/eth-query@npm:4.0.0" @@ -2190,7 +2135,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/json-rpc-engine@^7.1.1, @metamask/json-rpc-engine@^7.3.0, @metamask/json-rpc-engine@^7.3.1, @metamask/json-rpc-engine@^7.3.2, @metamask/json-rpc-engine@workspace:packages/json-rpc-engine": +"@metamask/json-rpc-engine@^7.1.1, @metamask/json-rpc-engine@^7.3.1, @metamask/json-rpc-engine@^7.3.2, @metamask/json-rpc-engine@workspace:packages/json-rpc-engine": version: 0.0.0-use.local resolution: "@metamask/json-rpc-engine@workspace:packages/json-rpc-engine" dependencies: @@ -2246,22 +2191,6 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-api@npm:^1.1.0": - version: 1.1.0 - resolution: "@metamask/keyring-api@npm:1.1.0" - dependencies: - "@metamask/providers": ^13.0.0 - "@metamask/snaps-controllers": ^3.1.0 - "@metamask/snaps-rpc-methods": ^3.1.0 - "@metamask/snaps-utils": ^3.0.0 - "@metamask/utils": ^8.1.0 - "@types/uuid": ^9.0.1 - superstruct: ^1.0.3 - uuid: ^9.0.0 - checksum: dd07db768861a4c4b9b5168dedac104c7e9a8fdd1de5520f81bf276f9923ea55f649d57e8eee1aa5dda06b6b82f163ec5447e32c614e920063baea06e130ecea - languageName: node - linkType: hard - "@metamask/keyring-api@npm:^2.0.0": version: 2.0.0 resolution: "@metamask/keyring-api@npm:2.0.0" @@ -2468,26 +2397,6 @@ __metadata: languageName: node linkType: hard -"@metamask/permission-controller@npm:^5.0.0": - version: 5.0.1 - resolution: "@metamask/permission-controller@npm:5.0.1" - dependencies: - "@metamask/approval-controller": ^4.1.0 - "@metamask/base-controller": ^3.2.3 - "@metamask/controller-utils": ^5.0.2 - "@metamask/json-rpc-engine": ^7.3.0 - "@metamask/rpc-errors": ^6.1.0 - "@metamask/utils": ^8.2.0 - "@types/deep-freeze-strict": ^1.1.0 - deep-freeze-strict: ^1.1.1 - immer: ^9.0.6 - nanoid: ^3.1.31 - peerDependencies: - "@metamask/approval-controller": ^4.1.0 - checksum: fc61df3f5532b35b9ec26ca712848d680d616103e3d06470691412ee8b5a4b70e27d530065f601b64e0a5c2022aa129b8e6ddcc7c3e8325720aa0f639e3e10ba - languageName: node - linkType: hard - "@metamask/permission-controller@npm:^7.0.0, @metamask/permission-controller@npm:^7.1.0": version: 7.1.0 resolution: "@metamask/permission-controller@npm:7.1.0" @@ -2633,7 +2542,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/providers@npm:^14.0.2": +"@metamask/providers@npm:^14.0.1, @metamask/providers@npm:^14.0.2": version: 14.0.2 resolution: "@metamask/providers@npm:14.0.2" dependencies: @@ -2798,7 +2707,7 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^3.1.0, @metamask/snaps-controllers@npm:^3.4.1": +"@metamask/snaps-controllers@npm:^3.4.1": version: 3.6.0 resolution: "@metamask/snaps-controllers@npm:3.6.0" dependencies: @@ -2870,17 +2779,6 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-registry@npm:^2.1.0": - version: 2.1.1 - resolution: "@metamask/snaps-registry@npm:2.1.1" - dependencies: - "@metamask/utils": ^8.1.0 - "@noble/secp256k1": ^1.7.1 - superstruct: ^1.0.3 - checksum: 274002c44f0fe028740c19d1014f844aa4b534862abc530f872baaad8b7b3b2445ffaefbd0a5a957b5102a5b04fe51e6f03a03b1236c8818abc4c748076d6475 - languageName: node - linkType: hard - "@metamask/snaps-registry@npm:^3.0.0": version: 3.0.0 resolution: "@metamask/snaps-registry@npm:3.0.0" @@ -2893,22 +2791,6 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-rpc-methods@npm:^3.1.0": - version: 3.3.0 - resolution: "@metamask/snaps-rpc-methods@npm:3.3.0" - dependencies: - "@metamask/key-tree": ^9.0.0 - "@metamask/permission-controller": ^5.0.0 - "@metamask/rpc-errors": ^6.1.0 - "@metamask/snaps-ui": ^3.1.0 - "@metamask/snaps-utils": ^3.3.0 - "@metamask/utils": ^8.1.0 - "@noble/hashes": ^1.3.1 - superstruct: ^1.0.3 - checksum: 5f830b22db427b4109411632fdd5108ff9c151a819bba35b9bfd0ac6cc70ff581407c91b2ffb34ee0f624869d65054bfde88396e2df13d3052262e29dabbaa05 - languageName: node - linkType: hard - "@metamask/snaps-rpc-methods@npm:^4.1.0": version: 4.1.0 resolution: "@metamask/snaps-rpc-methods@npm:4.1.0" @@ -2955,46 +2837,6 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-ui@npm:^3.1.0": - version: 3.1.0 - resolution: "@metamask/snaps-ui@npm:3.1.0" - dependencies: - "@metamask/utils": ^8.1.0 - is-svg: ^4.4.0 - superstruct: ^1.0.3 - checksum: a234217e961a103b89708d46732481e82c0778b3ebbecddabc9351eee48d082cdc231102442c3ae5c76aa33b24c24aad70e84fee9d7b492641f290a10c3211c1 - languageName: node - linkType: hard - -"@metamask/snaps-utils@npm:^3.0.0, @metamask/snaps-utils@npm:^3.3.0": - version: 3.3.0 - resolution: "@metamask/snaps-utils@npm:3.3.0" - dependencies: - "@babel/core": ^7.23.2 - "@babel/types": ^7.23.0 - "@metamask/base-controller": ^3.2.0 - "@metamask/key-tree": ^9.0.0 - "@metamask/permission-controller": ^5.0.0 - "@metamask/rpc-errors": ^6.1.0 - "@metamask/snaps-registry": ^2.1.0 - "@metamask/snaps-ui": ^3.1.0 - "@metamask/utils": ^8.1.0 - "@noble/hashes": ^1.3.1 - "@scure/base": ^1.1.1 - chalk: ^4.1.2 - cron-parser: ^4.5.0 - fast-deep-equal: ^3.1.3 - fast-json-stable-stringify: ^2.1.0 - is-svg: ^4.4.0 - rfdc: ^1.3.0 - semver: ^7.5.4 - ses: ^0.18.8 - superstruct: ^1.0.3 - validate-npm-package-name: ^5.0.0 - checksum: 3f106d8e290bc260ec0b7f8bcaff5077bd0505f586d3961ad953bae53bc12bc1dc3c4c666c15b58135489ff0b1b47d9630848842e0deee754527652a7ab77bbb - languageName: node - linkType: hard - "@metamask/snaps-utils@npm:^5.0.0, @metamask/snaps-utils@npm:^5.1.1, @metamask/snaps-utils@npm:^5.1.2, @metamask/snaps-utils@npm:^5.2.0": version: 5.2.0 resolution: "@metamask/snaps-utils@npm:5.2.0" @@ -3209,7 +3051,7 @@ __metadata: languageName: node linkType: hard -"@noble/secp256k1@npm:^1.5.5, @noble/secp256k1@npm:^1.7.1": +"@noble/secp256k1@npm:^1.5.5": version: 1.7.1 resolution: "@noble/secp256k1@npm:1.7.1" checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb @@ -6243,16 +6085,6 @@ __metadata: languageName: node linkType: hard -"ethjs-unit@npm:^0.1.6": - version: 0.1.6 - resolution: "ethjs-unit@npm:0.1.6" - dependencies: - bn.js: 4.11.6 - number-to-bn: 1.7.0 - checksum: df6b4752ff7461a59a20219f4b1684c631ea601241c39660e3f6c6bd63c950189723841c22b3c6c0ebeb3c9fc99e0e803e3c613101206132603705fcbcf4def5 - languageName: node - linkType: hard - "event-target-shim@npm:^5.0.0": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -10196,15 +10028,6 @@ __metadata: languageName: node linkType: hard -"ses@npm:^0.18.8": - version: 0.18.8 - resolution: "ses@npm:0.18.8" - dependencies: - "@endo/env-options": ^0.1.4 - checksum: d7976d2ee218baec021c5cfdfb193d63b52bf2b6cbdbbb90c19d835915a1872b6924910f7fd42bc849eb2de78fc7bdd6e7b4667e1df3c79244cc92d4ede48aa6 - languageName: node - linkType: hard - "ses@npm:^1.1.0": version: 1.1.0 resolution: "ses@npm:1.1.0" From 2db0081b93fbe4c02f01e5a7d80f31440ca8c4cd Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Mon, 22 Jan 2024 10:08:17 -0500 Subject: [PATCH 23/33] Update packages/assets-controllers/CHANGELOG.md Update packages/assets-controllers/CHANGELOG.md --- packages/assets-controllers/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 8caf380fcff..74bbc3ed22b 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -8,8 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - **BREAKING:** Adds `@metamask/accounts-controller` ^8.0.0 and `@metamask/keyring-controller` ^12.0.0 as dependencies and peer dependencies. ([#3775](https://github.com/MetaMask/core/pull/3775/)). - - Adds `@metamask/keyring-api` as a devDependency. - - Fixes `@metamask/providers` version to ^14.0.2 in the `resolutions` field in the root package.json file. +- Adds `@metamask/keyring-api` ^3.0.0 as a devDependency. ([#3775](https://github.com/MetaMask/core/pull/3775/)) ### Changed - **BREAKING:** `TokenDetectionController` is merged with `DetectTokensController` from the `metamask-extension` repo. ([#3775](https://github.com/MetaMask/core/pull/3775/)) From 7c25f3c84946f3267442403548da438deb0ac505 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 26 Jan 2024 10:49:10 -0500 Subject: [PATCH 24/33] Extract `findCaseInsensitiveMatch` helper function --- .../src/TokenDetectionController.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index ad770521e0f..d5b84ebfa7b 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -39,6 +39,16 @@ import type { TokensController, TokensState } from './TokensController'; const DEFAULT_INTERVAL = 180000; +/** + * Finds a case insensitive match in an array of strings + * @param source - An array of strings to search. + * @param target - The target string to search for. + * @returns The first match that is found. + */ +function findCaseInsensitiveMatch(source: string[], target: string) { + return source.find((e: string) => e.toLowerCase() === target.toLowerCase()); +} + type LegacyToken = Omit< Token, 'aggregators' | 'image' | 'balanceError' | 'isERC721' @@ -452,9 +462,6 @@ export class TokenDetectionController extends StaticIntervalPollingController< const { tokens, detectedTokens } = this.#getTokensState(); const tokensToDetect: string[] = []; - const findCaseInsensitiveMatch = (source: string[], target: string) => - source.find((e: string) => e.toLowerCase() === target.toLowerCase()); - for (const tokenAddress of Object.keys(tokenListUsed)) { if ( !findCaseInsensitiveMatch( From f1af71a398bde4cbf980b36e335f9e244f34f358 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 26 Jan 2024 10:51:50 -0500 Subject: [PATCH 25/33] Use `networkClientId` instead of `chainId` in `#restartTokenDetection` --- .../assets-controllers/src/TokenDetectionController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index d5b84ebfa7b..1b7b757f614 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -316,7 +316,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< if (isChainIdChanged && this.#isDetectionEnabledForNetwork) { this.#chainId = newChainId; await this.#restartTokenDetection({ - chainId: this.#chainId, + networkClientId: this.#networkClientId, }); } }, @@ -410,15 +410,15 @@ export class TokenDetectionController extends StaticIntervalPollingController< * * @param options - Options for restart token detection. * @param options.selectedAddress - the selectedAddress against which to detect for token balances - * @param options.chainId - the chainId against which to detect for token balances + * @param options.networkClientId - The ID of the network client to use. */ async #restartTokenDetection({ selectedAddress, - chainId, - }: Partial<{ selectedAddress: string; chainId: Hex }> = {}) { + networkClientId, + }: { selectedAddress?: string; networkClientId?: string } = {}) { await this.detectTokens({ + networkClientId, accountAddress: selectedAddress, - networkClientId: chainId, }); this.setIntervalLength(DEFAULT_INTERVAL); } From 907b4dfa0c1d6dcac18a279953061c6739d372da Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 26 Jan 2024 11:02:38 -0500 Subject: [PATCH 26/33] test: remove unnecessary action handler unregisters --- .../src/TokenDetectionController.test.ts | 34 +++---------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index e875462af23..c445cb2e095 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -1363,43 +1363,19 @@ async function withController( return await fn({ controller, mockKeyringGetState: (state: KeyringControllerState) => { - controllerMessenger.unregisterActionHandler( - 'KeyringController:getState', - ); - controllerMessenger.registerActionHandler( - 'KeyringController:getState', - mockKeyringState.mockReturnValue(state), - ); + mockKeyringState.mockReturnValue(state); }, mockPreferencesGetState: (state: PreferencesState) => { - controllerMessenger.unregisterActionHandler( - 'PreferencesController:getState', - ); - controllerMessenger.registerActionHandler( - 'PreferencesController:getState', - mockPreferencesState.mockReturnValue(state), - ); + mockPreferencesState.mockReturnValue(state); }, mockTokenListGetState: (state: TokenListState) => { - controllerMessenger.unregisterActionHandler( - 'TokenListController:getState', - ); - controllerMessenger.registerActionHandler( - 'TokenListController:getState', - mockTokenListState.mockReturnValue(state), - ); + mockTokenListState.mockReturnValue(state); }, mockGetNetworkConfigurationByNetworkClientId: ( handler: (networkClientId: string) => NetworkConfiguration, ) => { - controllerMessenger.unregisterActionHandler( - 'NetworkController:getNetworkConfigurationByNetworkClientId', - ); - controllerMessenger.registerActionHandler( - 'NetworkController:getNetworkConfigurationByNetworkClientId', - mockGetNetworkConfigurationByNetworkClientId.mockImplementation( - handler, - ), + mockGetNetworkConfigurationByNetworkClientId.mockImplementation( + handler, ); }, triggerKeyringUnlock: () => { From 04df2782bd706ad072369768610a36bed1c1e878 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Fri, 26 Jan 2024 16:24:46 -0500 Subject: [PATCH 27/33] Changelog linter fix --- packages/assets-controllers/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 74bbc3ed22b..76fe4d289e9 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -6,11 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + ### Added + - **BREAKING:** Adds `@metamask/accounts-controller` ^8.0.0 and `@metamask/keyring-controller` ^12.0.0 as dependencies and peer dependencies. ([#3775](https://github.com/MetaMask/core/pull/3775/)). - Adds `@metamask/keyring-api` ^3.0.0 as a devDependency. ([#3775](https://github.com/MetaMask/core/pull/3775/)) ### Changed + - **BREAKING:** `TokenDetectionController` is merged with `DetectTokensController` from the `metamask-extension` repo. ([#3775](https://github.com/MetaMask/core/pull/3775/)) - **BREAKING:** `TokenDetectionController` now resets its polling interval to the default value of 3 minutes when token detection is triggered by external controller events. - **BREAKING:** `TokenDetectionController` cannot initiate polling or token detection if `KeyringController` state is locked. It also subscribes to the `KeyringController:lock` and `KeyringController:unlock` events. @@ -19,7 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** In Mainnet, even if the `PreferenceController`'s `useTokenDetection` option is set to false, automatic token detection is performed on the legacy token list (token data from the contract-metadata repo). - `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange` events and allows the `PreferencesController:getState` messenger action. -## Removed +### Removed + - **BREAKING:** `TokenDetectionController` constructor no longer accepts options `onPreferencesStateChange`, `getPreferencesState`. ([#3775](https://github.com/MetaMask/core/pull/3775/)) - **BREAKING:** `TokenDetectionController` no longer allows the `NetworkController:stateChange` event. The `NetworkController:networkDidChange` event can be used instead. ([#3775](https://github.com/MetaMask/core/pull/3775/)) From 312e450a7a25627d0d266c65c4fd90fd7d382969 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Mon, 29 Jan 2024 13:31:43 -0500 Subject: [PATCH 28/33] In networkDidChange event listener, detect tokens if `networkClientId` is changed instead of `chainId`, and avoid resetting polling interval --- packages/assets-controllers/CHANGELOG.md | 3 ++- packages/assets-controllers/jest.config.js | 2 +- .../src/TokenDetectionController.test.ts | 8 ++++---- .../src/TokenDetectionController.ts | 12 ++++++------ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 76fe4d289e9..23dce9201db 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **BREAKING:** `TokenDetectionController` is merged with `DetectTokensController` from the `metamask-extension` repo. ([#3775](https://github.com/MetaMask/core/pull/3775/)) - - **BREAKING:** `TokenDetectionController` now resets its polling interval to the default value of 3 minutes when token detection is triggered by external controller events. + - **BREAKING:** `TokenDetectionController` now resets its polling interval to the default value of 3 minutes when token detection is triggered by external controller events `KeyringController:unlock`, `TokenListController:stateChange`, `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`. + - **BREAKING:** `TokenDetectionController` now responds to `NetworkController:networkDidChange` event only if the `networkClientId` is changed, and does not reset the polling interval in this case. - **BREAKING:** `TokenDetectionController` cannot initiate polling or token detection if `KeyringController` state is locked. It also subscribes to the `KeyringController:lock` and `KeyringController:unlock` events. - **BREAKING:** The `detectTokens` method now excludes tokens that are already included in the `TokensController`'s `detectedTokens` list from the batch of incoming tokens it sends to the `TokensController` `addDetectedTokens` method. - **BREAKING:** The constructor for `TokenDetectionController` expects a new required proprerty `trackMetaMetricsEvent`, which defines the callback that is called in the `detectTokens` method. diff --git a/packages/assets-controllers/jest.config.js b/packages/assets-controllers/jest.config.js index 0d6e2be3898..c3261c3c9a3 100644 --- a/packages/assets-controllers/jest.config.js +++ b/packages/assets-controllers/jest.config.js @@ -17,7 +17,7 @@ module.exports = merge(baseConfig, { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 88.5, + branches: 88.3, functions: 95.32, lines: 96.76, statements: 96.76, diff --git a/packages/assets-controllers/src/TokenDetectionController.test.ts b/packages/assets-controllers/src/TokenDetectionController.test.ts index c445cb2e095..be648263448 100644 --- a/packages/assets-controllers/src/TokenDetectionController.test.ts +++ b/packages/assets-controllers/src/TokenDetectionController.test.ts @@ -760,7 +760,7 @@ describe('TokenDetectionController', () => { }); describe('when "disabled" is "false"', () => { - it('should detect new tokens after switching chains', async () => { + it('should detect new tokens after switching network client id', async () => { const mockGetBalancesInSingleCall = jest.fn().mockResolvedValue({ [sampleTokenA.address]: new BN(1), }); @@ -859,7 +859,7 @@ describe('TokenDetectionController', () => { ); }); - it('should not detect new tokens if the chain has not changed', async () => { + it('should not detect new tokens if the network client id has not changed', async () => { const mockGetBalancesInSingleCall = jest.fn().mockResolvedValue({ [sampleTokenA.address]: new BN(1), }); @@ -898,7 +898,7 @@ describe('TokenDetectionController', () => { messenger.publish('NetworkController:networkDidChange', { ...defaultNetworkState, - selectedNetworkClientId: 'mainnnet', + selectedNetworkClientId: 'mainnet', }); await advanceTime({ clock, duration: 1 }); @@ -909,7 +909,7 @@ describe('TokenDetectionController', () => { }); describe('when "disabled" is "true"', () => { - it('should not detect new tokens after switching chains', async () => { + it('should not detect new tokens after switching network client id', async () => { const mockGetBalancesInSingleCall = jest.fn().mockResolvedValue({ [sampleTokenA.address]: new BN(1), }); diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index 1b7b757f614..165ee222c71 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -306,16 +306,16 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.messagingSystem.subscribe( 'NetworkController:networkDidChange', async ({ selectedNetworkClientId }) => { - this.#networkClientId = selectedNetworkClientId; - const newChainId = this.#getCorrectChainId(selectedNetworkClientId); - const isChainIdChanged = this.#chainId !== newChainId; + const isNetworkClientIdChanged = + this.#networkClientId !== selectedNetworkClientId; + const newChainId = this.#getCorrectChainId(selectedNetworkClientId); this.#isDetectionEnabledForNetwork = isTokenDetectionSupportedForNetwork(newChainId); - if (isChainIdChanged && this.#isDetectionEnabledForNetwork) { - this.#chainId = newChainId; - await this.#restartTokenDetection({ + if (isNetworkClientIdChanged && this.#isDetectionEnabledForNetwork) { + this.#networkClientId = selectedNetworkClientId; + await this.detectTokens({ networkClientId: this.#networkClientId, }); } From 9a4ae931a761342460e91c4ee9ee2efc3ea7e30f Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Wed, 31 Jan 2024 10:28:32 -0500 Subject: [PATCH 29/33] In changelog, move new actions/events under "Added" heading --- 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 23dce9201db..55533ea4121 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -11,17 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Adds `@metamask/accounts-controller` ^8.0.0 and `@metamask/keyring-controller` ^12.0.0 as dependencies and peer dependencies. ([#3775](https://github.com/MetaMask/core/pull/3775/)). - Adds `@metamask/keyring-api` ^3.0.0 as a devDependency. ([#3775](https://github.com/MetaMask/core/pull/3775/)) +- `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`, `KeyringController:lock`, `KeyringController:unlock` events, and allows the `PreferencesController:getState` messenger action. ([#3775](https://github.com/MetaMask/core/pull/3775/)) ### Changed - **BREAKING:** `TokenDetectionController` is merged with `DetectTokensController` from the `metamask-extension` repo. ([#3775](https://github.com/MetaMask/core/pull/3775/)) - **BREAKING:** `TokenDetectionController` now resets its polling interval to the default value of 3 minutes when token detection is triggered by external controller events `KeyringController:unlock`, `TokenListController:stateChange`, `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`. - **BREAKING:** `TokenDetectionController` now responds to `NetworkController:networkDidChange` event only if the `networkClientId` is changed, and does not reset the polling interval in this case. - - **BREAKING:** `TokenDetectionController` cannot initiate polling or token detection if `KeyringController` state is locked. It also subscribes to the `KeyringController:lock` and `KeyringController:unlock` events. + - **BREAKING:** `TokenDetectionController` cannot initiate polling or token detection if `KeyringController` state is locked. - **BREAKING:** The `detectTokens` method now excludes tokens that are already included in the `TokensController`'s `detectedTokens` list from the batch of incoming tokens it sends to the `TokensController` `addDetectedTokens` method. - **BREAKING:** The constructor for `TokenDetectionController` expects a new required proprerty `trackMetaMetricsEvent`, which defines the callback that is called in the `detectTokens` method. - **BREAKING:** In Mainnet, even if the `PreferenceController`'s `useTokenDetection` option is set to false, automatic token detection is performed on the legacy token list (token data from the contract-metadata repo). - - `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange` events and allows the `PreferencesController:getState` messenger action. ### Removed From 91be38550f3cd9c9f16054e39849bbff170d44a0 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 6 Feb 2024 09:19:18 -0500 Subject: [PATCH 30/33] Include `#stopPolling()` call in `KeyringController:lock` listener --- 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 165ee222c71..aa092e82e36 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -251,6 +251,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< this.messagingSystem.subscribe('KeyringController:lock', () => { this.#isUnlocked = false; + this.#stopPolling(); }); this.messagingSystem.subscribe( From 88eb7cac766db2fea4b5f3f3047d637a96902f2d Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 6 Feb 2024 15:31:40 -0500 Subject: [PATCH 31/33] Replace `detectTokens` with `#restartTokenDetection` in `networkDidChange` listener --- packages/assets-controllers/src/TokenDetectionController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index aa092e82e36..13175385f28 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -316,7 +316,7 @@ export class TokenDetectionController extends StaticIntervalPollingController< if (isNetworkClientIdChanged && this.#isDetectionEnabledForNetwork) { this.#networkClientId = selectedNetworkClientId; - await this.detectTokens({ + await this.#restartTokenDetection({ networkClientId: this.#networkClientId, }); } From e01f9933fc81b5c9b12461e30a33292cd1b8021e Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Tue, 6 Feb 2024 17:56:39 -0500 Subject: [PATCH 32/33] test: adjust coverage thresholds --- packages/assets-controllers/jest.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/assets-controllers/jest.config.js b/packages/assets-controllers/jest.config.js index c3261c3c9a3..34e91df6918 100644 --- a/packages/assets-controllers/jest.config.js +++ b/packages/assets-controllers/jest.config.js @@ -19,8 +19,8 @@ module.exports = merge(baseConfig, { global: { branches: 88.3, functions: 95.32, - lines: 96.76, - statements: 96.76, + lines: 96.69, + statements: 96.7, }, }, From 4a944d887c1c95f9e9bb003760237de609aefdbf Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Wed, 7 Feb 2024 13:44:59 -0500 Subject: [PATCH 33/33] Apply suggestions to CHANGELOG Co-authored-by: Elliot Winkler --- packages/assets-controllers/CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 55533ea4121..2f879da7c79 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -10,14 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **BREAKING:** Adds `@metamask/accounts-controller` ^8.0.0 and `@metamask/keyring-controller` ^12.0.0 as dependencies and peer dependencies. ([#3775](https://github.com/MetaMask/core/pull/3775/)). -- Adds `@metamask/keyring-api` ^3.0.0 as a devDependency. ([#3775](https://github.com/MetaMask/core/pull/3775/)) -- `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`, `KeyringController:lock`, `KeyringController:unlock` events, and allows the `PreferencesController:getState` messenger action. ([#3775](https://github.com/MetaMask/core/pull/3775/)) +- **BREAKING:** `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`, `KeyringController:lock`, `KeyringController:unlock` events, and allows the `PreferencesController:getState` messenger action. ([#3775](https://github.com/MetaMask/core/pull/3775/)) ### Changed - **BREAKING:** `TokenDetectionController` is merged with `DetectTokensController` from the `metamask-extension` repo. ([#3775](https://github.com/MetaMask/core/pull/3775/)) - **BREAKING:** `TokenDetectionController` now resets its polling interval to the default value of 3 minutes when token detection is triggered by external controller events `KeyringController:unlock`, `TokenListController:stateChange`, `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`. - - **BREAKING:** `TokenDetectionController` now responds to `NetworkController:networkDidChange` event only if the `networkClientId` is changed, and does not reset the polling interval in this case. + - **BREAKING:** `TokenDetectionController` now refetches tokens on `NetworkController:networkDidChange` if the `networkClientId` is changed instead of `chainId`. - **BREAKING:** `TokenDetectionController` cannot initiate polling or token detection if `KeyringController` state is locked. - **BREAKING:** The `detectTokens` method now excludes tokens that are already included in the `TokensController`'s `detectedTokens` list from the batch of incoming tokens it sends to the `TokensController` `addDetectedTokens` method. - **BREAKING:** The constructor for `TokenDetectionController` expects a new required proprerty `trackMetaMetricsEvent`, which defines the callback that is called in the `detectTokens` method.