From bd54012ec0f1459a879532cdee935a6383a21dab Mon Sep 17 00:00:00 2001 From: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:41:28 -0400 Subject: [PATCH] move `getProviderConfig` out of `ducks/metamask.js` to `selectors/networks.ts` `getProviderConfig` is so widely used in the codebase, and makes use of multiple selectors itself, it makes it very complicated to untangle. I've put it in its own file just to, hopefully temporarily, simplify untangling other circular dependency issues. --- app/scripts/lib/ppom/ppom-middleware.ts | 2 +- app/scripts/lib/ppom/ppom-util.ts | 2 +- app/scripts/metamask-controller.js | 4 +- shared/constants/bridge.ts | 2 + shared/constants/multichain/networks.ts | 2 + shared/constants/network.ts | 2 + .../modules/selectors/smart-transactions.ts | 14 +- test/data/mock-state.json | 8 +- .../app/currency-input/currency-input.js | 6 +- .../loading-network-screen.container.js | 2 +- .../transaction-already-confirmed.tsx | 9 +- .../selected-account.container.js | 2 +- .../wrong-network-notification.tsx | 9 +- .../address-copy-button.js | 2 +- .../asset-picker-modal-network.tsx | 6 +- .../ui/new-network-info/new-network-info.js | 2 +- ui/ducks/bridge/selectors.ts | 17 +-- ui/ducks/metamask/metamask.js | 43 +----- ui/ducks/send/send.js | 2 +- ui/hooks/bridge/useBridging.ts | 4 +- ui/hooks/ramps/useRamps/useRamps.ts | 11 +- ui/hooks/useMMICustodySendTransaction.ts | 2 +- ui/hooks/useTokenTracker.js | 3 +- ui/pages/asset/components/asset-page.tsx | 5 +- .../asset/components/chart/asset-chart.tsx | 3 +- ui/pages/asset/components/native-asset.tsx | 6 +- ui/pages/asset/useHistoricalPrices.ts | 3 +- ui/pages/asset/util.ts | 3 +- ui/pages/bridge/index.tsx | 7 +- .../confirm-subtitle/confirm-subtitle.test.js | 2 +- .../signature-request-header.js | 1 - .../confirm-transaction-base.container.js | 6 +- .../confirmations/hooks/useTransactionInfo.js | 2 +- .../send/gas-display/gas-display.js | 6 +- ui/pages/routes/routes.container.js | 6 +- .../edit-contact/edit-contact.container.js | 2 +- .../security-tab/security-tab.container.js | 2 +- .../settings-tab/settings-tab.container.js | 2 +- ui/pages/settings/settings.container.js | 2 +- ui/pages/swaps/hooks/useUpdateSwapsState.ts | 3 +- ui/selectors/confirm-transaction.js | 2 +- ui/selectors/index.js | 1 + ui/selectors/institutional/selectors.test.ts | 17 ++- ui/selectors/institutional/selectors.ts | 7 +- ui/selectors/multichain.test.ts | 6 +- ui/selectors/multichain.ts | 12 +- ui/selectors/networks.test.ts | 129 ++++++++++++++++++ ui/selectors/networks.ts | 114 ++++++++++++++++ ui/selectors/selectors.js | 51 ++----- ui/selectors/selectors.test.js | 59 +------- ui/selectors/transactions.js | 2 +- ui/store/actions.ts | 6 +- 52 files changed, 386 insertions(+), 237 deletions(-) create mode 100644 ui/selectors/networks.test.ts create mode 100644 ui/selectors/networks.ts diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index ebfdbe3f04d7..4188b8e1eb96 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -15,7 +15,7 @@ import { PreferencesController } from '../../controllers/preferences-controller' import { AppStateController } from '../../controllers/app-state-controller'; import { LOADING_SECURITY_ALERT_RESPONSE } from '../../../../shared/constants/security-provider'; // eslint-disable-next-line import/no-restricted-paths -import { getProviderConfig } from '../../../../ui/ducks/metamask/metamask'; +import { getProviderConfig } from '../../../../ui/selectors'; import { trace, TraceContext, TraceName } from '../../../../shared/lib/trace'; import { generateSecurityAlertId, diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index 7662c364b651..738ee673ae3b 100644 --- a/app/scripts/lib/ppom/ppom-util.ts +++ b/app/scripts/lib/ppom/ppom-util.ts @@ -135,7 +135,7 @@ export async function isChainSupported(chainId: Hex): Promise { `Error fetching supported chains from security alerts API`, ); } - return supportedChainIds.includes(chainId); + return supportedChainIds.includes(chainId as Hex); } function normalizePPOMRequest( diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b43ef72cae5c..b5403f4d4dc0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -237,10 +237,10 @@ import { } from '../../shared/lib/transactions-controller-utils'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths -import { getCurrentChainId } from '../../ui/selectors'; +import { getCurrentChainId } from '../../ui/selectors/selectors'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths -import { getProviderConfig } from '../../ui/ducks/metamask/metamask'; +import { getProviderConfig } from '../../ui/selectors/networks'; import { endTrace, trace } from '../../shared/lib/trace'; // eslint-disable-next-line import/no-restricted-paths import { isSnapId } from '../../ui/helpers/utils/snaps'; diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts index e87b0689777f..ca87f06460b9 100644 --- a/shared/constants/bridge.ts +++ b/shared/constants/bridge.ts @@ -13,6 +13,8 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [ CHAIN_IDS.BASE, ]; +export type AllowedBridgeChainIds = (typeof ALLOWED_BRIDGE_CHAIN_IDS)[number]; + export const BRIDGE_DEV_API_BASE_URL = 'https://bridge.dev-api.cx.metamask.io'; export const BRIDGE_PROD_API_BASE_URL = 'https://bridge.api.cx.metamask.io'; export const BRIDGE_API_BASE_URL = process.env.BRIDGE_USE_DEV_APIS diff --git a/shared/constants/multichain/networks.ts b/shared/constants/multichain/networks.ts index 5217394a5415..f3df27243cad 100644 --- a/shared/constants/multichain/networks.ts +++ b/shared/constants/multichain/networks.ts @@ -18,6 +18,8 @@ export type MultichainProviderConfig = ProviderConfigWithImageUrl & { isAddressCompatible: (address: string) => boolean; }; +export type MultichainNetworkIds = `${MultichainNetworks}`; + export enum MultichainNetworks { BITCOIN = 'bip122:000000000019d6689c085ae165831e93', BITCOIN_TESTNET = 'bip122:000000000933ea01ad0ee984209779ba', diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 64d330b73b2c..c7b0175411b5 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -87,6 +87,8 @@ export const NETWORK_TYPES = { LINEA_MAINNET: 'linea-mainnet', } as const; +export type NetworkTypes = (typeof NETWORK_TYPES)[keyof typeof NETWORK_TYPES]; + /** * An object containing shortcut names for any non-builtin network. We need * this to be able to differentiate between networks that require custom diff --git a/shared/modules/selectors/smart-transactions.ts b/shared/modules/selectors/smart-transactions.ts index a02fe63692b3..2850bd43e17f 100644 --- a/shared/modules/selectors/smart-transactions.ts +++ b/shared/modules/selectors/smart-transactions.ts @@ -14,9 +14,11 @@ import { } from '../../../ui/selectors/selectors'; // TODO: Migrate shared selectors to this file. import { isProduction } from '../environment'; -// TODO: Remove restricted import +// TODO: Remove restricted imports // eslint-disable-next-line import/no-restricted-paths import { MultichainState } from '../../../ui/selectors/multichain'; +// eslint-disable-next-line import/no-restricted-paths +import { NetworkState } from '../../../ui/selectors/networks'; type SmartTransactionsMetaMaskState = { metamask: { @@ -120,9 +122,7 @@ export const getCurrentChainSupportsSmartTransactions = ( return getAllowedSmartTransactionsChainIds().includes(chainId); }; -const getIsAllowedRpcUrlForSmartTransactions = ( - state: SmartTransactionsMetaMaskState, -) => { +const getIsAllowedRpcUrlForSmartTransactions = (state: NetworkState) => { const chainId = getCurrentChainId(state); if (!isProduction() || SKIP_STX_RPC_URL_CHECK_CHAIN_IDS.includes(chainId)) { // Allow any STX RPC URL in development and testing environments or for specific chain IDs. @@ -151,7 +151,7 @@ const hasNonZeroBalance = (state: SmartTransactionsMetaMaskState) => { }; export const getIsSmartTransactionsOptInModalAvailable = ( - state: SmartTransactionsMetaMaskState, + state: SmartTransactionsMetaMaskState & NetworkState, ) => { return ( getCurrentChainSupportsSmartTransactions(state) && @@ -162,7 +162,7 @@ export const getIsSmartTransactionsOptInModalAvailable = ( }; export const getSmartTransactionsEnabled = ( - state: SmartTransactionsMetaMaskState, + state: SmartTransactionsMetaMaskState & NetworkState, ): boolean => { const supportedAccount = accountSupportsSmartTx(state); // TODO: Create a new proxy service only for MM feature flags. @@ -181,7 +181,7 @@ export const getSmartTransactionsEnabled = ( }; export const getIsSmartTransaction = ( - state: SmartTransactionsMetaMaskState, + state: SmartTransactionsMetaMaskState & NetworkState, ): boolean => { const smartTransactionsPreferenceEnabled = getSmartTransactionsPreferenceEnabled(state); diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 2865478912f3..cdc856990261 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -392,26 +392,30 @@ "name": "Custom Mainnet RPC", "nativeCurrency": "ETH", "defaultRpcEndpointIndex": 0, + "ticker": "ETH", "rpcEndpoints": [ { "type": "custom", "url": "https://testrpc.com", "networkClientId": "testNetworkConfigurationId" } - ] + ], + "blockExplorerUrls": [] }, "0x5": { "chainId": "0x5", "name": "Goerli", "nativeCurrency": "ETH", "defaultRpcEndpointIndex": 0, + "ticker": "ETH", "rpcEndpoints": [ { "type": "custom", "url": "https://goerli.com", "networkClientId": "goerli" } - ] + ], + "blockExplorerUrls": [] } }, "internalAccounts": { diff --git a/ui/components/app/currency-input/currency-input.js b/ui/components/app/currency-input/currency-input.js index 43da00ad3ab0..ea538657c3e2 100644 --- a/ui/components/app/currency-input/currency-input.js +++ b/ui/components/app/currency-input/currency-input.js @@ -5,10 +5,8 @@ import { Box } from '../../component-library'; import { BlockSize } from '../../../helpers/constants/design-system'; import UnitInput from '../../ui/unit-input'; import CurrencyDisplay from '../../ui/currency-display'; -import { - getNativeCurrency, - getProviderConfig, -} from '../../../ducks/metamask/metamask'; +import { getNativeCurrency } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; import { getCurrentChainId, getCurrentCurrency, diff --git a/ui/components/app/loading-network-screen/loading-network-screen.container.js b/ui/components/app/loading-network-screen/loading-network-screen.container.js index 34a626bc1b5e..4321bdfe3bd3 100644 --- a/ui/components/app/loading-network-screen/loading-network-screen.container.js +++ b/ui/components/app/loading-network-screen/loading-network-screen.container.js @@ -6,7 +6,7 @@ import { getNetworkIdentifier, isNetworkLoading, } from '../../../selectors'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; import LoadingNetworkScreen from './loading-network-screen.component'; const DEPRECATED_TEST_NET_CHAINIDS = ['0x3', '0x2a', '0x4']; diff --git a/ui/components/app/modals/transaction-already-confirmed/transaction-already-confirmed.tsx b/ui/components/app/modals/transaction-already-confirmed/transaction-already-confirmed.tsx index 1bd2afc75926..031ffcf175c8 100644 --- a/ui/components/app/modals/transaction-already-confirmed/transaction-already-confirmed.tsx +++ b/ui/components/app/modals/transaction-already-confirmed/transaction-already-confirmed.tsx @@ -2,7 +2,6 @@ import React, { useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { getBlockExplorerLink } from '@metamask/etherscan-link'; import { type TransactionMeta } from '@metamask/transaction-controller'; -import { type NetworkClientConfiguration } from '@metamask/network-controller'; import { getRpcPrefsForCurrentProvider, getTransaction, @@ -38,9 +37,7 @@ export default function TransactionAlreadyConfirmed() { // eslint-disable-next-line @typescript-eslint/no-explicit-any (getTransaction as any)(state, originalTransactionId), ); - const rpcPrefs: NetworkClientConfiguration = useSelector( - getRpcPrefsForCurrentProvider, - ); + const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider); const viewTransaction = () => { // TODO: Fix getBlockExplorerLink arguments compatible with the actual controller types @@ -48,9 +45,7 @@ export default function TransactionAlreadyConfirmed() { // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any transaction as any, - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - rpcPrefs as any, + rpcPrefs, ); global.platform.openTab({ url: blockExplorerLink, diff --git a/ui/components/app/selected-account/selected-account.container.js b/ui/components/app/selected-account/selected-account.container.js index a6e0c03a347a..fc4027d967f7 100644 --- a/ui/components/app/selected-account/selected-account.container.js +++ b/ui/components/app/selected-account/selected-account.container.js @@ -10,7 +10,7 @@ import { getCustodyAccountDetails, getIsCustodianSupportedChain, } from '../../../selectors/institutional/selectors'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; ///: END:ONLY_INCLUDE_IF import SelectedAccount from './selected-account.component'; diff --git a/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx b/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx index 217ce2e575c0..cb38e6a8f0c1 100644 --- a/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx +++ b/ui/components/institutional/wrong-network-notification/wrong-network-notification.tsx @@ -11,16 +11,13 @@ import { import { getSelectedAccountCachedBalance } from '../../../selectors'; import { getIsCustodianSupportedChain } from '../../../selectors/institutional/selectors'; import { useI18nContext } from '../../../hooks/useI18nContext'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; import { Icon, IconName, IconSize, Box, Text } from '../../component-library'; const WrongNetworkNotification: React.FC = () => { const t = useI18nContext(); - const providerConfig = useSelector< - object, - { nickname?: string; type: string } | undefined - >(getProviderConfig); - const balance = useSelector(getSelectedAccountCachedBalance); + const providerConfig = useSelector(getProviderConfig); + const balance = useSelector(getSelectedAccountCachedBalance); const isCustodianSupportedChain = useSelector(getIsCustodianSupportedChain); diff --git a/ui/components/multichain/address-copy-button/address-copy-button.js b/ui/components/multichain/address-copy-button/address-copy-button.js index fa8b0803ee0b..b20ec632b506 100644 --- a/ui/components/multichain/address-copy-button/address-copy-button.js +++ b/ui/components/multichain/address-copy-button/address-copy-button.js @@ -8,7 +8,7 @@ import { getIsCustodianSupportedChain, getCustodianIconForAddress, } from '../../../selectors/institutional/selectors'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; ///: END:ONLY_INCLUDE_IF import { ButtonBase, IconName, Box } from '../../component-library'; import { diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx index d674fbef528e..cef92bc8184a 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal-network.tsx @@ -19,8 +19,10 @@ import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../../shared/constan import { useI18nContext } from '../../../../hooks/useI18nContext'; ///: END:ONLY_INCLUDE_IF import { NetworkListItem } from '../../network-list-item'; -import { getNetworkConfigurationsByChainId } from '../../../../selectors'; -import { getProviderConfig } from '../../../../ducks/metamask/metamask'; +import { + getNetworkConfigurationsByChainId, + getProviderConfig, +} from '../../../../selectors'; /** * AssetPickerModalNetwork component displays a modal for selecting a network in the asset picker. diff --git a/ui/components/ui/new-network-info/new-network-info.js b/ui/components/ui/new-network-info/new-network-info.js index 51b96bdc5b36..149e34ebb325 100644 --- a/ui/components/ui/new-network-info/new-network-info.js +++ b/ui/components/ui/new-network-info/new-network-info.js @@ -3,7 +3,7 @@ import { useSelector } from 'react-redux'; import { TOKEN_API_METASWAP_CODEFI_URL } from '../../../../shared/constants/tokens'; import fetchWithCache from '../../../../shared/lib/fetch-with-cache'; import { I18nContext } from '../../../contexts/i18n'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; import { AlignItems, BackgroundColor, diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 568d62e7a2d4..bde5a4a1137f 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -1,10 +1,6 @@ -import { - NetworkConfiguration, - NetworkState, -} from '@metamask/network-controller'; +import { NetworkConfiguration } from '@metamask/network-controller'; import { uniqBy } from 'lodash'; import { - getNetworkConfigurationsByChainId, getIsBridgeEnabled, getSwapsDefaultToken, SwapsEthToken, @@ -17,12 +13,17 @@ import { // eslint-disable-next-line import/no-restricted-paths } from '../../../app/scripts/controllers/bridge/types'; import { createDeepEqualSelector } from '../../selectors/util'; -import { getProviderConfig } from '../metamask/metamask'; +import { + NetworkState, + getProviderConfig, + getNetworkConfigurationsByChainId, +} from '../../selectors/networks'; import { SwapsTokenObject } from '../../../shared/constants/swaps'; import { BridgeState } from './bridge'; -type BridgeAppState = { - metamask: NetworkState & { bridgeState: BridgeControllerState } & { +// TODO add swaps state +type BridgeAppState = NetworkState & { + metamask: { bridgeState: BridgeControllerState } & { useExternalServices: boolean; }; bridge: BridgeState; diff --git a/ui/ducks/metamask/metamask.js b/ui/ducks/metamask/metamask.js index 9627608eb709..6196fa88dd57 100644 --- a/ui/ducks/metamask/metamask.js +++ b/ui/ducks/metamask/metamask.js @@ -1,7 +1,6 @@ import { addHexPrefix, isHexString } from 'ethereumjs-util'; import { createSelector } from 'reselect'; import { mergeGasFeeEstimates } from '@metamask/transaction-controller'; -import { RpcEndpointType } from '@metamask/network-controller'; import { AlertTypes } from '../../../shared/constants/alerts'; import { GasEstimateTypes, @@ -16,9 +15,11 @@ import { accountsWithSendEtherInfoSelector, checkNetworkAndAccountSupports1559, getAddressBook, - getSelectedNetworkClientId, - getNetworkConfigurationsByChainId, } from '../../selectors/selectors'; +import { + getProviderConfig, + getSelectedNetworkClientId, +} from '../../selectors/networks'; import { getSelectedInternalAccount } from '../../selectors/accounts'; import * as actionConstants from '../../store/actionConstants'; import { updateTransactionGasFees } from '../../store/actions'; @@ -274,42 +275,6 @@ export function updateGasFees({ export const getAlertEnabledness = (state) => state.metamask.alertEnabledness; -/** - * Get the provider configuration for the current selected network. - * - * @param {object} state - Redux state object. - */ -export const getProviderConfig = createSelector( - (state) => getNetworkConfigurationsByChainId(state), - (state) => getSelectedNetworkClientId(state), - (networkConfigurationsByChainId, selectedNetworkClientId) => { - for (const network of Object.values(networkConfigurationsByChainId)) { - for (const rpcEndpoint of network.rpcEndpoints) { - if (rpcEndpoint.networkClientId === selectedNetworkClientId) { - const blockExplorerUrl = - network.blockExplorerUrls?.[network.defaultBlockExplorerUrlIndex]; - - return { - chainId: network.chainId, - ticker: network.nativeCurrency, - rpcPrefs: { ...(blockExplorerUrl && { blockExplorerUrl }) }, - type: - rpcEndpoint.type === RpcEndpointType.Custom - ? 'rpc' - : rpcEndpoint.networkClientId, - ...(rpcEndpoint.type === RpcEndpointType.Custom && { - id: rpcEndpoint.networkClientId, - nickname: network.name, - rpcUrl: rpcEndpoint.url, - }), - }; - } - } - } - return undefined; // should not be reachable - }, -); - export const getUnconnectedAccountAlertEnabledness = (state) => getAlertEnabledness(state)[AlertTypes.unconnectedAccount]; diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 512b621c38f4..2441830a464b 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -101,9 +101,9 @@ import { import { getGasEstimateType, getNativeCurrency, - getProviderConfig, getTokens, } from '../metamask/metamask'; +import { getProviderConfig } from '../../selectors/networks'; import { resetDomainResolution } from '../domains'; import { diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts index fe7a21e2206f..8d080b626b5f 100644 --- a/ui/hooks/bridge/useBridging.ts +++ b/ui/hooks/bridge/useBridging.ts @@ -13,6 +13,9 @@ import { SwapsEthToken, ///: END:ONLY_INCLUDE_IF } from '../../selectors'; +///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) +import { getProviderConfig } from '../../selectors/networks'; +///: END:ONLY_INCLUDE_IF import { MetaMetricsContext } from '../../contexts/metametrics'; import { MetaMetricsEventCategory, @@ -30,7 +33,6 @@ import { isHardwareKeyring } from '../../helpers/utils/hardware'; import { getPortfolioUrl } from '../../helpers/utils/portfolio'; import { setSwapsFromToken } from '../../ducks/swaps/swaps'; import { SwapsTokenObject } from '../../../shared/constants/swaps'; -import { getProviderConfig } from '../../ducks/metamask/metamask'; ///: END:ONLY_INCLUDE_IF const useBridging = () => { diff --git a/ui/hooks/ramps/useRamps/useRamps.ts b/ui/hooks/ramps/useRamps/useRamps.ts index 429abbf1a96b..dd29b037cfd0 100644 --- a/ui/hooks/ramps/useRamps/useRamps.ts +++ b/ui/hooks/ramps/useRamps/useRamps.ts @@ -1,7 +1,6 @@ import { useCallback } from 'react'; import { useSelector } from 'react-redux'; -import { CaipChainId } from '@metamask/utils'; -import { ChainId } from '../../../../shared/constants/network'; +import type { Hex, CaipChainId } from '@metamask/utils'; import { getCurrentChainId, getDataCollectionForMarketing, @@ -10,8 +9,8 @@ import { } from '../../../selectors'; type IUseRamps = { - openBuyCryptoInPdapp: (chainId?: ChainId | CaipChainId) => void; - getBuyURI: (chainId: ChainId | CaipChainId) => string; + openBuyCryptoInPdapp: (chainId?: Hex | CaipChainId) => void; + getBuyURI: (chainId: Hex | CaipChainId) => string; }; export enum RampsMetaMaskEntry { @@ -32,7 +31,7 @@ const useRamps = ( const isMarketingEnabled = useSelector(getDataCollectionForMarketing); const getBuyURI = useCallback( - (_chainId: ChainId | CaipChainId) => { + (_chainId: Hex | CaipChainId) => { const params = new URLSearchParams(); params.set('metamaskEntry', metamaskEntry); params.set('chainId', _chainId); @@ -50,7 +49,7 @@ const useRamps = ( ); const openBuyCryptoInPdapp = useCallback( - (_chainId?: ChainId | CaipChainId) => { + (_chainId?: Hex | CaipChainId) => { const buyUrl = getBuyURI(_chainId || chainId); global.platform.openTab({ url: buyUrl, diff --git a/ui/hooks/useMMICustodySendTransaction.ts b/ui/hooks/useMMICustodySendTransaction.ts index 0c05d9e16f96..a25a1a415bc6 100644 --- a/ui/hooks/useMMICustodySendTransaction.ts +++ b/ui/hooks/useMMICustodySendTransaction.ts @@ -7,6 +7,7 @@ import { AccountType, CustodyStatus } from '../../shared/constants/custody'; import { getMostRecentOverviewPage } from '../ducks/history/history'; import { clearConfirmTransaction } from '../ducks/confirm-transaction/confirm-transaction.duck'; import { getAccountType } from '../selectors/selectors'; +import { getProviderConfig } from '../selectors/networks'; import { mmiActionsFactory } from '../store/institutional/institution-background'; import { showCustodyConfirmLink } from '../store/institutional/institution-actions'; import { @@ -19,7 +20,6 @@ import { getConfirmationSender } from '../pages/confirmations/components/confirm import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; import { getSmartTransactionsEnabled } from '../../shared/modules/selectors'; import { CHAIN_ID_TO_RPC_URL_MAP } from '../../shared/constants/network'; -import { getProviderConfig } from '../ducks/metamask/metamask'; type MMITransactionMeta = TransactionMeta & { txParams: { from: string }; diff --git a/ui/hooks/useTokenTracker.js b/ui/hooks/useTokenTracker.js index 0ce2c9cbcac2..effffa61cb67 100644 --- a/ui/hooks/useTokenTracker.js +++ b/ui/hooks/useTokenTracker.js @@ -1,10 +1,9 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import TokenTracker from '@metamask/eth-token-tracker'; import { shallowEqual, useSelector } from 'react-redux'; -import { getSelectedInternalAccount } from '../selectors'; +import { getSelectedInternalAccount, getProviderConfig } from '../selectors'; import { SECOND } from '../../shared/constants/time'; import { isEqualCaseInsensitive } from '../../shared/modules/string-utils'; -import { getProviderConfig } from '../ducks/metamask/metamask'; import { useEqualityCheck } from './useEqualityCheck'; export function useTokenTracker({ diff --git a/ui/pages/asset/components/asset-page.tsx b/ui/pages/asset/components/asset-page.tsx index c70b60169edb..846ce1cc3804 100644 --- a/ui/pages/asset/components/asset-page.tsx +++ b/ui/pages/asset/components/asset-page.tsx @@ -4,6 +4,7 @@ import { useHistory } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { EthMethod } from '@metamask/keyring-api'; import { isEqual } from 'lodash'; +import { Hex } from '@metamask/utils'; import { getCurrentCurrency, getIsBridgeChain, @@ -62,8 +63,8 @@ export type Asset = ( aggregators?: []; } ) & { - /** The hexadecimal chain id */ - chainId: `0x${string}`; + /** The hexadecimal or CAIP chain id */ + chainId: Hex; /** The asset's symbol, e.g. 'ETH' */ symbol: string; /** The asset's name, e.g. 'Ethereum' */ diff --git a/ui/pages/asset/components/chart/asset-chart.tsx b/ui/pages/asset/components/chart/asset-chart.tsx index efa7adad03ad..b49eaf7ced90 100644 --- a/ui/pages/asset/components/chart/asset-chart.tsx +++ b/ui/pages/asset/components/chart/asset-chart.tsx @@ -14,6 +14,7 @@ import { import { Line } from 'react-chartjs-2'; import classnames from 'classnames'; import { brandColor } from '@metamask/design-tokens'; +import { Hex } from '@metamask/utils'; import { useTheme } from '../../../../hooks/useTheme'; import { BackgroundColor, @@ -80,7 +81,7 @@ const AssetChart = ({ currentPrice, currency, }: { - chainId: `0x${string}`; + chainId: Hex; address: string; currentPrice?: number; currency: string; diff --git a/ui/pages/asset/components/native-asset.tsx b/ui/pages/asset/components/native-asset.tsx index cdc38afcd240..1743ac9646a3 100644 --- a/ui/pages/asset/components/native-asset.tsx +++ b/ui/pages/asset/components/native-asset.tsx @@ -11,10 +11,8 @@ import { getShouldShowFiat, } from '../../../selectors'; import { useCurrencyDisplay } from '../../../hooks/useCurrencyDisplay'; -import { - getNativeCurrency, - getProviderConfig, -} from '../../../ducks/metamask/metamask'; +import { getNativeCurrency } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; import { AssetType } from '../../../../shared/constants/transaction'; import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol'; import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics'; diff --git a/ui/pages/asset/useHistoricalPrices.ts b/ui/pages/asset/useHistoricalPrices.ts index e4b28add0bc7..e2ef9771b40d 100644 --- a/ui/pages/asset/useHistoricalPrices.ts +++ b/ui/pages/asset/useHistoricalPrices.ts @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; // @ts-expect-error suppress CommonJS vs ECMAScript error import { Point } from 'chart.js'; import { useSelector } from 'react-redux'; +import { Hex } from '@metamask/utils'; import fetchWithCache from '../../../shared/lib/fetch-with-cache'; import { MINUTE } from '../../../shared/constants/time'; import { getShouldShowFiat } from '../../selectors'; @@ -20,7 +21,7 @@ export const useHistoricalPrices = ({ currency, timeRange, }: { - chainId: `0x${string}`; + chainId: Hex; address: string; currency: string; timeRange: TimeRange; diff --git a/ui/pages/asset/util.ts b/ui/pages/asset/util.ts index 824040fa6560..9823f2d38baf 100644 --- a/ui/pages/asset/util.ts +++ b/ui/pages/asset/util.ts @@ -1,4 +1,5 @@ import { SUPPORTED_CHAIN_IDS } from '@metamask/assets-controllers'; +import { Hex } from '@metamask/utils'; /** Formats a datetime in a short human readable format like 'Feb 8, 12:11 PM' */ export const getShortDateFormatter = () => @@ -59,7 +60,7 @@ export const getPricePrecision = (price: number) => { * * @param chainId - The hexadecimal chain id. */ -export const chainSupportsPricing = (chainId: `0x${string}`) => +export const chainSupportsPricing = (chainId: Hex) => (SUPPORTED_CHAIN_IDS as readonly string[]).includes(chainId); /** The opacity components should set during transition */ diff --git a/ui/pages/bridge/index.tsx b/ui/pages/bridge/index.tsx index e81b20670011..ee8967bc12ac 100644 --- a/ui/pages/bridge/index.tsx +++ b/ui/pages/bridge/index.tsx @@ -16,14 +16,17 @@ import { ButtonIconSize, IconName, } from '../../components/component-library'; -import { getIsBridgeChain, getIsBridgeEnabled } from '../../selectors'; +import { + getIsBridgeChain, + getIsBridgeEnabled, + getProviderConfig, +} from '../../selectors'; import useBridging from '../../hooks/bridge/useBridging'; import { Content, Footer, Header, } from '../../components/multichain/pages/page'; -import { getProviderConfig } from '../../ducks/metamask/metamask'; import { resetInputFields, setFromChain } from '../../ducks/bridge/actions'; import PrepareBridgePage from './prepare/prepare-bridge-page'; import { BridgeCTAButton } from './prepare/bridge-cta-button'; diff --git a/ui/pages/confirmations/components/confirm-subtitle/confirm-subtitle.test.js b/ui/pages/confirmations/components/confirm-subtitle/confirm-subtitle.test.js index baa2d112b671..0dc786cfa09c 100644 --- a/ui/pages/confirmations/components/confirm-subtitle/confirm-subtitle.test.js +++ b/ui/pages/confirmations/components/confirm-subtitle/confirm-subtitle.test.js @@ -5,7 +5,7 @@ import mockState from '../../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import configureStore from '../../../../store/store'; import { getSelectedInternalAccountFromMockState } from '../../../../../test/jest/mocks'; -import { getProviderConfig } from '../../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../../selectors/networks'; import ConfirmSubTitle from './confirm-subtitle'; const mockSelectedInternalAccount = diff --git a/ui/pages/confirmations/components/signature-request-header/signature-request-header.js b/ui/pages/confirmations/components/signature-request-header/signature-request-header.js index 61cbfe13e290..5dfc26067326 100644 --- a/ui/pages/confirmations/components/signature-request-header/signature-request-header.js +++ b/ui/pages/confirmations/components/signature-request-header/signature-request-header.js @@ -4,7 +4,6 @@ import { useSelector } from 'react-redux'; import { RpcEndpointType } from '@metamask/network-controller'; import { NetworkType } from '@metamask/controller-utils'; import { useI18nContext } from '../../../../hooks/useI18nContext'; - import { accountsWithSendEtherInfoSelector, getCurrentCurrency, diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js index e06090f48e75..1b2cececc650 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js @@ -54,7 +54,6 @@ import { getUnapprovedTransactions, getInternalAccountByAddress, getApprovedAndSignedTransactions, - getSelectedNetworkClientId, getPrioritizedUnapprovedTemplatedConfirmations, } from '../../../selectors'; import { @@ -71,10 +70,13 @@ import { getIsGasEstimatesLoading, getNativeCurrency, getSendToAccounts, - getProviderConfig, findKeyringForAddress, getConversionRate, } from '../../../ducks/metamask/metamask'; +import { + getProviderConfig, + getSelectedNetworkClientId, +} from '../../../selectors/networks'; import { addHexPrefix, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) diff --git a/ui/pages/confirmations/hooks/useTransactionInfo.js b/ui/pages/confirmations/hooks/useTransactionInfo.js index 452e44c83dfb..2f1b989f0ca0 100644 --- a/ui/pages/confirmations/hooks/useTransactionInfo.js +++ b/ui/pages/confirmations/hooks/useTransactionInfo.js @@ -1,5 +1,5 @@ import { useSelector } from 'react-redux'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils'; import { getSelectedInternalAccount } from '../../../selectors'; diff --git a/ui/pages/confirmations/send/gas-display/gas-display.js b/ui/pages/confirmations/send/gas-display/gas-display.js index 33a011c2966a..0961c048542a 100644 --- a/ui/pages/confirmations/send/gas-display/gas-display.js +++ b/ui/pages/confirmations/send/gas-display/gas-display.js @@ -31,10 +31,8 @@ import { import { INSUFFICIENT_TOKENS_ERROR } from '../send.constants'; import { getCurrentDraftTransaction } from '../../../../ducks/send'; -import { - getNativeCurrency, - getProviderConfig, -} from '../../../../ducks/metamask/metamask'; +import { getNativeCurrency } from '../../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../../selectors/networks'; import { showModal } from '../../../../store/actions'; import { addHexes, diff --git a/ui/pages/routes/routes.container.js b/ui/pages/routes/routes.container.js index 7553e59963d2..dd90f2468dd9 100644 --- a/ui/pages/routes/routes.container.js +++ b/ui/pages/routes/routes.container.js @@ -44,10 +44,8 @@ import { import { pageChanged } from '../../ducks/history/history'; import { prepareToLeaveSwaps } from '../../ducks/swaps/swaps'; import { getSendStage } from '../../ducks/send'; -import { - getIsUnlocked, - getProviderConfig, -} from '../../ducks/metamask/metamask'; +import { getIsUnlocked } from '../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../selectors/networks'; import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../shared/constants/preferences'; import { selectSwitchedNetworkNeverShowMessage } from '../../components/app/toast-master/selectors'; import Routes from './routes.component'; diff --git a/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js b/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js index af248b04d330..3c7b541ad40a 100644 --- a/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js +++ b/ui/pages/settings/contact-list-tab/edit-contact/edit-contact.container.js @@ -5,7 +5,7 @@ import { getAddressBookEntry, getInternalAccountByAddress, } from '../../../../selectors'; -import { getProviderConfig } from '../../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../../selectors/networks'; import { CONTACT_VIEW_ROUTE, CONTACT_LIST_ROUTE, diff --git a/ui/pages/settings/security-tab/security-tab.container.js b/ui/pages/settings/security-tab/security-tab.container.js index 676a53097d4a..952d236eab1e 100644 --- a/ui/pages/settings/security-tab/security-tab.container.js +++ b/ui/pages/settings/security-tab/security-tab.container.js @@ -24,10 +24,10 @@ import { } from '../../../store/actions'; import { getIsSecurityAlertsEnabled, - getNetworkConfigurationsByChainId, getMetaMetricsDataDeletionId, getPetnamesEnabled, } from '../../../selectors/selectors'; +import { getNetworkConfigurationsByChainId } from '../../../selectors/networks'; import { openBasicFunctionalityModal } from '../../../ducks/app/app'; import SecurityTab from './security-tab.component'; diff --git a/ui/pages/settings/settings-tab/settings-tab.container.js b/ui/pages/settings/settings-tab/settings-tab.container.js index e6ad25f0df92..07b254159551 100644 --- a/ui/pages/settings/settings-tab/settings-tab.container.js +++ b/ui/pages/settings/settings-tab/settings-tab.container.js @@ -14,7 +14,7 @@ import { getTheme, getSelectedInternalAccount, } from '../../../selectors'; -import { getProviderConfig } from '../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../selectors/networks'; import SettingsTab from './settings-tab.component'; const mapStateToProps = (state) => { diff --git a/ui/pages/settings/settings.container.js b/ui/pages/settings/settings.container.js index 58a35f37e616..6c3109291c59 100644 --- a/ui/pages/settings/settings.container.js +++ b/ui/pages/settings/settings.container.js @@ -32,8 +32,8 @@ import { ADD_NETWORK_ROUTE, ADD_POPULAR_CUSTOM_NETWORK, } from '../../helpers/constants/routes'; -import { getProviderConfig } from '../../ducks/metamask/metamask'; import { toggleNetworkMenu } from '../../store/actions'; +import { getProviderConfig } from '../../selectors/networks'; import Settings from './settings.component'; const ROUTES_TO_I18N_KEYS = { diff --git a/ui/pages/swaps/hooks/useUpdateSwapsState.ts b/ui/pages/swaps/hooks/useUpdateSwapsState.ts index ea72c8e273ab..dc44c51a8489 100644 --- a/ui/pages/swaps/hooks/useUpdateSwapsState.ts +++ b/ui/pages/swaps/hooks/useUpdateSwapsState.ts @@ -18,6 +18,7 @@ import { getIsSwapsChain, getUseExternalServices, } from '../../../selectors'; +import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP } from '../../../../shared/constants/swaps'; export default function useUpdateSwapsState() { const dispatch = useDispatch(); @@ -35,7 +36,7 @@ export default function useUpdateSwapsState() { return undefined; } - fetchTokens(chainId) + fetchTokens(chainId as keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP) .then((tokens) => { dispatch(setSwapsTokens(tokens)); }) diff --git a/ui/selectors/confirm-transaction.js b/ui/selectors/confirm-transaction.js index 9a1457e2bc66..80e6d42aecc7 100644 --- a/ui/selectors/confirm-transaction.js +++ b/ui/selectors/confirm-transaction.js @@ -11,7 +11,6 @@ import { getGasEstimateType, getGasFeeEstimates, getNativeCurrency, - getProviderConfig, } from '../ducks/metamask/metamask'; import { GasEstimateTypes, @@ -29,6 +28,7 @@ import { subtractHexes, sumHexes, } from '../../shared/modules/conversion.utils'; +import { getProviderConfig } from './networks'; import { getAveragePriceEstimateInHexWEI } from './custom-gas'; import { checkNetworkAndAccountSupports1559, diff --git a/ui/selectors/index.js b/ui/selectors/index.js index 290c70fb2a31..a37d00d76612 100644 --- a/ui/selectors/index.js +++ b/ui/selectors/index.js @@ -8,3 +8,4 @@ export * from './selectors'; export * from './transactions'; export * from './approvals'; export * from './accounts'; +export * from './networks'; diff --git a/ui/selectors/institutional/selectors.test.ts b/ui/selectors/institutional/selectors.test.ts index 52b27bb8871f..4c398b6fab0b 100644 --- a/ui/selectors/institutional/selectors.test.ts +++ b/ui/selectors/institutional/selectors.test.ts @@ -4,7 +4,11 @@ import { Hex } from '@metamask/utils'; import { toHex } from '@metamask/controller-utils'; import { ETH_EOA_METHODS } from '../../../shared/constants/eth-methods'; import { mockNetworkState } from '../../../test/stub/networks'; -import { CHAIN_IDS } from '../../../shared/constants/network'; +import { + CHAIN_IDS, + CURRENCY_SYMBOLS, + NETWORK_TO_NAME_MAP, +} from '../../../shared/constants/network'; import { getConfiguredCustodians, getCustodianIconForAddress, @@ -71,10 +75,15 @@ const custodianMock = { function buildState(overrides = {}) { const defaultState = { metamask: { + selectedNetworkClientId: '0x1', networkConfigurationsByChainId: { - [toHex(1)]: { - chainId: toHex(1), - rpcEndpoints: [{}], + [CHAIN_IDS.MAINNET]: { + chainId: CHAIN_IDS.MAINNET, + blockExplorerUrls: [], + defaultRpcEndpointIndex: 0, + name: NETWORK_TO_NAME_MAP[CHAIN_IDS.MAINNET], + nativeCurrency: CURRENCY_SYMBOLS.ETH, + rpcEndpoints: [], }, }, internalAccounts: { diff --git a/ui/selectors/institutional/selectors.ts b/ui/selectors/institutional/selectors.ts index 05bd13b52509..8066f528ee64 100644 --- a/ui/selectors/institutional/selectors.ts +++ b/ui/selectors/institutional/selectors.ts @@ -1,7 +1,7 @@ import { toChecksumAddress } from 'ethereumjs-util'; import { getAccountType } from '../selectors'; import { getSelectedInternalAccount } from '../accounts'; -import { getProviderConfig } from '../../ducks/metamask/metamask'; +import { ProviderConfigState, getProviderConfig } from '../networks'; import { hexToDecimal } from '../../../shared/modules/conversion.utils'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths @@ -165,11 +165,14 @@ export function getCustodianIconForAddress(state: State, address: string) { return custodianIcon; } -export function getIsCustodianSupportedChain(state: State) { +export function getIsCustodianSupportedChain( + state: State & ProviderConfigState, +) { try { // @ts-expect-error state types don't match const selectedAccount = getSelectedInternalAccount(state); const accountType = getAccountType(state); + const providerConfig = getProviderConfig(state); if (!selectedAccount || !accountType || !providerConfig) { diff --git a/ui/selectors/multichain.test.ts b/ui/selectors/multichain.test.ts index 3097d61f9549..bc8fcbf7ce6c 100644 --- a/ui/selectors/multichain.test.ts +++ b/ui/selectors/multichain.test.ts @@ -2,10 +2,7 @@ import { Cryptocurrency } from '@metamask/assets-controllers'; import { InternalAccount } from '@metamask/keyring-api'; import { Hex } from '@metamask/utils'; import { NetworkConfiguration } from '@metamask/network-controller'; -import { - getNativeCurrency, - getProviderConfig, -} from '../ducks/metamask/metamask'; +import { getNativeCurrency } from '../ducks/metamask/metamask'; import { MULTICHAIN_PROVIDER_CONFIGS, MultichainNetworks, @@ -24,6 +21,7 @@ import { } from '../../shared/constants/network'; import { MultichainNativeAssets } from '../../shared/constants/multichain/assets'; import { mockNetworkState } from '../../test/stub/networks'; +import { getProviderConfig } from './networks'; import { AccountsState } from './accounts'; import { MultichainState, diff --git a/ui/selectors/multichain.ts b/ui/selectors/multichain.ts index 96266687b6df..ffc608c547ef 100644 --- a/ui/selectors/multichain.ts +++ b/ui/selectors/multichain.ts @@ -14,7 +14,6 @@ import { getCompletedOnboarding, getConversionRate, getNativeCurrency, - getProviderConfig, } from '../ducks/metamask/metamask'; // TODO: Remove restricted import // eslint-disable-next-line import/no-restricted-paths @@ -24,6 +23,11 @@ import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, TEST_NETWORK_IDS, } from '../../shared/constants/network'; +import { + getProviderConfig, + NetworkState, + getNetworkConfigurationsByChainId, +} from './networks'; import { AccountsState, getSelectedInternalAccount } from './accounts'; import { getCurrentChainId, @@ -31,7 +35,6 @@ import { getIsMainnet, getMaybeSelectedInternalAccount, getNativeCurrencyImage, - getNetworkConfigurationsByChainId, getSelectedAccountCachedBalance, getShouldShowFiat, getShowFiatInTestnets, @@ -45,7 +48,10 @@ export type BalancesState = { metamask: BalancesControllerState; }; -export type MultichainState = AccountsState & RatesState & BalancesState; +export type MultichainState = AccountsState & + RatesState & + BalancesState & + NetworkState; // TODO: Remove after updating to @metamask/network-controller 20.0.0 export type ProviderConfigWithImageUrlAndExplorerUrl = { diff --git a/ui/selectors/networks.test.ts b/ui/selectors/networks.test.ts new file mode 100644 index 000000000000..84bf0efb1c84 --- /dev/null +++ b/ui/selectors/networks.test.ts @@ -0,0 +1,129 @@ +import { NetworkStatus, RpcEndpointType } from '@metamask/network-controller'; +import mockState from '../../test/data/mock-state.json'; +import { mockNetworkState } from '../../test/stub/networks'; +import { CHAIN_IDS } from '../../shared/constants/network'; +import * as networks from './networks'; + +describe('Network Selectors', () => { + describe('#getNetworkConfigurationsByChainId', () => { + it('returns networkConfigurationsByChainId', () => { + const networkConfigurationsByChainId = { + '0x1351': { + name: 'TEST', + chainId: '0x1351' as const, + nativeCurrency: 'TEST', + defaultRpcEndpointUrl: 'https://mock-rpc-url-1', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: RpcEndpointType.Custom as const, + networkClientId: 'testNetworkConfigurationId1', + url: 'https://mock-rpc-url-1', + }, + ], + blockExplorerUrls: [], + }, + '0x1337': { + name: 'RPC', + chainId: '0x1337' as const, + nativeCurrency: 'RPC', + defaultRpcEndpointUrl: 'https://mock-rpc-url-2', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: RpcEndpointType.Custom as const, + networkClientId: 'testNetworkConfigurationId2', + url: 'https://mock-rpc-url-2', + }, + ], + blockExplorerUrls: [], + }, + }; + + expect( + networks.getNetworkConfigurationsByChainId({ + metamask: { + networkConfigurationsByChainId, + }, + }), + ).toStrictEqual(networkConfigurationsByChainId); + }); + }); + + describe('#getNetworkConfigurations', () => { + it('returns undefined if state.metamask.networkConfigurations is undefined', () => { + expect( + networks.getNetworkConfigurations({ + metamask: { + // @ts-expect-error the types forbid `undefined`. this is a strange test. + networkConfigurations: undefined, + }, + }), + ).toBeUndefined(); + }); + + it('returns networkConfigurations', () => { + const networkConfigurations = { + '0x1351': { + name: 'TEST', + chainId: '0x1351' as const, + nativeCurrency: 'TEST', + defaultRpcEndpointUrl: 'https://mock-rpc-url-1', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: RpcEndpointType.Custom as const, + networkClientId: 'testNetworkConfigurationId1', + url: 'https://mock-rpc-url-1', + }, + ], + blockExplorerUrls: [], + }, + '0x1337': { + name: 'RPC', + chainId: '0x1337' as const, + nativeCurrency: 'RPC', + defaultRpcEndpointUrl: 'https://mock-rpc-url-2', + defaultRpcEndpointIndex: 0, + rpcEndpoints: [ + { + type: RpcEndpointType.Custom as const, + networkClientId: 'testNetworkConfigurationId2', + url: 'https://mock-rpc-url-2', + }, + ], + blockExplorerUrls: [], + }, + }; + expect( + networks.getNetworkConfigurations({ + metamask: { + networkConfigurations, + }, + }), + ).toStrictEqual(networkConfigurations); + }); + }); + + describe('#getInfuraBlocked', () => { + it('returns getInfuraBlocked', () => { + let isInfuraBlocked = networks.getInfuraBlocked( + mockState as networks.NetworkState, + ); + expect(isInfuraBlocked).toBe(false); + + const modifiedMockState = { + ...mockState, + metamask: { + ...mockState.metamask, + ...mockNetworkState({ + chainId: CHAIN_IDS.GOERLI, + metadata: { status: NetworkStatus.Blocked, EIPS: {} }, + }), + }, + }; + isInfuraBlocked = networks.getInfuraBlocked(modifiedMockState); + expect(isInfuraBlocked).toBe(true); + }); + }); +}); diff --git a/ui/selectors/networks.ts b/ui/selectors/networks.ts new file mode 100644 index 000000000000..998d8469a060 --- /dev/null +++ b/ui/selectors/networks.ts @@ -0,0 +1,114 @@ +import { + RpcEndpointType, + type NetworkConfiguration, + type NetworkState as _NetworkState, +} from '@metamask/network-controller'; +import { createSelector } from 'reselect'; +import { NetworkStatus } from '../../shared/constants/network'; +import { createDeepEqualSelector } from './util'; + +export type NetworkState = { metamask: _NetworkState }; + +export type NetworkConfigurationsState = { + metamask: { + networkConfigurations: Record< + string, + MetaMaskExtensionNetworkConfiguration + >; + }; +}; + +export type SelectedNetworkClientIdState = { + metamask: { + selectedNetworkClientId: string; + }; +}; + +export type MetaMaskExtensionNetworkConfiguration = NetworkConfiguration; + +export type NetworkConfigurationsByChainIdState = { + metamask: Pick<_NetworkState, 'networkConfigurationsByChainId'>; +}; + +export type ProviderConfigState = NetworkConfigurationsByChainIdState & + SelectedNetworkClientIdState; + +export const getNetworkConfigurationsByChainId = createDeepEqualSelector( + (state: NetworkConfigurationsByChainIdState) => + state.metamask.networkConfigurationsByChainId, + (networkConfigurationsByChainId) => networkConfigurationsByChainId, +); + +export function getSelectedNetworkClientId( + state: SelectedNetworkClientIdState, +) { + return state.metamask.selectedNetworkClientId; +} + +/** + * Get the provider configuration for the current selected network. + * + * @param state - Redux state object. + */ +export const getProviderConfig = createSelector( + (state: ProviderConfigState) => getNetworkConfigurationsByChainId(state), + getSelectedNetworkClientId, + (networkConfigurationsByChainId, selectedNetworkClientId) => { + for (const network of Object.values(networkConfigurationsByChainId)) { + for (const rpcEndpoint of network.rpcEndpoints) { + if (rpcEndpoint.networkClientId === selectedNetworkClientId) { + const blockExplorerUrl = + network.defaultBlockExplorerUrlIndex === undefined + ? undefined + : network.blockExplorerUrls?.[ + network.defaultBlockExplorerUrlIndex + ]; + + return { + chainId: network.chainId, + ticker: network.nativeCurrency, + rpcPrefs: { ...(blockExplorerUrl && { blockExplorerUrl }) }, + type: + rpcEndpoint.type === RpcEndpointType.Custom + ? 'rpc' + : rpcEndpoint.networkClientId, + ...(rpcEndpoint.type === RpcEndpointType.Custom && { + id: rpcEndpoint.networkClientId, + nickname: network.name, + rpcUrl: rpcEndpoint.url, + }), + }; + } + } + } + return undefined; // should not be reachable + }, +); + +export function getNetworkConfigurations( + state: NetworkConfigurationsState, +): Record { + return state.metamask.networkConfigurations; +} + +/** + * Returns true if the currently selected network is inaccessible or whether no + * provider has been set yet for the currently selected network. + * + * @param state - Redux state object. + */ +export function isNetworkLoading(state: NetworkState) { + const selectedNetworkClientId = getSelectedNetworkClientId(state); + return ( + selectedNetworkClientId && + state.metamask.networksMetadata[selectedNetworkClientId].status !== + NetworkStatus.Available + ); +} + +export function getInfuraBlocked(state: NetworkState) { + return ( + state.metamask.networksMetadata[getSelectedNetworkClientId(state)] + .status === NetworkStatus.Blocked + ); +} diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 70e970c4c9b3..57b59db89a81 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -24,7 +24,6 @@ import { CHAIN_ID_TO_RPC_URL_MAP, CHAIN_IDS, NETWORK_TYPES, - NetworkStatus, SEPOLIA_DISPLAY_NAME, GOERLI_DISPLAY_NAME, LINEA_GOERLI_DISPLAY_NAME, @@ -78,7 +77,6 @@ import { STATIC_MAINNET_TOKEN_LIST } from '../../shared/constants/tokens'; import { DAY } from '../../shared/constants/time'; import { TERMS_OF_USE_LAST_UPDATED } from '../../shared/constants/terms'; import { - getProviderConfig, getConversionRate, isNotEIP1559Network, isEIP1559Network, @@ -104,6 +102,10 @@ import { MultichainNativeAssets } from '../../shared/constants/multichain/assets import { BridgeFeatureFlagsKey } from '../../app/scripts/controllers/bridge/types'; import { hasTransactionData } from '../../shared/modules/transaction.utils'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; +import { + getProviderConfig, + getNetworkConfigurationsByChainId, +} from './networks'; import { getAllUnapprovedTransactions, getCurrentNetworkTransactions, @@ -121,25 +123,6 @@ import { getSelectedInternalAccount, getInternalAccounts } from './accounts'; import { createDeepEqualSelector } from './util'; import { getMultichainBalances, getMultichainNetwork } from './multichain'; -/** - * Returns true if the currently selected network is inaccessible or whether no - * provider has been set yet for the currently selected network. - * - * @param {object} state - Redux state object. - */ -export function isNetworkLoading(state) { - const selectedNetworkClientId = getSelectedNetworkClientId(state); - return ( - selectedNetworkClientId && - state.metamask.networksMetadata[selectedNetworkClientId].status !== - NetworkStatus.Available - ); -} - -export function getSelectedNetworkClientId(state) { - return state.metamask.selectedNetworkClientId; -} - export function getNetworkIdentifier(state) { const { type, nickname, rpcUrl } = getProviderConfig(state); @@ -703,14 +686,15 @@ export function getGasIsLoading(state) { return state.appState.gasIsLoading; } -export const getNetworkConfigurationsByChainId = createDeepEqualSelector( - (state) => state.metamask.networkConfigurationsByChainId, - /** - * @param networkConfigurationsByChainId - * @returns { import('@metamask/network-controller').NetworkState['networkConfigurationsByChainId']} - */ - (networkConfigurationsByChainId) => networkConfigurationsByChainId, -); +/** + * Retrieves user preference to never see the "Switched Network" toast + * + * @param state - Redux state object. + * @returns Boolean preference value + */ +export function getNeverShowSwitchedNetworkMessage(state) { + return state.metamask.switchedNetworkNeverShowMessage; +} /** * @type (state: any, chainId: string) => import('@metamask/network-controller').NetworkConfiguration @@ -1226,7 +1210,7 @@ export const getMultipleTargetsSubjectMetadata = createDeepEqualSelector( export function getRpcPrefsForCurrentProvider(state) { const { rpcPrefs } = getProviderConfig(state); - return rpcPrefs || {}; + return rpcPrefs; } export function getKnownMethodData(state, data) { @@ -1262,13 +1246,6 @@ export function getUseExternalServices(state) { return state.metamask.useExternalServices; } -export function getInfuraBlocked(state) { - return ( - state.metamask.networksMetadata[getSelectedNetworkClientId(state)] - .status === NetworkStatus.Blocked - ); -} - export function getUSDConversionRate(state) { return state.metamask.currencyRates[getProviderConfig(state).ticker] ?.usdConversionRate; diff --git a/ui/selectors/selectors.test.js b/ui/selectors/selectors.test.js index d6656e481709..65fc6ab80f6f 100644 --- a/ui/selectors/selectors.test.js +++ b/ui/selectors/selectors.test.js @@ -10,10 +10,10 @@ import { KeyringType } from '../../shared/constants/keyring'; import mockState from '../../test/data/mock-state.json'; import { CHAIN_IDS, NETWORK_TYPES } from '../../shared/constants/network'; import { createMockInternalAccount } from '../../test/jest/mocks'; -import { getProviderConfig } from '../ducks/metamask/metamask'; import { mockNetworkState } from '../../test/stub/networks'; import { DeleteRegulationStatus } from '../../shared/constants/metametrics'; import { selectSwitchedNetworkNeverShowMessage } from '../components/app/toast-master/selectors'; +import { getProviderConfig } from './networks'; import * as selectors from './selectors'; jest.mock('../../app/scripts/lib/util', () => ({ @@ -646,45 +646,6 @@ describe('Selectors', () => { }); }); - describe('#getNetworkConfigurationsByChainId', () => { - it('returns networkConfigurationsByChainId', () => { - const networkConfigurationsByChainId = { - '0xtest': { - chainId: '0xtest', - nativeCurrency: 'TEST', - defaultRpcEndpointUrl: 'https://mock-rpc-url-1', - defaultRpcEndpointIndex: 0, - rpcEndpoints: [ - { - networkClientId: 'testNetworkConfigurationId1', - url: 'https://mock-rpc-url-1', - }, - ], - }, - '0x1337': { - chainId: '0x1337', - nativeCurrency: 'RPC', - defaultRpcEndpointUrl: 'https://mock-rpc-url-2', - defaultRpcEndpointIndex: 0, - rpcEndpoints: [ - { - networkClientId: 'testNetworkConfigurationId2', - url: 'https://mock-rpc-url-2', - }, - ], - }, - }; - - expect( - selectors.getNetworkConfigurationsByChainId({ - metamask: { - networkConfigurationsByChainId, - }, - }), - ).toStrictEqual(networkConfigurationsByChainId); - }); - }); - describe('#getCurrentNetwork', () => { it('returns built-in network configuration', () => { const modifiedMockState = { @@ -1226,24 +1187,6 @@ describe('Selectors', () => { expect(selectors.getSnapsInstallPrivacyWarningShown(mockState)).toBe(false); }); - it('#getInfuraBlocked', () => { - let isInfuraBlocked = selectors.getInfuraBlocked(mockState); - expect(isInfuraBlocked).toBe(false); - - const modifiedMockState = { - ...mockState, - metamask: { - ...mockState.metamask, - ...mockNetworkState({ - chainId: CHAIN_IDS.GOERLI, - metadata: { status: 'blocked' }, - }), - }, - }; - isInfuraBlocked = selectors.getInfuraBlocked(modifiedMockState); - expect(isInfuraBlocked).toBe(true); - }); - it('#getSnapRegistryData', () => { const mockSnapId = 'npm:@metamask/test-snap-bip44'; expect(selectors.getSnapRegistryData(mockState, mockSnapId)).toStrictEqual( diff --git a/ui/selectors/transactions.js b/ui/selectors/transactions.js index 3074fd4bfde4..7fbfef9602a1 100644 --- a/ui/selectors/transactions.js +++ b/ui/selectors/transactions.js @@ -12,7 +12,7 @@ import { import txHelper from '../helpers/utils/tx-helper'; import { SmartTransactionStatus } from '../../shared/constants/transaction'; import { hexToDecimal } from '../../shared/modules/conversion.utils'; -import { getProviderConfig } from '../ducks/metamask/metamask'; +import { getProviderConfig } from './networks'; import { getCurrentChainId } from './selectors'; import { getSelectedInternalAccount } from './accounts'; import { hasPendingApprovals, getApprovalRequestsByType } from './approvals'; diff --git a/ui/store/actions.ts b/ui/store/actions.ts index 886739d2d54f..f3879501510a 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -81,10 +81,8 @@ import { SEND_STAGES, } from '../ducks/send'; import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-account'; -import { - getProviderConfig, - getUnconnectedAccountAlertEnabledness, -} from '../ducks/metamask/metamask'; +import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask'; +import { getProviderConfig } from '../selectors/networks'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; import { HardwareDeviceNames,