Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[token-detection-controller] Refactor detectTokens method #3938

Merged
merged 17 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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/)).
- **BREAKING:** `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`, `KeyringController:lock`, `KeyringController:unlock` events, and allows messenger actions `AccountsController:getSelectedAccount`, `NetworkController:findNetworkClientIdByChainId`, `NetworkController:getNetworkConfigurationByNetworkClientId`, `NetworkController:getProviderConfig`, `KeyringController:getState`, `PreferencesController:getState`, `TokenListController:getState`, `TokensController:getState`, `TokensController:addDetectedTokens`. ([#3775](https://github.com/MetaMask/core/pull/3775/)), ([#3923](https://github.com/MetaMask/core/pull/3923/))
- **BREAKING:** `TokenDetectionController` newly subscribes to the `PreferencesController:stateChange`, `AccountsController:selectedAccountChange`, `KeyringController:lock`, `KeyringController:unlock` events, and allows messenger actions `AccountsController:getSelectedAccount`, `NetworkController:getNetworkClientById`, `NetworkController:getNetworkConfigurationByNetworkClientId`, `NetworkController:getState`, `KeyringController:getState`, `PreferencesController:getState`, `TokenListController:getState`, `TokensController:getState`, `TokensController:addDetectedTokens`. ([#3775](https://github.com/MetaMask/core/pull/3775/), [#3923](https://github.com/MetaMask/core/pull/3923/), [#3938](https://github.com/MetaMask/core/pull/3938))
- `TokensController` now exports `TokensControllerActions`, `TokensControllerGetStateAction`, `TokensControllerAddDetectedTokensAction`, `TokensControllerEvents`, `TokensControllerStateChangeEvent`. ([#3690](https://github.com/MetaMask/core/pull/3690/))

### Changed
Expand All @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The constructor option `selectedAddress` no longer defaults to `''` if omitted. Instead, the correct address is assigned using the `AccountsController:getSelectedAccount` messenger action.
- **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).
- **BREAKING:** The `TokensState` type is now defined as a type alias rather than an interface. ([#3690](https://github.com/MetaMask/core/pull/3690/))
- This is breaking because it could affect how this type is used with other types, such as `Json`, which does not support TypeScript interfaces.
- `TokensState` now extends the `Record` types, and it has an index signature of `string`, making it compatible with the `BaseControllerV2` state object constraint of `Record<string, Json>`.

### Removed

Expand Down
6 changes: 3 additions & 3 deletions packages/assets-controllers/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 88.58,
branches: 88.38,
functions: 96.98,
lines: 97.35,
statements: 97.4,
lines: 97.37,
statements: 97.42,
},
},

Expand Down
85 changes: 50 additions & 35 deletions packages/assets-controllers/src/TokenDetectionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import type {
NetworkState,
NetworkConfiguration,
NetworkController,
ProviderConfig,
NetworkClientId,
} from '@metamask/network-controller';
import { defaultState as defaultNetworkState } from '@metamask/network-controller';
import type { AutoManagedNetworkClient } from '@metamask/network-controller/src/create-auto-managed-network-client';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads up: #3998 will make it impossible to import subpaths, so this will need to be changed when that happens, but is okay for now.

Copy link
Contributor Author

@MajorLift MajorLift Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I was surprised to learn that AutoManagedNetworkClient isn't a package-level export. Is this an intentional choice? Otherwise I could create a ticket for exporting this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't. I don't think I expected anyone to have to use this type, as it's low-level. But there's no harm in exporting it or anything.

import type { CustomNetworkClientConfiguration } from '@metamask/network-controller/src/types';
import {
getDefaultPreferencesState,
type PreferencesState,
Expand Down Expand Up @@ -142,9 +143,9 @@ function buildTokenDetectionControllerMessenger(
allowedActions: [
'AccountsController:getSelectedAccount',
'KeyringController:getState',
'NetworkController:findNetworkClientIdByChainId',
'NetworkController:getNetworkClientById',
'NetworkController:getNetworkConfigurationByNetworkClientId',
'NetworkController:getProviderConfig',
'NetworkController:getState',
'TokensController:getState',
'TokensController:addDetectedTokens',
'TokenListController:getState',
Expand Down Expand Up @@ -345,13 +346,21 @@ describe('TokenDetectionController', () => {
},
async ({
controller,
mockGetProviderConfig,
mockTokenListGetState,
mockNetworkState,
mockGetNetworkClientById,
callActionSpy,
}) => {
mockGetProviderConfig({
chainId: '0x89',
} as unknown as ProviderConfig);
mockNetworkState({
...defaultNetworkState,
selectedNetworkClientId: 'polygon',
});
mockGetNetworkClientById(
() =>
({
configuration: { chainId: '0x89' },
} as unknown as AutoManagedNetworkClient<CustomNetworkClientConfiguration>),
);

mockTokenListGetState({
...getDefaultTokenListState(),
Expand Down Expand Up @@ -1852,17 +1861,12 @@ describe('TokenDetectionController', () => {
expect(callActionSpy).toHaveBeenLastCalledWith(
'TokensController:addDetectedTokens',
Object.values(STATIC_MAINNET_TOKEN_LIST).map((token) => {
const newToken = {
...token,
const { iconUrl, ...tokenMetadata } = token;
return {
...tokenMetadata,
image: token.iconUrl,
isERC721: false,
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (newToken as any).erc20;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (newToken as any).erc721;
delete newToken.iconUrl;
return newToken;
}),
{
selectedAddress,
Expand Down Expand Up @@ -2002,9 +2006,9 @@ type WithControllerCallback<ReturnValue> = ({
mockTokensGetState,
mockTokenListGetState,
mockPreferencesGetState,
mockFindNetworkClientIdByChainId,
mockGetNetworkClientById,
mockGetNetworkConfigurationByNetworkClientId,
mockGetProviderConfig,
mockNetworkState,
callActionSpy,
triggerKeyringUnlock,
triggerKeyringLock,
Expand All @@ -2019,13 +2023,15 @@ type WithControllerCallback<ReturnValue> = ({
mockTokensGetState: (state: TokensState) => void;
mockTokenListGetState: (state: TokenListState) => void;
mockPreferencesGetState: (state: PreferencesState) => void;
mockFindNetworkClientIdByChainId: (
handler: (chainId: Hex) => NetworkClientId,
mockGetNetworkClientById: (
handler: (
networkClientId: NetworkClientId,
) => AutoManagedNetworkClient<CustomNetworkClientConfiguration>,
) => void;
mockGetNetworkConfigurationByNetworkClientId: (
handler: (networkClientId: string) => NetworkConfiguration,
handler: (networkClientId: NetworkClientId) => NetworkConfiguration,
) => void;
mockGetProviderConfig: (config: ProviderConfig) => void;
mockNetworkState: (state: NetworkState) => void;
callActionSpy: jest.SpyInstance;
triggerKeyringUnlock: () => void;
triggerKeyringLock: () => void;
Expand Down Expand Up @@ -2076,10 +2082,20 @@ async function withController<ReturnValue>(
isUnlocked: isKeyringUnlocked ?? true,
} as KeyringControllerState),
);
const mockFindNetworkClientIdByChainId = jest.fn<NetworkClientId, [Hex]>();
const mockGetNetworkClientById = jest.fn<
ReturnType<NetworkController['getNetworkClientById']>,
Parameters<NetworkController['getNetworkClientById']>
>();
controllerMessenger.registerActionHandler(
'NetworkController:findNetworkClientIdByChainId',
mockFindNetworkClientIdByChainId.mockReturnValue(NetworkType.mainnet),
'NetworkController:getNetworkClientById',
mockGetNetworkClientById.mockImplementation(() => {
return {
configuration: { chainId: '0x1' },
provider: {},
destroy: {},
blockTracker: {},
} as unknown as AutoManagedNetworkClient<CustomNetworkClientConfiguration>;
}),
);
const mockGetNetworkConfigurationByNetworkClientId = jest.fn<
ReturnType<NetworkController['getNetworkConfigurationByNetworkClientId']>,
Expand All @@ -2093,13 +2109,10 @@ async function withController<ReturnValue>(
},
),
);
const mockGetProviderConfig = jest.fn<ProviderConfig, []>();
const mockNetworkState = jest.fn<NetworkState, []>();
controllerMessenger.registerActionHandler(
'NetworkController:getProviderConfig',
mockGetProviderConfig.mockReturnValue({
type: NetworkType.mainnet,
chainId: '0x1',
} as unknown as ProviderConfig),
'NetworkController:getState',
mockNetworkState.mockReturnValue({ ...defaultNetworkState }),
);
const mockTokensState = jest.fn<TokensState, []>();
controllerMessenger.registerActionHandler(
Expand Down Expand Up @@ -2154,10 +2167,12 @@ async function withController<ReturnValue>(
mockTokenListGetState: (state: TokenListState) => {
mockTokenListState.mockReturnValue(state);
},
mockFindNetworkClientIdByChainId: (
handler: (chainId: Hex) => NetworkClientId,
mockGetNetworkClientById: (
handler: (
networkClientId: NetworkClientId,
) => AutoManagedNetworkClient<CustomNetworkClientConfiguration>,
) => {
mockFindNetworkClientIdByChainId.mockImplementation(handler);
mockGetNetworkClientById.mockImplementation(handler);
},
mockGetNetworkConfigurationByNetworkClientId: (
handler: (networkClientId: NetworkClientId) => NetworkConfiguration,
Expand All @@ -2166,8 +2181,8 @@ async function withController<ReturnValue>(
handler,
);
},
mockGetProviderConfig: (config: ProviderConfig) => {
mockGetProviderConfig.mockReturnValue(config);
mockNetworkState: (state: NetworkState) => {
mockNetworkState.mockReturnValue(state);
},
callActionSpy,
triggerKeyringUnlock: () => {
Expand Down
Loading
Loading