From a59756873da17fc420f8f14d27c267224a2097ac Mon Sep 17 00:00:00 2001 From: Salim TOUBAL Date: Mon, 16 Dec 2024 20:37:04 +0100 Subject: [PATCH] fix: poll only popular network (#12658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** To reduce the impact of showing assets across all networks, which can be unbounded, this PR scopes the portfolio view to the 9 popular networks built into metamask. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-extension/issues/29055 ## **Manual testing steps** 1. On popular networks: - The token filter should allow switching between popular networks and current network - The filter should work as described - RPC requests in the background should only hit the popular networks 2. On other networks - The token filter should become disabled and scoped to the current network - RPC requests in the background should only hit the current network ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/a65afd42-b67e-401e-b33c-b46fad3dd812 https://github.com/user-attachments/assets/e2179b4c-c3da-43d1-86d3-50fc066f0899 ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../TokenList/TokenListFooter/index.tsx | 8 +- .../TokenFilterBottomSheet.test.tsx | 9 +- .../TokenFilterBottomSheet.tsx | 8 +- .../Tokens/__snapshots__/index.test.tsx.snap | 2 +- app/components/UI/Tokens/index.tsx | 32 +-- app/components/Views/DetectedTokens/index.tsx | 4 +- .../Views/NetworkSelector/NetworkSelector.tsx | 13 +- app/components/Views/Wallet/index.tsx | 3 +- .../useAccountTrackerPolling.test.ts | 202 ++++++++++++++++++ .../AssetPolling/useAccountTrackerPolling.ts | 41 ++-- .../useCurrencyRatePolling.test.ts | 66 +++++- .../AssetPolling/useCurrencyRatePolling.ts | 33 ++- .../useTokenBalancesPolling.test.ts | 74 ++++++- .../AssetPolling/useTokenBalancesPolling.ts | 21 +- .../useTokenDetectionPolling.test.ts | 64 +++++- .../AssetPolling/useTokenDetectionPolling.ts | 23 +- .../AssetPolling/useTokenListPolling.test.ts | 70 +++++- .../hooks/AssetPolling/useTokenListPolling.ts | 22 +- .../AssetPolling/useTokenRatesPolling.test.ts | 64 +++++- .../AssetPolling/useTokenRatesPolling.ts | 22 +- app/selectors/networkController.test.ts | 34 ++- app/selectors/networkController.ts | 41 +++- 22 files changed, 768 insertions(+), 88 deletions(-) create mode 100644 app/components/hooks/AssetPolling/useAccountTrackerPolling.test.ts diff --git a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx index c3732eb505d..5ab9d0aba4d 100644 --- a/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx +++ b/app/components/UI/Tokens/TokenList/TokenListFooter/index.tsx @@ -32,6 +32,7 @@ import { import { selectChainId, selectIsAllNetworks, + selectIsPopularNetwork, } from '../../../../../selectors/networkController'; import { TokenI } from '../../types'; import { selectUseTokenDetection } from '../../../../../selectors/preferencesController'; @@ -48,12 +49,15 @@ const getDetectedTokensCount = ( isAllNetworksSelected: boolean, allTokens: TokenI[], filteredTokens: TokenI[] | undefined, + isPopularNetworks: boolean, ): number => { if (!isPortfolioEnabled) { return filteredTokens?.length ?? 0; } - return isAllNetworksSelected ? allTokens.length : filteredTokens?.length ?? 0; + return isAllNetworksSelected && isPopularNetworks + ? allTokens.length + : filteredTokens?.length ?? 0; }; export const TokenListFooter = ({ @@ -75,6 +79,7 @@ export const TokenListFooter = ({ const isTokenDetectionEnabled = useSelector(selectUseTokenDetection); const chainId = useSelector(selectChainId); const isAllNetworks = useSelector(selectIsAllNetworks); + const isPopularNetworks = useSelector(selectIsPopularNetwork); const styles = createStyles(colors); @@ -103,6 +108,7 @@ export const TokenListFooter = ({ isAllNetworks, allDetectedTokens, detectedTokens, + isPopularNetworks, ); const areTokensDetected = tokenCount > 0; diff --git a/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.test.tsx b/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.test.tsx index 49117d58278..c0b0e2f4249 100644 --- a/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.test.tsx +++ b/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.test.tsx @@ -4,6 +4,7 @@ import { TokenFilterBottomSheet } from './TokenFilterBottomSheet'; import { useSelector } from 'react-redux'; import Engine from '../../../../core/Engine'; import { + selectAllPopularNetworkConfigurations, selectChainId, selectNetworkConfigurations, } from '../../../../selectors/networkController'; @@ -102,6 +103,8 @@ describe('TokenFilterBottomSheet', () => { return {}; // default to show all networks } else if (selector === selectNetworkConfigurations) { return mockNetworks; // default to show all networks + } else if (selector === selectAllPopularNetworkConfigurations) { + return mockNetworks; // default to show all networks } return null; }); @@ -114,14 +117,14 @@ describe('TokenFilterBottomSheet', () => { it('renders correctly with the default option (All Networks) selected', () => { const { queryByText } = render(); - expect(queryByText('All Networks')).toBeTruthy(); + expect(queryByText('Popular networks')).toBeTruthy(); expect(queryByText('Current Network')).toBeTruthy(); }); it('sets filter to All Networks and closes bottom sheet when first option is pressed', async () => { const { queryByText } = render(); - fireEvent.press(queryByText('All Networks')); + fireEvent.press(queryByText('Popular networks')); await waitFor(() => { expect( @@ -152,6 +155,8 @@ describe('TokenFilterBottomSheet', () => { return { '0x1': true }; // filter by current network } else if (selector === selectNetworkConfigurations) { return mockNetworks; + } else if (selector === selectAllPopularNetworkConfigurations) { + return mockNetworks; } return null; }); diff --git a/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx b/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx index ba4c7a41961..de98dc93d00 100644 --- a/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx +++ b/app/components/UI/Tokens/TokensBottomSheet/TokenFilterBottomSheet.tsx @@ -2,8 +2,8 @@ import React, { useRef, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { selectChainId, - selectNetworkConfigurations, selectIsAllNetworks, + selectAllPopularNetworkConfigurations, } from '../../../../selectors/networkController'; import { selectTokenNetworkFilter } from '../../../../selectors/preferencesController'; import BottomSheet, { @@ -29,7 +29,7 @@ enum FilterOption { const TokenFilterBottomSheet = () => { const sheetRef = useRef(null); - const allNetworks = useSelector(selectNetworkConfigurations); + const allNetworks = useSelector(selectAllPopularNetworkConfigurations); const { colors } = useTheme(); const styles = createStyles(colors); @@ -79,7 +79,9 @@ const TokenFilterBottomSheet = () => { verticalAlignment={VerticalAlignment.Center} > - {strings('wallet.all_networks')} + {`${strings('app_settings.popular')} ${strings( + 'app_settings.networks', + )}`} - All Networks + Ethereum Main Network = ({ tokens }) => { const multiChainMarketData = useSelector(selectTokenMarketData); const multiChainTokenBalance = useSelector(selectTokensBalances); const multiChainCurrencyRates = useSelector(selectCurrencyRates); + const isPopularNetwork = useSelector(selectIsPopularNetwork); const styles = createStyles(colors); const tokensList = useMemo((): TokenI[] => { + // if it is not popular network, display tokens only for current network + const filteredAssetsParam = isPopularNetwork + ? tokenNetworkFilter + : { [currentChainId]: true }; if (isPortfolioViewEnabled()) { // MultiChain implementation const allTokens = Object.values( @@ -183,7 +189,7 @@ const Tokens: React.FC = ({ tokens }) => { const filteredAssets = filterAssets(tokensToDisplay, [ { key: 'chainId', - opts: tokenNetworkFilter, + opts: filteredAssetsParam, filterCallback: 'inclusive', }, ]); @@ -289,6 +295,7 @@ const Tokens: React.FC = ({ tokens }) => { tokens, // Dependencies for multichain implementation selectedAccountTokensChains, + isPopularNetwork, tokenNetworkFilter, currentChainId, multiChainCurrencyRates, @@ -404,15 +411,6 @@ const Tokens: React.FC = ({ tokens }) => { const onActionSheetPress = (index: number) => index === 0 ? removeToken() : null; - useEffect(() => { - const { PreferencesController } = Engine.context; - if (isTestNet(currentChainId)) { - PreferencesController.setTokenNetworkFilter({ - [currentChainId]: true, - }); - } - }, [currentChainId]); - return ( = ({ tokens }) => { testID={WalletViewSelectorsIDs.TOKEN_NETWORK_FILTER} label={ - {isAllNetworks - ? strings('wallet.all_networks') + {isAllNetworks && isPopularNetwork + ? `${strings('app_settings.popular')} ${strings( + 'app_settings.networks', + )}` : networkName ?? strings('wallet.current_network')} } - isDisabled={isTestNet(currentChainId)} + isDisabled={isTestNet(currentChainId) || !isPopularNetwork} onPress={showFilterControls} endIconName={IconName.ArrowDown} style={ - isTestNet(currentChainId) + isTestNet(currentChainId) || !isPopularNetwork ? styles.controlButtonDisabled : styles.controlButton } - disabled={isTestNet(currentChainId)} + disabled={isTestNet(currentChainId) || !isPopularNetwork} /> { ) as TokenI[]; const allNetworks = useSelector(selectNetworkConfigurations); const chainId = useSelector(selectChainId); + const isPopularNetworks = useSelector(selectIsPopularNetwork); const selectedNetworkClientId = useSelector(selectNetworkClientId); const [ignoredTokens, setIgnoredTokens] = useState( {}, @@ -108,7 +110,7 @@ const DetectedTokens = () => { const styles = createStyles(colors); const currentDetectedTokens = - isPortfolioViewEnabled() && isAllNetworks + isPortfolioViewEnabled() && isAllNetworks && isPopularNetworks ? allDetectedTokens : detectedTokens; diff --git a/app/components/Views/NetworkSelector/NetworkSelector.tsx b/app/components/Views/NetworkSelector/NetworkSelector.tsx index a0db393144f..f4ff82e7ca1 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.tsx @@ -27,8 +27,8 @@ import BottomSheet, { import { IconName } from '../../../component-library/components/Icons/Icon'; import { useSelector } from 'react-redux'; import { - selectNetworkConfigurations, selectIsAllNetworks, + selectNetworkConfigurations, } from '../../../selectors/networkController'; import { selectShowTestNetworks } from '../../../selectors/preferencesController'; import Networks, { @@ -126,7 +126,7 @@ const NetworkSelector = () => { const styles = createStyles(colors); const sheetRef = useRef(null); const showTestNetworks = useSelector(selectShowTestNetworks); - const isAllNetworks = useSelector(selectIsAllNetworks); + const isAllNetwork = useSelector(selectIsAllNetworks); const networkConfigurations = useSelector(selectNetworkConfigurations); @@ -179,14 +179,19 @@ const NetworkSelector = () => { const setTokenNetworkFilter = useCallback( (chainId: string) => { + const isPopularNetwork = + chainId === CHAIN_IDS.MAINNET || + chainId === CHAIN_IDS.LINEA_MAINNET || + PopularList.some((network) => network.chainId === chainId); + const { PreferencesController } = Engine.context; - if (!isAllNetworks) { + if (!isAllNetwork && isPopularNetwork) { PreferencesController.setTokenNetworkFilter({ [chainId]: true, }); } }, - [isAllNetworks], + [isAllNetwork], ); const onRpcSelect = useCallback( diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index df2a6c4fef2..0aec38bdeae 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -55,7 +55,6 @@ import { selectTokens, selectTokensByChainIdAndAddress, } from '../../../selectors/tokensController'; -import { selectTokenNetworkFilter } from '../../../selectors/preferencesController'; import { NavigationProp, ParamListBase, @@ -93,6 +92,7 @@ import { PortfolioBalance } from '../../UI/Tokens/TokenList/PortfolioBalance'; import useCheckNftAutoDetectionModal from '../../hooks/useCheckNftAutoDetectionModal'; import useCheckMultiRpcModal from '../../hooks/useCheckMultiRpcModal'; import { selectContractBalances } from '../../../selectors/tokenBalancesController'; +import { selectTokenNetworkFilter } from '../../../selectors/preferencesController'; const createStyles = ({ colors, typography }: Theme) => StyleSheet.create({ @@ -310,6 +310,7 @@ const Wallet = ({ const networkImageSource = useSelector(selectNetworkImageSource); const tokenNetworkFilter = useSelector(selectTokenNetworkFilter); + /** * Shows Nft auto detect modal if the user is on mainnet, never saw the modal and have nft detection off */ diff --git a/app/components/hooks/AssetPolling/useAccountTrackerPolling.test.ts b/app/components/hooks/AssetPolling/useAccountTrackerPolling.test.ts new file mode 100644 index 00000000000..4aacec21e5f --- /dev/null +++ b/app/components/hooks/AssetPolling/useAccountTrackerPolling.test.ts @@ -0,0 +1,202 @@ +import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; +import Engine from '../../../core/Engine'; +import useAccountTrackerPolling from './useAccountTrackerPolling'; +// eslint-disable-next-line import/no-namespace +import * as networks from '../../../util/networks'; +import { RootState } from '../../../reducers'; + +jest.mock('../../../core/Engine', () => ({ + context: { + AccountTrackerController: { + startPolling: jest.fn(), + stopPollingByPollingToken: jest.fn(), + }, + }, +})); + +describe('useAccountTrackerPolling', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + const state = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x1': { + chainId: '0x1', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + defaultRpcEndpointIndex: 0, + }, + '0x89': { + chainId: '0x89', + rpcEndpoints: [ + { + networkClientId: 'otherNetworkClientId', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + AccountTrackerController: { + accountsByChainId: { + '0x1': {}, + '0x2': {}, + }, + }, + PreferencesController: { + tokenNetworkFilter: { + '0x1': true, + '0x89': true, + }, + }, + }, + }, + } as unknown as RootState; + + it('should poll all network configurations when portfolio view is enabled', () => { + jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(true); + + const { unmount } = renderHookWithProvider( + () => useAccountTrackerPolling(), + { state }, + ); + + const mockedAccountTrackerController = jest.mocked( + Engine.context.AccountTrackerController, + ); + + expect(mockedAccountTrackerController.startPolling).toHaveBeenCalledTimes( + 2, + ); + expect(mockedAccountTrackerController.startPolling).toHaveBeenCalledWith({ + networkClientId: 'selectedNetworkClientId', + }); + expect(mockedAccountTrackerController.startPolling).toHaveBeenCalledWith({ + networkClientId: 'otherNetworkClientId', + }); + + unmount(); + expect( + mockedAccountTrackerController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(2); + }); + + it('should use provided network client IDs when specified, even with portfolio view enabled', () => { + jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(true); + + const specificNetworkClientIds = [ + { networkClientId: 'specificNetworkClientId' }, + ]; + + const { unmount } = renderHookWithProvider( + () => + useAccountTrackerPolling({ + networkClientIds: specificNetworkClientIds, + }), + { state }, + ); + + const mockedAccountTrackerController = jest.mocked( + Engine.context.AccountTrackerController, + ); + + expect(mockedAccountTrackerController.startPolling).toHaveBeenCalledTimes( + 1, + ); + expect(mockedAccountTrackerController.startPolling).toHaveBeenCalledWith({ + networkClientId: 'specificNetworkClientId', + }); + + unmount(); + expect( + mockedAccountTrackerController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(1); + }); + + it('should return accountsByChainId from the state', () => { + const { result } = renderHookWithProvider( + () => useAccountTrackerPolling(), + { + state, + }, + ); + + expect(result.current.accountsByChainId).toEqual({ + '0x1': {}, + '0x2': {}, + }); + }); + + it('should poll only for current network if selected one is not popular', () => { + const { unmount } = renderHookWithProvider( + () => useAccountTrackerPolling(), + { + state: { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'otherNetworkClientId', + networkConfigurationsByChainId: { + '0x89': { + chainId: '0x89', + rpcEndpoints: [ + { + networkClientId: 'otherNetworkClientId', + }, + ], + defaultRpcEndpointIndex: 0, + }, + '0x82750': { + chainId: '0x82750', + rpcEndpoints: [ + { + networkClientId: 'otherNetworkClientId', + }, + ], + defaultRpcEndpointIndex: 0, + }, + }, + }, + AccountTrackerController: { + accountsByChainId: { + '0x89': {}, + '0x82750': {}, + }, + }, + PreferencesController: { + tokenNetworkFilter: { + '0x82750': true, + '0x89': true, + }, + }, + }, + }, + } as unknown as RootState, + }, + ); + + const mockedAccountTrackerController = jest.mocked( + Engine.context.AccountTrackerController, + ); + + expect(mockedAccountTrackerController.startPolling).toHaveBeenCalledTimes( + 1, + ); + expect(mockedAccountTrackerController.startPolling).toHaveBeenCalledWith({ + networkClientId: 'otherNetworkClientId', + }); + + unmount(); + expect( + mockedAccountTrackerController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/components/hooks/AssetPolling/useAccountTrackerPolling.ts b/app/components/hooks/AssetPolling/useAccountTrackerPolling.ts index 195d1226c6a..718de835d33 100644 --- a/app/components/hooks/AssetPolling/useAccountTrackerPolling.ts +++ b/app/components/hooks/AssetPolling/useAccountTrackerPolling.ts @@ -1,37 +1,44 @@ import { useSelector } from 'react-redux'; import usePolling from '../usePolling'; import { - selectNetworkConfigurations, + selectAllPopularNetworkConfigurations, + selectIsAllNetworks, + selectIsPopularNetwork, selectSelectedNetworkClientId, } from '../../../selectors/networkController'; import Engine from '../../../core/Engine'; -import { isPortfolioViewEnabled } from '../../../util/networks'; import { selectAccountsByChainId } from '../../../selectors/accountTrackerController'; +import { isPortfolioViewEnabled } from '../../../util/networks'; // Polls native currency prices across networks. const useAccountTrackerPolling = ({ networkClientIds, }: { networkClientIds?: { networkClientId: string }[] } = {}) => { // Selectors to determine polling input - const networkConfigurations = useSelector(selectNetworkConfigurations); + const networkConfigurationsPopularNetworks = useSelector( + selectAllPopularNetworkConfigurations, + ); + const isAllNetworksSelected = useSelector(selectIsAllNetworks); + const isPopularNetwork = useSelector(selectIsPopularNetwork); + const selectedNetworkClientId = useSelector(selectSelectedNetworkClientId); const accountsByChainId = useSelector(selectAccountsByChainId); - const networkClientIdsConfig = Object.values(networkConfigurations).map( - (network) => ({ - networkClientId: - network?.rpcEndpoints?.[network?.defaultRpcEndpointIndex] - ?.networkClientId, - }), - ); + const networkClientIdsConfig = Object.values( + networkConfigurationsPopularNetworks, + ).map((network) => ({ + networkClientId: + network?.rpcEndpoints?.[network?.defaultRpcEndpointIndex] + ?.networkClientId, + })); + + // if all networks are selected, poll all popular networks + const networkConfigurationsToPoll = + isAllNetworksSelected && isPopularNetwork && isPortfolioViewEnabled() + ? networkClientIdsConfig + : [{ networkClientId: selectedNetworkClientId }]; - const chainIdsToPoll = isPortfolioViewEnabled() - ? networkClientIds ?? networkClientIdsConfig - : [ - { - networkClientId: selectedNetworkClientId, - }, - ]; + const chainIdsToPoll = networkClientIds ?? networkConfigurationsToPoll; const { AccountTrackerController } = Engine.context; diff --git a/app/components/hooks/AssetPolling/useCurrencyRatePolling.test.ts b/app/components/hooks/AssetPolling/useCurrencyRatePolling.test.ts index 1c2fbc6403e..0ab00825675 100644 --- a/app/components/hooks/AssetPolling/useCurrencyRatePolling.test.ts +++ b/app/components/hooks/AssetPolling/useCurrencyRatePolling.test.ts @@ -1,6 +1,7 @@ import useCurrencyRatePolling from './useCurrencyRatePolling'; import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; import Engine from '../../../core/Engine'; +import { RootState } from '../../../reducers'; jest.mock('../../../core/Engine', () => ({ context: { @@ -17,18 +18,37 @@ describe('useCurrencyRatePolling', () => { engine: { backgroundState: { NetworkController: { + selectedNetworkClientId: 'selectedNetworkClientId', networkConfigurationsByChainId: { '0x1': { + chainId: '0x1', nativeCurrency: 'ETH', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], }, '0x89': { + chainId: '0x89', nativeCurrency: 'POL', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], }, }, }, + PreferencesController: { + tokenNetworkFilter: { + '0x1': true, + '0x89': true, + }, + }, }, }, - }; + } as unknown as RootState; renderHookWithProvider(() => useCurrencyRatePolling(), { state }); @@ -36,4 +56,48 @@ describe('useCurrencyRatePolling', () => { jest.mocked(Engine.context.CurrencyRateController.startPolling), ).toHaveBeenCalledWith({ nativeCurrencies: ['ETH', 'POL'] }); }); + + it('should poll only for current network if selected one is not popular', async () => { + const state = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x82750': { + nativeCurrency: 'SCROLL', + chainId: '0x82750', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + '0x89': { + chainId: '0x89', + nativeCurrency: 'POL', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], + }, + }, + }, + PreferencesController: { + tokenNetworkFilter: { + '0x82750': true, + '0x89': true, + }, + }, + }, + }, + } as unknown as RootState; + + renderHookWithProvider(() => useCurrencyRatePolling(), { state }); + + expect( + jest.mocked(Engine.context.CurrencyRateController.startPolling), + ).toHaveBeenCalledWith({ nativeCurrencies: ['SCROLL'] }); + }); }); diff --git a/app/components/hooks/AssetPolling/useCurrencyRatePolling.ts b/app/components/hooks/AssetPolling/useCurrencyRatePolling.ts index 7ff94f3bd89..755663827ad 100644 --- a/app/components/hooks/AssetPolling/useCurrencyRatePolling.ts +++ b/app/components/hooks/AssetPolling/useCurrencyRatePolling.ts @@ -1,25 +1,50 @@ import { useSelector } from 'react-redux'; import usePolling from '../usePolling'; -import { selectNetworkConfigurations } from '../../../selectors/networkController'; +import { + selectAllPopularNetworkConfigurations, + selectChainId, + selectIsAllNetworks, + selectIsPopularNetwork, + selectNetworkConfigurations, +} from '../../../selectors/networkController'; import Engine from '../../../core/Engine'; import { selectConversionRate, selectCurrencyRates, } from '../../../selectors/currencyRateController'; +import { isPortfolioViewEnabled } from '../../../util/networks'; // Polls native currency prices across networks. const useCurrencyRatePolling = () => { // Selectors to determine polling input + const networkConfigurationsPopularNetworks = useSelector( + selectAllPopularNetworkConfigurations, + ); const networkConfigurations = useSelector(selectNetworkConfigurations); + const currentChainId = useSelector(selectChainId); + const isAllNetworksSelected = useSelector(selectIsAllNetworks); + const isPopularNetwork = useSelector(selectIsPopularNetwork); // Selectors returning state updated by the polling const conversionRate = useSelector(selectConversionRate); const currencyRates = useSelector(selectCurrencyRates); + // if all networks are selected, poll all popular networks + const networkConfigurationsToPoll = + isAllNetworksSelected && isPopularNetwork && isPortfolioViewEnabled() + ? Object.values(networkConfigurationsPopularNetworks).map((network) => ({ + nativeCurrency: network.nativeCurrency, + })) + : [ + { + nativeCurrency: + networkConfigurations[currentChainId].nativeCurrency, + }, + ]; + + // get all native currencies to poll const nativeCurrencies = [ - ...new Set( - Object.values(networkConfigurations).map((n) => n.nativeCurrency), - ), + ...new Set(networkConfigurationsToPoll.map((n) => n.nativeCurrency)), ]; const { CurrencyRateController } = Engine.context; diff --git a/app/components/hooks/AssetPolling/useTokenBalancesPolling.test.ts b/app/components/hooks/AssetPolling/useTokenBalancesPolling.test.ts index 2411b4c6f61..6795ce4dc96 100644 --- a/app/components/hooks/AssetPolling/useTokenBalancesPolling.test.ts +++ b/app/components/hooks/AssetPolling/useTokenBalancesPolling.test.ts @@ -3,6 +3,7 @@ import Engine from '../../../core/Engine'; import useTokenBalancesPolling from './useTokenBalancesPolling'; // eslint-disable-next-line import/no-namespace import * as networks from '../../../util/networks'; +import { RootState } from '../../../reducers'; jest.mock('../../../core/Engine', () => ({ context: { @@ -36,12 +37,25 @@ describe('useTokenBalancesPolling', () => { }, ], }, - '0x89': {}, + '0x89': { + chainId: '0x89', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], + }, + }, + }, + PreferencesController: { + tokenNetworkFilter: { + [selectedChainId]: true, + '0x89': true, }, }, }, }, - }; + } as unknown as RootState; it('should poll by selected chain id when portfolio view is disabled', () => { jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(false); @@ -119,4 +133,60 @@ describe('useTokenBalancesPolling', () => { mockedTokenBalancesController.stopPollingByPollingToken, ).toHaveBeenCalledTimes(1); }); + + it('should poll only for current network if selected one is not popular', () => { + jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(true); + + const { unmount } = renderHookWithProvider( + () => useTokenBalancesPolling(), + { + state: { + ...state, + engine: { + ...state.engine, + backgroundState: { + ...state.engine.backgroundState, + AccountsController: { + internalAccounts: { + selectedAccount: '1', + accounts: { + '1': { + address: undefined, + }, + }, + }, + }, + NetworkController: { + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x82750': { + chainId: '0x82750', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + }, + }, + }, + }, + }, + }, + ); + + const mockedTokenBalancesController = jest.mocked( + Engine.context.TokenBalancesController, + ); + + expect(mockedTokenBalancesController.startPolling).toHaveBeenCalledTimes(1); + expect(mockedTokenBalancesController.startPolling).toHaveBeenCalledWith({ + chainId: '0x82750', + }); + + unmount(); + expect( + mockedTokenBalancesController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(1); + }); }); diff --git a/app/components/hooks/AssetPolling/useTokenBalancesPolling.ts b/app/components/hooks/AssetPolling/useTokenBalancesPolling.ts index d2f0ca1cc1c..2cc3b416ecf 100644 --- a/app/components/hooks/AssetPolling/useTokenBalancesPolling.ts +++ b/app/components/hooks/AssetPolling/useTokenBalancesPolling.ts @@ -2,8 +2,10 @@ import { useSelector } from 'react-redux'; import usePolling from '../usePolling'; import Engine from '../../../core/Engine'; import { + selectAllPopularNetworkConfigurations, selectChainId, - selectNetworkConfigurations, + selectIsAllNetworks, + selectIsPopularNetwork, } from '../../../selectors/networkController'; import { Hex } from '@metamask/utils'; import { isPortfolioViewEnabled } from '../../../util/networks'; @@ -11,15 +13,24 @@ import { selectAllTokenBalances } from '../../../selectors/tokenBalancesControll const useTokenBalancesPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { // Selectors to determine polling input - const networkConfigurations = useSelector(selectNetworkConfigurations); + const networkConfigurationsPopularNetworks = useSelector( + selectAllPopularNetworkConfigurations, + ); const currentChainId = useSelector(selectChainId); + const isAllNetworksSelected = useSelector(selectIsAllNetworks); + const isPopularNetwork = useSelector(selectIsPopularNetwork); // Selectors returning state updated by the polling const tokenBalances = useSelector(selectAllTokenBalances); - const chainIdsToPoll = isPortfolioViewEnabled() - ? chainIds ?? Object.keys(networkConfigurations) - : [currentChainId]; + const networkConfigurationsToPoll = + isAllNetworksSelected && isPopularNetwork && isPortfolioViewEnabled() + ? Object.values(networkConfigurationsPopularNetworks).map( + (network) => network.chainId, + ) + : [currentChainId]; + + const chainIdsToPoll = chainIds ?? networkConfigurationsToPoll; const { TokenBalancesController } = Engine.context; diff --git a/app/components/hooks/AssetPolling/useTokenDetectionPolling.test.ts b/app/components/hooks/AssetPolling/useTokenDetectionPolling.test.ts index b454c1be5e8..0ef7649d2c1 100644 --- a/app/components/hooks/AssetPolling/useTokenDetectionPolling.test.ts +++ b/app/components/hooks/AssetPolling/useTokenDetectionPolling.test.ts @@ -3,6 +3,7 @@ import Engine from '../../../core/Engine'; import useTokenDetectionPolling from './useTokenDetectionPolling'; // eslint-disable-next-line import/no-namespace import * as networks from '../../../util/networks'; +import { RootState } from '../../../reducers'; jest.mock('../../../core/Engine', () => ({ context: { @@ -36,6 +37,10 @@ describe('useTokenDetectionPolling', () => { }, PreferencesController: { useTokenDetection: true, + tokenNetworkFilter: { + '0x1': true, + '0x89': true, + }, }, NetworkController: { selectedNetworkClientId: 'selectedNetworkClientId', @@ -52,7 +57,7 @@ describe('useTokenDetectionPolling', () => { }, }, }, - }; + } as unknown as RootState; it('Should poll by current chain ids/address, and stop polling on dismount', async () => { const { unmount } = renderHookWithProvider( @@ -263,4 +268,61 @@ describe('useTokenDetectionPolling', () => { mockedTokenDetectionController.stopPollingByPollingToken, ).toHaveBeenCalledTimes(1); }); + + it('should poll only for current network if selected one is not popular', () => { + const { unmount } = renderHookWithProvider( + () => useTokenDetectionPolling(), + { + state: { + ...state, + engine: { + ...state.engine, + backgroundState: { + ...state.engine.backgroundState, + AccountsController: { + internalAccounts: { + selectedAccount: '1', + accounts: { + '1': { + address: undefined, + }, + }, + }, + }, + NetworkController: { + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x82750': { + chainId: '0x82750', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + }, + }, + }, + }, + }, + }, + ); + + const mockedTokenDetectionController = jest.mocked( + Engine.context.TokenDetectionController, + ); + + expect(mockedTokenDetectionController.startPolling).toHaveBeenCalledTimes( + 1, + ); + expect(mockedTokenDetectionController.startPolling).toHaveBeenCalledWith({ + chainIds: ['0x82750'], + address: undefined, + }); + + unmount(); + expect( + mockedTokenDetectionController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(1); + }); }); diff --git a/app/components/hooks/AssetPolling/useTokenDetectionPolling.ts b/app/components/hooks/AssetPolling/useTokenDetectionPolling.ts index dccdf39acbd..85139b6fde3 100644 --- a/app/components/hooks/AssetPolling/useTokenDetectionPolling.ts +++ b/app/components/hooks/AssetPolling/useTokenDetectionPolling.ts @@ -2,8 +2,10 @@ import { useSelector } from 'react-redux'; import usePolling from '../usePolling'; import Engine from '../../../core/Engine'; import { + selectAllPopularNetworkConfigurations, selectChainId, - selectNetworkConfigurations, + selectIsAllNetworks, + selectIsPopularNetwork, } from '../../../selectors/networkController'; import { Hex } from '@metamask/utils'; import { isPortfolioViewEnabled } from '../../../util/networks'; @@ -11,14 +13,25 @@ import { selectSelectedInternalAccount } from '../../../selectors/accountsContro import { selectUseTokenDetection } from '../../../selectors/preferencesController'; const useTokenDetectionPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { - const networkConfigurations = useSelector(selectNetworkConfigurations); + const networkConfigurationsPopularNetworks = useSelector( + selectAllPopularNetworkConfigurations, + ); const currentChainId = useSelector(selectChainId); const selectedAccount = useSelector(selectSelectedInternalAccount); const useTokenDetection = useSelector(selectUseTokenDetection); + const isAllNetworksSelected = useSelector(selectIsAllNetworks); + const isPopularNetwork = useSelector(selectIsPopularNetwork); - const chainIdsToPoll = isPortfolioViewEnabled() - ? chainIds ?? Object.keys(networkConfigurations) - : [currentChainId]; + // if all networks are selected, poll all popular networks + const filteredChainIds = + isAllNetworksSelected && isPopularNetwork && isPortfolioViewEnabled() + ? Object.values(networkConfigurationsPopularNetworks).map( + (network) => network.chainId, + ) + : [currentChainId]; + + // if portfolio view is enabled, poll all chain ids + const chainIdsToPoll = chainIds ?? filteredChainIds; const { TokenDetectionController } = Engine.context; diff --git a/app/components/hooks/AssetPolling/useTokenListPolling.test.ts b/app/components/hooks/AssetPolling/useTokenListPolling.test.ts index 6ffc511088e..66ecf1a2e3d 100644 --- a/app/components/hooks/AssetPolling/useTokenListPolling.test.ts +++ b/app/components/hooks/AssetPolling/useTokenListPolling.test.ts @@ -3,6 +3,7 @@ import Engine from '../../../core/Engine'; import useTokenListPolling from './useTokenListPolling'; // eslint-disable-next-line import/no-namespace import * as networks from '../../../util/networks'; +import { RootState } from '../../../reducers'; jest.mock('../../../core/Engine', () => ({ context: { @@ -33,12 +34,26 @@ describe('useTokenListPolling', () => { }, ], }, - '0x89': {}, + '0x89': { + chainId: '0x89', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId2', + }, + ], + }, + }, + }, + PreferencesController: { + useTokenDetection: true, + tokenNetworkFilter: { + '0x1': true, + '0x89': true, }, }, }, }, - }; + } as unknown as RootState; it('Should poll by selected chain id, and stop polling on dismount', async () => { const { unmount } = renderHookWithProvider(() => useTokenListPolling(), { @@ -89,4 +104,55 @@ describe('useTokenListPolling', () => { mockedTokenListController.stopPollingByPollingToken, ).toHaveBeenCalledTimes(2); }); + + it('should poll only for current network if selected one is not popular', () => { + jest.spyOn(networks, 'isPortfolioViewEnabled').mockReturnValue(true); + + const stateToTest = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x82750': { + chainId: '0x82750', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + }, + }, + PreferencesController: { + useTokenDetection: true, + tokenNetworkFilter: { + '0x82750': true, + }, + }, + }, + }, + } as unknown as RootState; + + const { unmount } = renderHookWithProvider(() => useTokenListPolling(), { + state: stateToTest, + }); + + const mockedTokenListController = jest.mocked( + Engine.context.TokenListController, + ); + + expect(mockedTokenListController.startPolling).toHaveBeenCalledTimes(1); + expect(mockedTokenListController.startPolling).toHaveBeenCalledWith({ + chainId: '0x82750', + }); + expect(mockedTokenListController.startPolling).toHaveBeenCalledWith({ + chainId: '0x82750', + }); + + unmount(); + expect( + mockedTokenListController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(1); + }); }); diff --git a/app/components/hooks/AssetPolling/useTokenListPolling.ts b/app/components/hooks/AssetPolling/useTokenListPolling.ts index cccce3f4c15..aacf8ab782b 100644 --- a/app/components/hooks/AssetPolling/useTokenListPolling.ts +++ b/app/components/hooks/AssetPolling/useTokenListPolling.ts @@ -2,8 +2,10 @@ import { useSelector } from 'react-redux'; import usePolling from '../usePolling'; import Engine from '../../../core/Engine'; import { + selectAllPopularNetworkConfigurations, selectChainId, - selectNetworkConfigurations, + selectIsAllNetworks, + selectIsPopularNetwork, } from '../../../selectors/networkController'; import { Hex } from '@metamask/utils'; import { isPortfolioViewEnabled } from '../../../util/networks'; @@ -14,16 +16,26 @@ import { const useTokenListPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { // Selectors to determine polling input - const networkConfigurations = useSelector(selectNetworkConfigurations); + const networkConfigurationsPopularNetworks = useSelector( + selectAllPopularNetworkConfigurations, + ); const currentChainId = useSelector(selectChainId); + const isAllNetworksSelected = useSelector(selectIsAllNetworks); + const isPopularNetwork = useSelector(selectIsPopularNetwork); // Selectors returning state updated by the polling const tokenList = useSelector(selectTokenList); const tokenListByChain = useSelector(selectERC20TokensByChain); - const chainIdsToPoll = isPortfolioViewEnabled() - ? chainIds ?? Object.keys(networkConfigurations) - : [currentChainId]; + // if all networks are selected, poll all popular networks + const filteredChainIds = + isAllNetworksSelected && isPopularNetwork && isPortfolioViewEnabled() + ? Object.values(networkConfigurationsPopularNetworks).map( + (network) => network.chainId, + ) + : [currentChainId]; + + const chainIdsToPoll = chainIds ?? filteredChainIds; const { TokenListController } = Engine.context; diff --git a/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts b/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts index 577737c40c6..164896f055d 100644 --- a/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts +++ b/app/components/hooks/AssetPolling/useTokenRatesPolling.test.ts @@ -1,6 +1,7 @@ import useTokenRatesPolling from './useTokenRatesPolling'; import { renderHookWithProvider } from '../../../util/test/renderWithProvider'; import Engine from '../../../core/Engine'; +import { RootState } from '../../../reducers'; jest.mock('../../../core/Engine', () => ({ context: { @@ -28,9 +29,16 @@ describe('useTokenRatesPolling', () => { '0x89': {}, }, }, + PreferencesController: { + useTokenDetection: true, + tokenNetworkFilter: { + '0x1': true, + '0x89': true, + }, + }, }, }, - }; + } as unknown as RootState; it('Should poll by provided chain ids, and stop polling on dismount', async () => { const { unmount } = renderHookWithProvider( @@ -55,4 +63,58 @@ describe('useTokenRatesPolling', () => { mockedTokenRatesController.stopPollingByPollingToken, ).toHaveBeenCalledTimes(1); }); + + it('should poll only for current network if selected one is not popular', () => { + const stateToTest = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'selectedNetworkClientId', + networkConfigurationsByChainId: { + '0x82750': { + chainId: '0x82750', + rpcEndpoints: [ + { + networkClientId: 'selectedNetworkClientId', + }, + ], + }, + }, + }, + TokenRatesController: { + marketData: { + '0x82750': {}, + }, + }, + PreferencesController: { + useTokenDetection: true, + tokenNetworkFilter: { + '0x82750': true, + }, + }, + }, + }, + } as unknown as RootState; + + const { unmount } = renderHookWithProvider(() => useTokenRatesPolling(), { + state: stateToTest, + }); + + const mockedTokenRatesController = jest.mocked( + Engine.context.TokenRatesController, + ); + + expect(mockedTokenRatesController.startPolling).toHaveBeenCalledTimes(1); + expect(mockedTokenRatesController.startPolling).toHaveBeenCalledWith({ + chainId: '0x82750', + }); + + expect( + mockedTokenRatesController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(0); + unmount(); + expect( + mockedTokenRatesController.stopPollingByPollingToken, + ).toHaveBeenCalledTimes(1); + }); }); diff --git a/app/components/hooks/AssetPolling/useTokenRatesPolling.ts b/app/components/hooks/AssetPolling/useTokenRatesPolling.ts index 25351bccd5b..b08b31b6f15 100644 --- a/app/components/hooks/AssetPolling/useTokenRatesPolling.ts +++ b/app/components/hooks/AssetPolling/useTokenRatesPolling.ts @@ -2,8 +2,10 @@ import { useSelector } from 'react-redux'; import usePolling from '../usePolling'; import Engine from '../../../core/Engine'; import { + selectAllPopularNetworkConfigurations, selectChainId, - selectNetworkConfigurations, + selectIsAllNetworks, + selectIsPopularNetwork, } from '../../../selectors/networkController'; import { Hex } from '@metamask/utils'; import { @@ -14,16 +16,26 @@ import { isPortfolioViewEnabled } from '../../../util/networks'; const useTokenRatesPolling = ({ chainIds }: { chainIds?: Hex[] } = {}) => { // Selectors to determine polling input - const networkConfigurations = useSelector(selectNetworkConfigurations); + const networkConfigurationsPopularNetworks = useSelector( + selectAllPopularNetworkConfigurations, + ); const currentChainId = useSelector(selectChainId); + const isPopularNetwork = useSelector(selectIsPopularNetwork); + const isAllNetworksSelected = useSelector(selectIsAllNetworks); // Selectors returning state updated by the polling const contractExchangeRates = useSelector(selectContractExchangeRates); const tokenMarketData = useSelector(selectTokenMarketData); - const chainIdsToPoll = isPortfolioViewEnabled() - ? chainIds ?? Object.keys(networkConfigurations) - : [currentChainId]; + // if all networks are selected, poll all popular networks + const filteredChainIds = + isAllNetworksSelected && isPopularNetwork && isPortfolioViewEnabled() + ? Object.values(networkConfigurationsPopularNetworks).map( + (network) => network.chainId, + ) + : [currentChainId]; + + const chainIdsToPoll = chainIds ?? filteredChainIds; const { TokenRatesController } = Engine.context; diff --git a/app/selectors/networkController.test.ts b/app/selectors/networkController.test.ts index 0c3946640b6..6bc77fab3cd 100644 --- a/app/selectors/networkController.test.ts +++ b/app/selectors/networkController.test.ts @@ -35,8 +35,8 @@ describe('networkSelectors', () => { ], blockExplorerUrls: ['https://etherscan.io'], }, - '0x2': { - chainId: '0x2', + '0x89': { + chainId: '0x89', nativeCurrency: 'MATIC', name: 'Polygon', rpcEndpoints: [ @@ -65,7 +65,7 @@ describe('networkSelectors', () => { it('selectProviderConfig should return the provider config for the selected network', () => { expect(selectProviderConfig(mockState)).toEqual({ - chainId: '0x2', + chainId: '0x89', ticker: 'MATIC', rpcPrefs: { blockExplorerUrl: 'https://polygonscan.com' }, type: 'rpc', @@ -80,7 +80,7 @@ describe('networkSelectors', () => { }); it('selectChainId should return the chainId of the provider config', () => { - expect(selectChainId(mockState)).toBe('0x2'); + expect(selectChainId(mockState)).toBe('0x89'); }); it('selectProviderType should return the type of the provider config', () => { @@ -111,14 +111,30 @@ describe('networkSelectors', () => { }); it('selectIsAllNetworks should return false if tokenNetworkFilter length is greater than 1', () => { - const tokenNetworkFilter = { '0x1': 'true' }; - expect(selectIsAllNetworks.resultFunc(tokenNetworkFilter)).toBe(false); + expect( + selectIsAllNetworks({ + ...mockState, + engine: { + ...mockState.engine, + backgroundState: { + ...mockState.engine.backgroundState, + NetworkController: { + ...mockState.engine.backgroundState.NetworkController, + }, + PreferencesController: { + ...mockState.engine.backgroundState.PreferencesController, + tokenNetworkFilter: { '0x1': 'true' }, + }, + }, + }, + }), + ).toBe(false); }); it('selectNetworkConfigurationByChainId should return the network configuration for a given chainId', () => { - expect(selectNetworkConfigurationByChainId(mockState, '0x2')).toEqual( + expect(selectNetworkConfigurationByChainId(mockState, '0x89')).toEqual( mockState.engine.backgroundState.NetworkController - .networkConfigurationsByChainId['0x2'], + .networkConfigurationsByChainId['0x89'], ); }); @@ -131,7 +147,7 @@ describe('networkSelectors', () => { noMatchState.engine.backgroundState.NetworkController.selectedNetworkClientId = 'unknown-network'; expect(selectProviderConfig(noMatchState)).toEqual({ - chainId: '0x2', + chainId: '0x89', id: 'custom-network', nickname: 'Polygon', rpcPrefs: { diff --git a/app/selectors/networkController.ts b/app/selectors/networkController.ts index d74a47d6074..cb18e5e4deb 100644 --- a/app/selectors/networkController.ts +++ b/app/selectors/networkController.ts @@ -12,6 +12,9 @@ import { RootState } from '../reducers'; import { createDeepEqualSelector } from './util'; import { NETWORKS_CHAIN_ID } from '../constants/network'; import { selectTokenNetworkFilter } from './preferencesController'; +import { enableAllNetworksFilter } from '../components/UI/Tokens/util/enableAllNetworksFilter'; +import { PopularList } from '../util/networks/customNetworks'; +import { CHAIN_IDS } from '@metamask/transaction-controller'; interface InfuraRpcEndpoint { name?: string; @@ -167,13 +170,47 @@ export const selectIsEIP1559Network = createSelector( ].EIPS[1559] === true, ); +// Selector to get the popular network configurations, this filter also testnet networks +export const selectAllPopularNetworkConfigurations = createSelector( + selectNetworkConfigurations, + (networkConfigurations: Record) => { + const popularNetworksChainIds = PopularList.map( + (popular) => popular.chainId, + ); + + return Object.keys(networkConfigurations) + .filter( + (chainId) => + popularNetworksChainIds.includes(chainId as Hex) || + chainId === CHAIN_IDS.MAINNET || + chainId === CHAIN_IDS.LINEA_MAINNET, + ) + .reduce((acc: Record, chainId) => { + acc[chainId as Hex] = networkConfigurations[chainId as Hex]; + return acc; + }, {}); + }, +); + +export const selectIsPopularNetwork = createSelector( + selectChainId, + (chainId) => + chainId === CHAIN_IDS.MAINNET || + chainId === CHAIN_IDS.LINEA_MAINNET || + PopularList.some((network) => network.chainId === chainId), +); + export const selectIsAllNetworks = createSelector( + selectAllPopularNetworkConfigurations, (state: RootState) => selectTokenNetworkFilter(state), - (tokenNetworkFilter) => { + (popularNetworkConfigurations, tokenNetworkFilter) => { if (Object.keys(tokenNetworkFilter).length === 1) { return false; } - return true; + const allNetworks = enableAllNetworksFilter(popularNetworkConfigurations); + return ( + Object.keys(tokenNetworkFilter).length === Object.keys(allNetworks).length + ); }, );