From 38c38f2742525d0c243f6b3abba1a36668a960ee Mon Sep 17 00:00:00 2001 From: salimtb Date: Mon, 24 Jun 2024 12:37:19 +0200 Subject: [PATCH] feat: add custom network new checkers --- app/_locales/en/messages.json | 24 ++ shared/constants/swaps.ts | 4 +- .../network-list-menu/network-list-menu.js | 29 +- .../add-network-modal.test.js.snap | 47 +-- .../add-network-modal.test.js | 6 +- .../add-network-modal/index.js | 8 +- .../networks-form/networks-form.js | 337 ++++++++++++++++-- .../networks-form/networks-form.test.js | 25 +- .../networks-tab-content.test.js | 8 + .../networks-tab/networks-tab.test.js | 4 + ui/selectors/selectors.js | 4 + 11 files changed, 424 insertions(+), 72 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 814a31bdca50..7383929dbafd 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1685,6 +1685,9 @@ "editGasTooLow": { "message": "Unknown processing time" }, + "editNetworkLink": { + "message": "edit the original network" + }, "editNonceField": { "message": "Edit nonce" }, @@ -1851,6 +1854,15 @@ "etherscanViewOn": { "message": "View on Etherscan" }, + "existingChainId": { + "message": "The information you have entered is associated with an existing chain ID." + }, + "existingChainIdAndUrl": { + "message": "You already have a network with the same Chain ID and RPC URL. Enter a new chain ID or RPC URL." + }, + "existingRpcUrl": { + "message": "This URL is associated with another chain ID." + }, "expandView": { "message": "Expand view" }, @@ -1905,6 +1917,9 @@ "message": "File import not working? Click here!", "description": "Helps user import their account from a JSON file" }, + "findTheRightChainId": { + "message": "Find the right one on:" + }, "flaskWelcomeUninstall": { "message": "you should uninstall this extension", "description": "This request is shown on the Flask Welcome screen. It is intended for non-developers, and will be bolded." @@ -6159,6 +6174,9 @@ "message": "U2F", "description": "A name on an API for the browser to interact with devices that support the U2F protocol. On some browsers we use it to connect MetaMask to Ledger devices." }, + "unMatchedChain": { + "message": "According to our records, this URL does not match a known provider for this chain ID." + }, "unapproved": { "message": "Unapproved" }, @@ -6210,6 +6228,9 @@ "update": { "message": "Update" }, + "updateOrEditNetworkInformations": { + "message": "Update your information or" + }, "updateRequest": { "message": "Update request" }, @@ -6431,6 +6452,9 @@ "whatsThis": { "message": "What's this?" }, + "wrongChainId": { + "message": "This chain ID doesn’t match the network name." + }, "wrongNetworkName": { "message": "According to our records, the network name may not correctly match this chain ID." }, diff --git a/shared/constants/swaps.ts b/shared/constants/swaps.ts index bd3db9d94a2f..b73fa9b4f1d4 100644 --- a/shared/constants/swaps.ts +++ b/shared/constants/swaps.ts @@ -175,14 +175,14 @@ export const GAS_API_BASE_URL = 'https://gas.api.cx.metamask.io'; export const GAS_DEV_API_BASE_URL = 'https://gas.uat-api.cx.metamask.io'; const BSC_DEFAULT_BLOCK_EXPLORER_URL = 'https://bscscan.com/'; -const MAINNET_DEFAULT_BLOCK_EXPLORER_URL = 'https://etherscan.io/'; +export const MAINNET_DEFAULT_BLOCK_EXPLORER_URL = 'https://etherscan.io/'; const GOERLI_DEFAULT_BLOCK_EXPLORER_URL = 'https://goerli.etherscan.io/'; const POLYGON_DEFAULT_BLOCK_EXPLORER_URL = 'https://polygonscan.com/'; const AVALANCHE_DEFAULT_BLOCK_EXPLORER_URL = 'https://snowtrace.io/'; const OPTIMISM_DEFAULT_BLOCK_EXPLORER_URL = 'https://optimistic.etherscan.io/'; const ARBITRUM_DEFAULT_BLOCK_EXPLORER_URL = 'https://arbiscan.io/'; const ZKSYNC_DEFAULT_BLOCK_EXPLORER_URL = 'https://explorer.zksync.io/'; -const LINEA_DEFAULT_BLOCK_EXPLORER_URL = 'https://lineascan.build/'; +export const LINEA_DEFAULT_BLOCK_EXPLORER_URL = 'https://lineascan.build/'; const BASE_DEFAULT_BLOCK_EXPLORER_URL = 'https://basescan.org/'; export const ALLOWED_PROD_SWAPS_CHAIN_IDS = [ diff --git a/ui/components/multichain/network-list-menu/network-list-menu.js b/ui/components/multichain/network-list-menu/network-list-menu.js index c481b163f8a0..ca5e4f490e9f 100644 --- a/ui/components/multichain/network-list-menu/network-list-menu.js +++ b/ui/components/multichain/network-list-menu/network-list-menu.js @@ -257,16 +257,19 @@ export const NetworkListMenu = ({ onClose }) => { }; }; - const getOnEditCallback = (network) => { - return () => { - const networkToUse = { - ...network, - label: network.nickname, - }; - setModalTitle(network.nickname); - setNetworkToEdit(networkToUse); - setActionMode(ACTION_MODES.EDIT); + const getOnEdit = (network) => { + const networkToUse = { + ...network, + label: network.nickname, }; + + setModalTitle(network.nickname); + setNetworkToEdit(networkToUse); + setActionMode(ACTION_MODES.EDIT); + }; + + const getOnEditCallback = (network) => { + return () => getOnEdit(network); }; const generateMenuItems = (desiredNetworks) => { @@ -527,7 +530,13 @@ export const NetworkListMenu = ({ onClose }) => { ); } else if (actionMode === ACTION_MODES.ADD) { - return ; + return ( + + ); } return ( - -

- Default RPC URL -

-

-

+ - +
- -
-
@@ -209,7 +214,7 @@ exports[`Add Network Modal should render 1`] = ` class="mm-box mm-text mm-button-base mm-button-base--size-lg mm-button-base--disabled mm-button-primary mm-button-primary--disabled mm-text--body-md-medium mm-box--padding-0 mm-box--padding-right-4 mm-box--padding-left-4 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--width-full mm-box--color-primary-inverse mm-box--background-color-primary-default mm-box--rounded-pill" disabled="" > - Save + Next diff --git a/ui/pages/onboarding-flow/add-network-modal/add-network-modal.test.js b/ui/pages/onboarding-flow/add-network-modal/add-network-modal.test.js index 16909bc8010b..615c1790530e 100644 --- a/ui/pages/onboarding-flow/add-network-modal/add-network-modal.test.js +++ b/ui/pages/onboarding-flow/add-network-modal/add-network-modal.test.js @@ -23,7 +23,7 @@ describe('Add Network Modal', () => { mockNetworkMenuRedesignToggle.mockImplementation(() => false); const mockStore = configureMockStore([])({ - metamask: { useSafeChainsListValidation: true }, + metamask: { useSafeChainsListValidation: true, orderedNetworkList: {} }, }); const { container } = renderWithProvider( @@ -40,7 +40,7 @@ describe('Add Network Modal', () => { mockNetworkMenuRedesignToggle.mockReturnValue(true); const mockStore = configureMockStore([thunk])({ - metamask: { useSafeChainsListValidation: true }, + metamask: { useSafeChainsListValidation: true, orderedNetworkList: {} }, }); const { queryByText } = renderWithProvider( @@ -50,7 +50,7 @@ describe('Add Network Modal', () => { await waitFor(() => { expect(queryByText('Cancel')).not.toBeInTheDocument(); - expect(queryByText('Save')).toBeInTheDocument(); + expect(queryByText('Next')).toBeInTheDocument(); }); }); }); diff --git a/ui/pages/onboarding-flow/add-network-modal/index.js b/ui/pages/onboarding-flow/add-network-modal/index.js index c031739b68b6..3213c15a6125 100644 --- a/ui/pages/onboarding-flow/add-network-modal/index.js +++ b/ui/pages/onboarding-flow/add-network-modal/index.js @@ -16,7 +16,7 @@ import NetworksForm from '../../settings/networks-tab/networks-form/networks-for export default function AddNetworkModal({ showHeader = false, - isNewNetworkFlow = false, + getOnEditCallback = null, addNewNetwork = true, networkToEdit = null, }) { @@ -50,7 +50,7 @@ export default function AddNetworkModal({ networksToRender={[]} cancelCallback={closeCallback} submitCallback={closeCallback} - isNewNetworkFlow={isNewNetworkFlow} + getOnEditCallback={getOnEditCallback} {...additionalProps} /> @@ -59,14 +59,14 @@ export default function AddNetworkModal({ AddNetworkModal.propTypes = { showHeader: PropTypes.bool, - isNewNetworkFlow: PropTypes.bool, + getOnEditCallback: PropTypes.func, addNewNetwork: PropTypes.bool, networkToEdit: PropTypes.object, }; AddNetworkModal.defaultProps = { showHeader: false, - isNewNetworkFlow: false, + getOnEditCallback: null, addNewNetwork: true, networkToEdit: null, }; diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.js b/ui/pages/settings/networks-tab/networks-form/networks-form.js index 298a063f98b6..4f0a640d880f 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.js @@ -21,10 +21,12 @@ import { import { BUILT_IN_NETWORKS, CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, + CHAIN_ID_TO_RPC_URL_MAP, CHAIN_IDS, CHAINLIST_CURRENCY_SYMBOLS_MAP_NETWORK_COLLISION, FEATURED_RPCS, infuraProjectId, + NETWORK_TO_NAME_MAP, } from '../../../../../shared/constants/network'; import fetchWithCache from '../../../../../shared/lib/fetch-with-cache'; import { decimalToHex } from '../../../../../shared/modules/conversion.utils'; @@ -40,7 +42,11 @@ import { MetaMetricsContext } from '../../../../contexts/metametrics'; import { getNetworkLabelKey } from '../../../../helpers/utils/i18n-helper'; import { useI18nContext } from '../../../../hooks/useI18nContext'; import { usePrevious } from '../../../../hooks/usePrevious'; -import { useSafeChainsListValidationSelector } from '../../../../selectors'; +import { + getNonTestNetworks, + getOrderedNetworksList, + useSafeChainsListValidationSelector, +} from '../../../../selectors'; import { editAndSetNetworkConfiguration, requestUserApproval, @@ -59,6 +65,8 @@ import { ButtonPrimarySize, HelpText, HelpTextSeverity, + IconName, + IconSize, Text, } from '../../../../components/component-library'; import { FormTextField } from '../../../../components/component-library/form-text-field/deprecated'; @@ -120,11 +128,13 @@ const NetworksForm = ({ selectedNetwork, cancelCallback, submitCallback, + getOnEditCallback, }) => { const t = useI18nContext(); const dispatch = useDispatch(); const DEFAULT_SUGGESTED_TICKER = []; const DEFAULT_SUGGESTED_NAME = []; + const CHAIN_LIST_URL = 'https://chainid.network/'; const { label, labelKey, viewOnly, rpcPrefs } = selectedNetwork; const selectedNetworkName = label || (labelKey && t(getNetworkLabelKey(labelKey))); @@ -138,6 +148,7 @@ const NetworksForm = ({ const [blockExplorerUrl, setBlockExplorerUrl] = useState( selectedNetwork?.blockExplorerUrl || '', ); + const [errors, setErrors] = useState({}); const [warnings, setWarnings] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); @@ -147,12 +158,16 @@ const NetworksForm = ({ const [isEditing, setIsEditing] = useState(Boolean(addNewNetwork)); const [previousNetwork, setPreviousNetwork] = useState(selectedNetwork); const [suggestedNames, setSuggestedNames] = useState(DEFAULT_SUGGESTED_NAME); + const nonTestNetworks = useSelector(getNonTestNetworks); const trackEvent = useContext(MetaMetricsContext); const useSafeChainsListValidation = useSelector( useSafeChainsListValidationSelector, ); + + const orderedNetworksList = useSelector(getOrderedNetworksList); + const networkMenuRedesign = useSelector( getLocalNetworkMenuRedesignFeatureFlag, ); @@ -283,6 +298,40 @@ const NetworksForm = ({ isEditing, ]); + const newOrderNetworks = () => { + if (!orderedNetworksList || orderedNetworksList.length === 0) { + return nonTestNetworks; + } + + // Create a mapping of chainId to index in orderedNetworksList + const orderedIndexMap = {}; + orderedNetworksList.forEach((network, index) => { + orderedIndexMap[`${network.networkId}_${network.networkRpcUrl}`] = index; + }); + + // Sort nonTestNetworks based on the order in orderedNetworksList + const sortedNonTestNetworks = nonTestNetworks.sort((a, b) => { + const keyA = `${a.chainId}_${a.rpcUrl}`; + const keyB = `${b.chainId}_${b.rpcUrl}`; + return orderedIndexMap[keyA] - orderedIndexMap[keyB]; + }); + + return sortedNonTestNetworks; + }; + + const getOnEditCallbackClick = () => { + const networksList = newOrderNetworks(); + + const networkToEdit = Object.values(networksList).find( + (network) => + getDisplayChainId(chainId) === + parseInt(network.chainId, 16).toString(10), + ); + + if (networkToEdit) { + getOnEditCallback(networkToEdit); + } + }; useEffect(() => { return () => { setNetworkName(''); @@ -412,6 +461,39 @@ const NetworksForm = ({ } } + if ( + Object.values(orderedNetworksList).some( + (network) => + getDisplayChainId(chainArg) === + parseInt(network.networkId, 16).toString(10) && + rpcUrl === network.networkRpcUrl, + ) && + addNewNetwork + ) { + return { + error: { + key: 'existingChainIdAndUrl', + msg: t('existingChainIdAndUrl'), + }, + }; + } + + if ( + Object.values(orderedNetworksList).some( + (network) => + getDisplayChainId(chainArg) === + parseInt(network.networkId, 16).toString(10) && + network.rpcUrl === rpcUrl, + ) && + addNewNetwork + ) { + return { + error: { + key: 'existingChainId', + }, + }; + } + const [matchingChainId] = networksToRender.filter( (e) => e.chainId === hexChainId && e.rpcUrl !== rpcUrl, ); @@ -473,13 +555,9 @@ const NetworksForm = ({ } errorKey = 'endpointReturnedDifferentChainId'; - errorMessage = t('endpointReturnedDifferentChainId', [ - endpointChainId.length <= 12 - ? endpointChainId - : `${endpointChainId.slice(0, 9)}...`, - ]); } } + if (errorKey) { return { error: { @@ -500,7 +578,15 @@ const NetworksForm = ({ autoSuggestName(formChainId); return null; }, - [rpcUrl, networksToRender, t], + [ + rpcUrl, + networksToRender, + t, + addNewNetwork, + autoSuggestName, + autoSuggestTicker, + orderedNetworksList, + ], ); /** @@ -566,7 +652,23 @@ const NetworksForm = ({ let warningMessage; const decimalChainId = getDisplayChainId(formChainId); - if (!decimalChainId || !formName) { + let hexChainId = formChainId; + if (!formChainId.startsWith('0x')) { + try { + hexChainId = `0x${decimalToHex(formChainId)}`; + } catch (err) { + return { + error: { + key: 'invalidHexNumber', + msg: t('invalidHexNumber'), + }, + }; + } + } + + const isMatchedName = NETWORK_TO_NAME_MAP[hexChainId] === formName; + + if (!decimalChainId || !formName || isMatchedName) { setSuggestedNames([]); return null; } @@ -612,7 +714,9 @@ const NetworksForm = ({ ); const validateRPCUrl = useCallback( - (url) => { + async (url, formChainId) => { + const decimalChainId = getDisplayChainId(formChainId); + const [ { rpcUrl: matchingRPCUrl = null, @@ -622,6 +726,22 @@ const NetworksForm = ({ ] = networksToRender.filter((e) => e.rpcUrl === url); const { rpcUrl: selectedNetworkRpcUrl } = selectedNetwork; + if ( + Object.values(orderedNetworksList).some( + (network) => url === network.networkRpcUrl, + ) && + addNewNetwork + ) { + return { + key: 'existingRpcUrl', + msg: t('existingRpcUrl'), + }; + } + + if (!url || !decimalChainId) { + return null; + } + if (url?.length > 0 && !isWebUrl(url)) { if (isWebUrl(`https://${url}`)) { return { @@ -641,9 +761,33 @@ const NetworksForm = ({ ]), }; } + + let endpointChainId; + let providerError; + + try { + endpointChainId = await jsonRpcRequest(rpcUrl, 'eth_chainId'); + } catch (err) { + log.warn('Failed to fetch the chainId from the endpoint.', err); + providerError = err; + } + + if (providerError || typeof endpointChainId !== 'string') { + return { + key: 'failedToFetchChainId', + msg: t('unMatchedChain'), + }; + } return null; }, - [selectedNetwork, networksToRender, t], + [ + selectedNetwork, + networksToRender, + t, + orderedNetworksList, + rpcUrl, + addNewNetwork, + ], ); // validation effect @@ -651,6 +795,8 @@ const NetworksForm = ({ const previousChainId = usePrevious(chainId); const previousTicker = usePrevious(ticker); const previousBlockExplorerUrl = usePrevious(blockExplorerUrl); + const previousNetworkName = usePrevious(networkName); + useEffect(() => { if (viewOnly) { return; @@ -660,7 +806,8 @@ const NetworksForm = ({ previousRpcUrl === rpcUrl && previousChainId === chainId && previousTicker === ticker && - previousBlockExplorerUrl === blockExplorerUrl + previousBlockExplorerUrl === blockExplorerUrl && + previousNetworkName === networkName ) { return; } @@ -670,7 +817,7 @@ const NetworksForm = ({ const tickerWarning = await validateTickerSymbol(chainId, ticker); const nameWarning = await validateNetworkName(chainId, networkName); const blockExplorerError = validateBlockExplorerURL(blockExplorerUrl); - const rpcUrlError = validateRPCUrl(rpcUrl); + const rpcUrlError = await validateRPCUrl(rpcUrl, chainId); setErrors({ ...errors, @@ -702,6 +849,7 @@ const NetworksForm = ({ previousChainId, previousTicker, previousBlockExplorerUrl, + previousNetworkName, validateBlockExplorerURL, validateChainId, validateTickerSymbol, @@ -839,6 +987,23 @@ const NetworksForm = ({ }), ); }; + + const isPopularNetwork = Object.values(FEATURED_RPCS).some( + (network) => + getDisplayChainId(chainId) === + parseInt(network.chainId, 16).toString(10) && rpcUrl === network.rpcUrl, + ); + + const isDefaultMainnet = + getDisplayChainId(chainId) === + parseInt(CHAIN_IDS.MAINNET, 16).toString(10) && + rpcUrl === CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.MAINNET]; + + const isDefaultLineaMainnet = + getDisplayChainId(chainId) === + parseInt(CHAIN_IDS.LINEA_MAINNET, 16).toString(10) && + rpcUrl === CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_MAINNET]; + const deletable = !isCurrentRpcTarget && !viewOnly && !addNewNetwork; const stateUnchanged = stateIsUnchanged(); const chainIdErrorOnFeaturedRpcDuringEdit = @@ -885,16 +1050,33 @@ const NetworksForm = ({ > { setIsEditing(true); setNetworkName(value); }} titleText={t('networkName')} value={networkName} - disabled={viewOnly} + disabled={viewOnly || isDefaultMainnet || isDefaultLineaMainnet} dataTestId="network-form-network-name" /> + {errors.networkName?.msg ? ( + + {errors.networkName.msg} + + ) : null} + {warnings.networkName?.msg ? ( + + {warnings.networkName.msg} + + ) : null} {suggestedNames && suggestedNames.length > 0 && !suggestedNames.some( @@ -925,7 +1107,7 @@ const NetworksForm = ({ ))} ) : null} - {networkMenuRedesign ? ( + {/* {networkMenuRedesign ? ( ) : ( - )} + )} */} + { + setIsEditing(true); + setRpcUrl(value); + }} + titleText={t('rpcUrl')} + value={displayRpcUrl} + disabled={viewOnly || isDefaultMainnet || isDefaultLineaMainnet} + dataTestId="network-form-rpc-url" + /> + {errors.rpcUrl?.msg ? ( + + {errors.rpcUrl.msg} + + ) : null} + { setIsEditing(true); setChainId(value); autoSuggestTicker(value); + autoSuggestName(value); }} titleText={t('chainId')} value={chainId} - disabled={viewOnly} + disabled={ + viewOnly || + isDefaultMainnet || + isDefaultLineaMainnet || + isPopularNetwork + } tooltipText={viewOnly ? null : t('networkSettingsChainIdDescription')} dataTestId="network-form-chain-id" /> + + {warnings.chainId?.msg ? ( + + {warnings.chainId?.msg} + + ) : null} + {errors.chainId?.msg ? ( + + {errors.chainId.msg} + + ) : null} + {errors.chainId?.key === 'endpointReturnedDifferentChainId' ? ( + + + {t('wrongChainId')} + + + {t('findTheRightChainId')}{' '} + ({})} + href={CHAIN_LIST_URL} + endIconName={IconName.Export} + endIconProps={{ + size: IconSize.Xs, + }} + > + chainid.network + + + + ) : null} + {errors.chainId?.key === 'existingChainId' ? ( + + + {t('existingChainId')} + + + {t('updateOrEditNetworkInformations')}{' '} + + {t('editNetworkLink')} + + + + ) : null} {warnings.ticker?.msg ? ( ) : null} { setIsEditing(true); setBlockExplorerUrl(value); @@ -1025,10 +1306,19 @@ const NetworksForm = ({ titleText={t('blockExplorerUrl')} titleUnit={t('optionalWithParanthesis')} value={blockExplorerUrl} - disabled={viewOnly} + disabled={viewOnly || isDefaultMainnet || isDefaultLineaMainnet} autoFocus={window.location.hash.split('#')[2] === 'blockExplorerUrl'} dataTestId="network-form-block-explorer-url" /> + {errors.blockExplorerUrl?.msg ? ( + + {errors.blockExplorerUrl.msg} + + ) : null} {networkMenuRedesign ? ( @@ -1050,7 +1340,7 @@ const NetworksForm = ({ width={BlockSize.Full} alignItems={AlignItems.center} > - {t('save')} + {addNewNetwork ? t('next') : t('save')} ) : ( @@ -1100,6 +1390,7 @@ NetworksForm.propTypes = { submitCallback: PropTypes.func, restrictHeight: PropTypes.bool, setActiveOnSubmit: PropTypes.bool, + getOnEditCallback: PropTypes.func, }; NetworksForm.defaultProps = { diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.test.js b/ui/pages/settings/networks-tab/networks-form/networks-form.test.js index e4b338a8a580..0d1cc47edf39 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.test.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.test.js @@ -13,11 +13,21 @@ import NetworksForm from '.'; const renderComponent = (props) => { const store = configureMockStore([])({ - metamask: { useSafeChainsListValidation: true }, + metamask: { + useSafeChainsListValidation: true, + orderedNetworkList: { + networkId: '0x1', + networkRpcUrl: 'https://mainnet.infura.io/v3/', + }, + }, }); return renderWithProvider(, store); }; +jest.mock('../../../../helpers/utils/feature-flags', () => ({ + getLocalNetworkMenuRedesignFeatureFlag: jest.fn(() => false), +})); + const defaultNetworks = defaultNetworksData.map((network) => ({ ...network, viewOnly: true, @@ -43,11 +53,6 @@ const propNetworkDisplay = { addNewNetwork: false, }; -jest.mock('../../../../helpers/utils/feature-flags', () => ({ - ...jest.requireActual('../../../../helpers/utils/feature-flags'), - getLocalNetworkMenuRedesignFeatureFlag: () => false, -})); - describe('NetworkForm Component', () => { beforeAll(() => { nock.disableNetConnect(); @@ -173,6 +178,9 @@ describe('NetworkForm Component', () => { await fireEvent.change(rpcUrlField, { target: { value: 'test' }, }); + await fireEvent.change(screen.getByRole('textbox', { name: 'Chain ID' }), { + target: { value: '1' }, + }); expect( await screen.findByText( 'URLs require the appropriate HTTP/HTTPS prefix.', @@ -248,14 +256,13 @@ describe('NetworkForm Component', () => { target: { value: 'https://bsc-dataseed.binance.org/' }, }); - const expectedWarning = - 'The RPC URL you have entered returned a different chain ID (56). Please update the Chain ID to match the RPC URL of the network you are trying to add.'; + const expectedWarning = 'This chain ID doesn’t match the network name.'; expect(await screen.findByText(expectedWarning)).toBeInTheDocument(); expect(screen.getByText('Save')).toBeDisabled(); fireEvent.change(chainIdField, { - target: { value: 'a' }, + target: { value: 'z' }, }); expect( diff --git a/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js b/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js index 17c87e63262d..c2be47048c9b 100644 --- a/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js +++ b/ui/pages/settings/networks-tab/networks-tab-content/networks-tab-content.test.js @@ -15,9 +15,17 @@ const mockState = { ticker: 'ETH', type: 'localhost', }, + orderedNetworkList: { + chainId: '0x539', + rpcUrl: 'http://localhost:8545', + }, }, }; +jest.mock('../../../../helpers/utils/feature-flags', () => ({ + getLocalNetworkMenuRedesignFeatureFlag: jest.fn(() => false), +})); + const renderComponent = (props) => { const store = configureMockStore([])(mockState); return renderWithProvider(, store); diff --git a/ui/pages/settings/networks-tab/networks-tab.test.js b/ui/pages/settings/networks-tab/networks-tab.test.js index cead29634a70..eba3d07524ae 100644 --- a/ui/pages/settings/networks-tab/networks-tab.test.js +++ b/ui/pages/settings/networks-tab/networks-tab.test.js @@ -14,6 +14,10 @@ const mockState = { type: 'localhost', }, networkConfigurations: {}, + orderedNetworkList: { + chainId: '0x539', + rpcUrl: 'http://localhost:8545', + }, }, appState: { networksTabSelectedRpcUrl: 'http://localhost:8545', diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 73f4af5637d7..c606e88323db 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -64,6 +64,8 @@ import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP, ALLOWED_PROD_SWAPS_CHAIN_IDS, ALLOWED_DEV_SWAPS_CHAIN_IDS, + MAINNET_DEFAULT_BLOCK_EXPLORER_URL, + LINEA_DEFAULT_BLOCK_EXPLORER_URL, } from '../../shared/constants/swaps'; import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../shared/constants/bridge'; @@ -653,6 +655,7 @@ export const getNonTestNetworks = createDeepEqualSelector( ticker: CURRENCY_SYMBOLS.ETH, id: NETWORK_TYPES.MAINNET, removable: false, + blockExplorerUrl: MAINNET_DEFAULT_BLOCK_EXPLORER_URL, }, { chainId: CHAIN_IDS.LINEA_MAINNET, @@ -665,6 +668,7 @@ export const getNonTestNetworks = createDeepEqualSelector( ticker: CURRENCY_SYMBOLS.ETH, id: NETWORK_TYPES.LINEA_MAINNET, removable: false, + blockExplorerUrl: LINEA_DEFAULT_BLOCK_EXPLORER_URL, }, // Custom networks added by the user ...Object.values(networkConfigurations)