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,