From 6f8db43c32d3e2649b4ed91c1d8afa2bb5c34972 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-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 | 15 +- test/stub/networks.ts | 4 +- .../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 +- .../ui/new-network-info/new-network-info.js | 2 +- ui/ducks/bridge/selectors.test.ts | 2 +- ui/ducks/bridge/selectors.ts | 7 +- ui/ducks/metamask/metamask.js | 43 +----- ui/ducks/send/send.js | 2 +- ui/hooks/ramps/useRamps/useRamps.ts | 11 +- ui/hooks/useFirstPartyContractName.ts | 5 +- 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 +- .../confirm-subtitle/confirm-subtitle.test.js | 2 +- .../signature-request-header.js | 6 +- .../signature-request/signature-request.js | 6 +- .../signature-request.test.js | 6 +- .../confirm-transaction-base.container.js | 6 +- .../confirmations/hooks/useTransactionInfo.js | 2 +- .../send/gas-display/gas-display.js | 6 +- ui/pages/routes/routes.container.js | 2 +- .../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/pages/token-details/token-details-page.js | 3 +- ui/selectors/accounts.test.ts | 1 + 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 | 43 +----- ui/selectors/selectors.test.js | 66 ++------- ui/selectors/transactions.js | 2 +- ui/store/actions.ts | 6 +- 55 files changed, 386 insertions(+), 242 deletions(-) create mode 100644 ui/selectors/networks.test.ts create mode 100644 ui/selectors/networks.ts diff --git a/app/scripts/lib/ppom/ppom-util.ts b/app/scripts/lib/ppom/ppom-util.ts index 73999061a910..4e28f6214d25 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 30112ee61a3c..f5b9b17697bc 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -227,10 +227,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'; import { BalancesController as MultichainBalancesController } from './lib/accounts/BalancesController'; import { 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 a98417794d81..1fd765c8440c 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 1c3147632381..134394d4d0ba 100644 --- a/shared/modules/selectors/smart-transactions.ts +++ b/shared/modules/selectors/smart-transactions.ts @@ -12,9 +12,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: { @@ -69,9 +71,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. @@ -100,7 +100,7 @@ const hasNonZeroBalance = (state: SmartTransactionsMetaMaskState) => { }; export const getIsSmartTransactionsOptInModalAvailable = ( - state: SmartTransactionsMetaMaskState, + state: SmartTransactionsMetaMaskState & NetworkState, ) => { return ( getCurrentChainSupportsSmartTransactions(state) && @@ -111,7 +111,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. @@ -130,7 +130,7 @@ export const getSmartTransactionsEnabled = ( }; export const getIsSmartTransaction = ( - state: SmartTransactionsMetaMaskState, + state: SmartTransactionsMetaMaskState & NetworkState, ): boolean => { const smartTransactionsOptInStatus = getSmartTransactionsOptInStatus(state); const smartTransactionsEnabled = getSmartTransactionsEnabled(state); diff --git a/test/data/mock-state.json b/test/data/mock-state.json index 4b6a2f506215..7ee0ca55d7d1 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -387,26 +387,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": { @@ -415,6 +419,7 @@ "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", "id": "cf8dace4-9439-4bd4-b3a8-88c821c8fcb3", "metadata": { + "importTime": 0, "name": "Test Account", "keyring": { "type": "HD Key Tree" @@ -434,6 +439,7 @@ "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b", "id": "07c2cfec-36c9-46c4-8115-3836d3ac9047", "metadata": { + "importTime": 0, "name": "Test Account 2", "keyring": { "type": "HD Key Tree" @@ -453,6 +459,7 @@ "address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", "id": "15e69915-2a1a-4019-93b3-916e11fd432f", "metadata": { + "importTime": 0, "name": "Ledger Hardware 2", "keyring": { "type": "Ledger Hardware" @@ -472,6 +479,7 @@ "address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823", "id": "784225f4-d30b-4e77-a900-c8bbce735b88", "metadata": { + "importTime": 0, "name": "Test Account 3", "keyring": { "type": "HD Key Tree" @@ -491,6 +499,7 @@ "address": "0xca8f1F0245530118D0cf14a06b01Daf8f76Cf281", "id": "694225f4-d30b-4e77-a900-c8bbce735b42", "metadata": { + "importTime": 0, "name": "Test Account 4", "keyring": { "type": "Custody test" @@ -510,11 +519,13 @@ "address": "0xb552685e3d2790efd64a175b00d51f02cdafee5d", "id": "c3deeb99-ba0d-4a4e-a0aa-033fc1f79ae3", "metadata": { + "importTime": 0, "name": "Snap Account 1", "keyring": { "type": "Snap Keyring" }, "snap": { + "enabled": true, "id": "snap-id", "name": "snap-name" } diff --git a/test/stub/networks.ts b/test/stub/networks.ts index bae4798d001f..44dd07386fbb 100644 --- a/test/stub/networks.ts +++ b/test/stub/networks.ts @@ -24,7 +24,7 @@ export const mockNetworkStateOld = ( blockExplorerUrl?: string; metadata?: NetworkMetadata; }[] -) => { +): NetworkState => { const networkConfigurations = networks.map((network) => ({ id: network.id ?? uuidv4(), chainId: network.chainId, @@ -64,7 +64,7 @@ export const mockNetworkStateOld = ( return { selectedNetworkClientId: networkConfigurations[0].id, - networkConfigurations: networkConfigurations.reduce( + networkConfigurationsByChainId: networkConfigurations.reduce( (acc, network) => ({ ...acc, [network.id]: network }), {}, ), 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/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.test.ts b/ui/ducks/bridge/selectors.test.ts index 50a5ad4beb33..141dc1fc51e7 100644 --- a/ui/ducks/bridge/selectors.test.ts +++ b/ui/ducks/bridge/selectors.test.ts @@ -1,7 +1,7 @@ import { createBridgeMockStore } from '../../../test/jest/mock-store'; import { CHAIN_IDS, FEATURED_RPCS } from '../../../shared/constants/network'; import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../../shared/constants/bridge'; -import { getProviderConfig } from '../metamask/metamask'; +import { getProviderConfig } from '../../selectors/networks'; import { mockNetworkState } from '../../../test/stub/networks'; import { getAllBridgeableNetworks, diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 5f482ddecb91..5b80e4ab53a7 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -1,4 +1,3 @@ -import { NetworkState } from '@metamask/network-controller'; import { uniqBy } from 'lodash'; import { getIsBridgeEnabled, @@ -13,12 +12,12 @@ import { } from '../../../app/scripts/controllers/bridge/types'; import { FEATURED_RPCS } from '../../../shared/constants/network'; import { createDeepEqualSelector } from '../../selectors/util'; -import { getProviderConfig } from '../metamask/metamask'; +import { NetworkState, getProviderConfig } from '../../selectors/networks'; import { BridgeState } from './bridge'; // TODO add swaps state -type BridgeAppState = { - metamask: NetworkState & { bridgeState: BridgeControllerState } & { +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 427f604f0096..712f197fad65 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 cdbe7d2daa86..add13cafb6ac 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -100,9 +100,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/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/useFirstPartyContractName.ts b/ui/hooks/useFirstPartyContractName.ts index 47468b472955..0a03befa882c 100644 --- a/ui/hooks/useFirstPartyContractName.ts +++ b/ui/hooks/useFirstPartyContractName.ts @@ -1,5 +1,6 @@ import { NameType } from '@metamask/name-controller'; import { useSelector } from 'react-redux'; +import { type Hex } from '@metamask/utils'; import { getCurrentChainId } from '../selectors'; import { EXPERIENCES_TYPE, @@ -28,8 +29,8 @@ export function useFirstPartyContractNames( return ( Object.keys(FIRST_PARTY_CONTRACT_NAMES).find( (name) => - FIRST_PARTY_CONTRACT_NAMES[name as EXPERIENCES_TYPE]?.[ - chainId + FIRST_PARTY_CONTRACT_NAMES[name as EXPERIENCES_TYPE][ + chainId as Hex ]?.toLowerCase() === normalizedValue, ) ?? null ); 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/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 30f839b3f78c..aade46506c41 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 @@ -3,10 +3,8 @@ import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { useI18nContext } from '../../../../hooks/useI18nContext'; -import { - getNativeCurrency, - getProviderConfig, -} from '../../../../ducks/metamask/metamask'; +import { getNativeCurrency } from '../../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../../selectors/networks'; import { accountsWithSendEtherInfoSelector, conversionRateSelector, diff --git a/ui/pages/confirmations/components/signature-request/signature-request.js b/ui/pages/confirmations/components/signature-request/signature-request.js index f15c7045e2d7..e2dd11df5e6e 100644 --- a/ui/pages/confirmations/components/signature-request/signature-request.js +++ b/ui/pages/confirmations/components/signature-request/signature-request.js @@ -24,10 +24,8 @@ import { getAccountType, ///: END:ONLY_INCLUDE_IF } from '../../../../selectors'; -import { - getProviderConfig, - isAddressLedger, -} from '../../../../ducks/metamask/metamask'; +import { isAddressLedger } from '../../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../../selectors/networks'; import { sanitizeMessage, ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) diff --git a/ui/pages/confirmations/components/signature-request/signature-request.test.js b/ui/pages/confirmations/components/signature-request/signature-request.test.js index 0d50f906e5ca..7634573362c5 100644 --- a/ui/pages/confirmations/components/signature-request/signature-request.test.js +++ b/ui/pages/confirmations/components/signature-request/signature-request.test.js @@ -6,10 +6,8 @@ import { EthAccountType } from '@metamask/keyring-api'; import mockState from '../../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../../shared/constants/security-provider'; -import { - getNativeCurrency, - getProviderConfig, -} from '../../../../ducks/metamask/metamask'; +import { getNativeCurrency } from '../../../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../../../selectors/networks'; import { accountsWithSendEtherInfoSelector, conversionRateSelector, 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 ed012d07e5ce..332526b3148e 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 @@ -55,7 +55,6 @@ import { getUnapprovedTransactions, getInternalAccountByAddress, getApprovedAndSignedTransactions, - getSelectedNetworkClientId, getPrioritizedUnapprovedTemplatedConfirmations, } from '../../../selectors'; import { @@ -72,10 +71,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 5fbad8445cd6..25447074ba7e 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 419daf561778..d8eefd030e61 100644 --- a/ui/pages/routes/routes.container.js +++ b/ui/pages/routes/routes.container.js @@ -61,8 +61,8 @@ import { getSendStage } from '../../ducks/send'; import { getAlertEnabledness, getIsUnlocked, - getProviderConfig, } from '../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../selectors/networks'; import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../shared/constants/preferences'; 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 e8f2931c0e85..551dd6ddef15 100644 --- a/ui/pages/settings/security-tab/security-tab.container.js +++ b/ui/pages/settings/security-tab/security-tab.container.js @@ -23,9 +23,9 @@ import { } from '../../../store/actions'; import { getIsSecurityAlertsEnabled, - getNetworkConfigurationsByChainId, 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 fa6cc6e389c6..6925530991db 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, ownProps) => { diff --git a/ui/pages/settings/settings.container.js b/ui/pages/settings/settings.container.js index 638b6aea23af..6d05ee1d5816 100644 --- a/ui/pages/settings/settings.container.js +++ b/ui/pages/settings/settings.container.js @@ -30,8 +30,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/pages/token-details/token-details-page.js b/ui/pages/token-details/token-details-page.js index 086a2de61f4b..88a66e6edc07 100644 --- a/ui/pages/token-details/token-details-page.js +++ b/ui/pages/token-details/token-details-page.js @@ -1,7 +1,8 @@ import React, { useContext } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Redirect, useHistory, useParams } from 'react-router-dom'; -import { getProviderConfig, getTokens } from '../../ducks/metamask/metamask'; +import { getTokens } from '../../ducks/metamask/metamask'; +import { getProviderConfig } from '../../selectors/networks'; import { getTokenList } from '../../selectors'; import { useCopyToClipboard } from '../../hooks/useCopyToClipboard'; import Identicon from '../../components/ui/identicon'; diff --git a/ui/selectors/accounts.test.ts b/ui/selectors/accounts.test.ts index 6563a27b1285..033d88c30faa 100644 --- a/ui/selectors/accounts.test.ts +++ b/ui/selectors/accounts.test.ts @@ -44,6 +44,7 @@ describe('Accounts Selectors', () => { address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', metadata: { + importTime: 0, name: 'Test Account', keyring: { type: 'HD Key Tree', 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 19fdac1559a7..e944b3c72611 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 335f557d5318..99c4248b70b5 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 44a58908f3ee..42df7507b140 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, @@ -77,7 +76,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, @@ -108,6 +106,10 @@ import { MultichainNativeAssets } from '../../shared/constants/multichain/assets // eslint-disable-next-line import/no-restricted-paths import { BridgeFeatureFlagsKey } from '../../app/scripts/controllers/bridge/types'; import { hasTransactionData } from '../../shared/modules/transaction.utils'; +import { + getProviderConfig, + getNetworkConfigurationsByChainId, +} from './networks'; import { getAllUnapprovedTransactions, getCurrentNetworkTransactions, @@ -125,25 +127,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); @@ -648,15 +631,6 @@ export function getNeverShowSwitchedNetworkMessage(state) { return state.metamask.switchedNetworkNeverShowMessage; } -export const getNetworkConfigurationsByChainId = createDeepEqualSelector( - (state) => state.metamask.networkConfigurationsByChainId, - /** - * @param networkConfigurationsByChainId - * @returns { import('@metamask/network-controller').NetworkState['networkConfigurationsByChainId']} - */ - (networkConfigurationsByChainId) => networkConfigurationsByChainId, -); - export function getRequestingNetworkInfo(state, chainIds) { // If chainIds is undefined, set it to an empty array let processedChainIds = chainIds === undefined ? [] : chainIds; @@ -1153,7 +1127,7 @@ export const getMultipleTargetsSubjectMetadata = createDeepEqualSelector( export function getRpcPrefsForCurrentProvider(state) { const { rpcPrefs } = getProviderConfig(state); - return rpcPrefs || {}; + return rpcPrefs; } export function getKnownMethodData(state, data) { @@ -1189,13 +1163,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 ca09f4dbc318..00d6786d28c3 100644 --- a/ui/selectors/selectors.test.js +++ b/ui/selectors/selectors.test.js @@ -8,8 +8,8 @@ import { CHAIN_IDS, NETWORK_TYPES } from '../../shared/constants/network'; import { SURVEY_DATE, SURVEY_GMT } from '../helpers/constants/survey'; import { PRIVACY_POLICY_DATE } from '../helpers/constants/privacy-policy'; import { createMockInternalAccount } from '../../test/jest/mocks'; -import { getProviderConfig } from '../ducks/metamask/metamask'; import { mockNetworkState } from '../../test/stub/networks'; +import { getProviderConfig } from './networks'; import * as selectors from './selectors'; jest.mock('../../app/scripts/lib/util', () => ({ @@ -627,45 +627,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 = { @@ -1207,24 +1168,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( @@ -1570,6 +1513,7 @@ describe('Selectors', () => { balance: '0x0', id: '07c2cfec-36c9-46c4-8115-3836d3ac9047', metadata: { + importTime: 0, name: 'Test Account 2', keyring: { type: 'HD Key Tree', @@ -1594,6 +1538,7 @@ describe('Selectors', () => { balance: '0x0', id: '784225f4-d30b-4e77-a900-c8bbce735b88', metadata: { + importTime: 0, name: 'Test Account 3', keyring: { type: 'HD Key Tree', @@ -1617,6 +1562,7 @@ describe('Selectors', () => { address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3', metadata: { + importTime: 0, name: 'Test Account', keyring: { type: 'HD Key Tree', @@ -1642,6 +1588,7 @@ describe('Selectors', () => { address: '0xc42edfcc21ed14dda456aa0756c153f7985d8813', id: '15e69915-2a1a-4019-93b3-916e11fd432f', metadata: { + importTime: 0, name: 'Ledger Hardware 2', keyring: { type: 'Ledger Hardware', @@ -1669,8 +1616,10 @@ describe('Selectors', () => { keyring: { type: 'Snap Keyring', }, + importTime: 0, name: 'Snap Account 1', snap: { + enabled: true, id: 'snap-id', name: 'snap-name', }, @@ -1691,6 +1640,7 @@ describe('Selectors', () => { { id: '694225f4-d30b-4e77-a900-c8bbce735b42', metadata: { + importTime: 0, name: 'Test Account 4', keyring: { type: 'Custody test', 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 c4bed2665a6b..cb50528a45e4 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -78,10 +78,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,