diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index bb7176cbb..088fe8444 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2346,6 +2346,10 @@ "message": "Gas fees are $1 relative to the past 72 hours.", "description": "$1 is networks stability value - stable, low, high" }, + "networkSwitchConnectionError": { + "message": "We can't connect to $1", + "description": "$1 represents the network name" + }, "networkURL": { "message": "Network URL" }, diff --git a/ui/components/app/loading-network-screen/loading-network-screen.component.js b/ui/components/app/loading-network-screen/loading-network-screen.component.js index 72c4ea31e..23c1c309d 100644 --- a/ui/components/app/loading-network-screen/loading-network-screen.component.js +++ b/ui/components/app/loading-network-screen/loading-network-screen.component.js @@ -1,9 +1,24 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import Button from '../../ui/button'; import LoadingScreen from '../../ui/loading-screen'; import { SECOND } from '../../../../shared/constants/time'; import { NETWORK_TYPES } from '../../../../shared/constants/network'; +import Popover from '../../ui/popover/popover.component'; +import { + ButtonPrimary, + ButtonSecondary, + Icon, + IconName, + IconSize, + Text, +} from '../../component-library'; +import { + DISPLAY, + IconColor, + TextAlign, + TextVariant, +} from '../../../helpers/constants/design-system'; +import Box from '../../ui/box/box'; export default class LoadingNetworkScreen extends PureComponent { state = { @@ -56,63 +71,76 @@ export default class LoadingNetworkScreen extends PureComponent { } }; - renderDeprecatedRpcUrlWarning = () => { - const { showNetworkDropdown } = this.props; - - return ( -
- 😞 - {this.context.t('currentRpcUrlDeprecated')} -
- -
-
- ); - }; - - renderErrorScreenContent = () => { + renderConnectionFailureNotification = (message, showTryAgain = false) => { const { showNetworkDropdown, setProviderArgs, setProviderType } = this.props; return ( -
- 😞 - {this.context.t('somethingWentWrong')} -
- + + {showTryAgain ? ( + { + this.setState({ showErrorScreen: false }); + setProviderType(...setProviderArgs); + window.clearTimeout(this.cancelCallTimeout); + this.cancelCallTimeout = setTimeout( + this.cancelCall, + this.props.cancelTime || SECOND * 15, + ); + }} + variant={TextVariant.bodySm} + block + > + {this.context.t('tryAgain')} + + ) : null} + + + ); + }; - -
-
+ renderDeprecatedRpcUrlWarning = () => { + return this.renderConnectionFailureNotification( + this.context.t('currentRpcUrlDeprecated'), + false, + ); + }; + + renderErrorScreenContent = () => { + const { providerConfig } = this.props; + return this.renderConnectionFailureNotification( + this.context.t('networkSwitchConnectionError', [providerConfig.nickname]), + true, ); }; 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 badf2b723..e8faf891e 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 @@ -1,7 +1,11 @@ import { connect } from 'react-redux'; import { NETWORK_TYPES } from '../../../../shared/constants/network'; import * as actions from '../../../store/actions'; -import { getNetworkIdentifier, isNetworkLoading } from '../../../selectors'; +import { + getAllEnabledNetworks, + getNetworkIdentifier, + isNetworkLoading, +} from '../../../selectors'; import { getProviderConfig } from '../../../ducks/metamask/metamask'; import LoadingNetworkScreen from './loading-network-screen.component'; @@ -21,11 +25,27 @@ const mapStateToProps = (state) => { const isInfuraRpcUrl = rpcUrl && new URL(rpcUrl).host.endsWith('.infura.io'); const showDeprecatedRpcUrlWarning = isDeprecatedNetwork && isInfuraRpcUrl; + // Ensure we have a nickname to provide the user + // in case of connection error + let networkName = nickname; + if (networkName === undefined) { + const networks = getAllEnabledNetworks(state); + const desiredNetwork = networks.find( + (network) => network.chainId === chainId, + ); + if (desiredNetwork) { + networkName = desiredNetwork.nickname; + } + } + return { isNetworkLoading: isNetworkLoading(state), loadingMessage, setProviderArgs, - providerConfig, + providerConfig: { + ...providerConfig, + nickname: networkName, + }, providerId: getNetworkIdentifier(state), showDeprecatedRpcUrlWarning, }; @@ -38,7 +58,12 @@ const mapDispatchToProps = (dispatch) => { }, rollbackToPreviousProvider: () => dispatch(actions.rollbackToPreviousProvider()), - showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), + showNetworkDropdown: () => { + if (process.env.MULTICHAIN) { + return dispatch(actions.toggleNetworkMenu()); + } + return dispatch(actions.showNetworkDropdown()); + }, }; }; 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 bcde1fcc9..460f7ce1d 100644 --- a/ui/components/multichain/network-list-menu/network-list-menu.js +++ b/ui/components/multichain/network-list-menu/network-list-menu.js @@ -11,12 +11,14 @@ import { setShowTestNetworks, setProviderType, toggleNetworkMenu, + upsertNetworkConfiguration, } from '../../../store/actions'; import { CHAIN_IDS, TEST_CHAINS } from '../../../../shared/constants/network'; import { getShowTestNetworks, getAllEnabledNetworks, getCurrentChainId, + getNetworkConfigurations, } from '../../../selectors'; import Box from '../../ui/box/box'; import ToggleButton from '../../ui/toggle-button'; @@ -32,6 +34,7 @@ import { MetaMetricsContext } from '../../../contexts/metametrics'; import { MetaMetricsEventCategory, MetaMetricsEventName, + MetaMetricsNetworkEventSource, } from '../../../../shared/constants/metametrics'; const UNREMOVABLE_CHAIN_IDS = [CHAIN_IDS.MAINNET, ...TEST_CHAINS]; @@ -40,6 +43,7 @@ export const NetworkListMenu = ({ onClose }) => { const t = useI18nContext(); const networks = useSelector(getAllEnabledNetworks); const showTestNetworks = useSelector(getShowTestNetworks); + const networkConfigurations = useSelector(getNetworkConfigurations); const currentChainId = useSelector(getCurrentChainId); const dispatch = useDispatch(); const history = useHistory(); @@ -75,12 +79,34 @@ export const NetworkListMenu = ({ onClose }) => { iconSrc={network?.rpcPrefs?.imageUrl} key={network.id || network.chainId} selected={isCurrentNetwork} - onClick={() => { + onClick={async () => { dispatch(toggleNetworkMenu()); if (network.providerType) { dispatch(setProviderType(network.providerType)); } else { - dispatch(setActiveNetwork(network.id)); + // Linea needs to be added as a custom network because + // it is not yet supported by Infura. The following lazily + // adds Linea to the custom network configurations object + let networkId = network.id; + if (network.chainId === CHAIN_IDS.LINEA_TESTNET) { + const lineaNetworkConfiguration = Object.values( + networkConfigurations, + ).find( + ({ chainId }) => chainId === CHAIN_IDS.LINEA_TESTNET, + ); + if (lineaNetworkConfiguration) { + networkId = lineaNetworkConfiguration.id; + } else { + networkId = await dispatch( + upsertNetworkConfiguration(network, { + setActive: true, + source: + MetaMetricsNetworkEventSource.CustomNetworkForm, + }), + ); + } + } + dispatch(setActiveNetwork(networkId)); } trackEvent({ event: MetaMetricsEventName.NavNetworkSwitched, diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index e8c54687d..de7a4b8dc 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -29,6 +29,8 @@ import { GOERLI_DISPLAY_NAME, ETH_TOKEN_IMAGE_URL, LINEA_TESTNET_DISPLAY_NAME, + CURRENCY_SYMBOLS, + TEST_NETWORK_TICKER_MAP, } from '../../shared/constants/network'; import { WebHIDConnectedStatuses, @@ -1180,13 +1182,18 @@ export function getAllNetworks(state) { imageUrl: ETH_TOKEN_IMAGE_URL, }, providerType: NETWORK_TYPES.MAINNET, + ticker: CURRENCY_SYMBOLS.ETH, }); // Custom networks added networks.push( ...Object.entries(networkConfigurations) .filter( ([, network]) => - !localhostFilter(network) && network.chainId !== CHAIN_IDS.MAINNET, + !localhostFilter(network) && + network.chainId !== CHAIN_IDS.MAINNET && + // Linea gets added as a custom network configuration so + // we must ignore it here to display in test networks + network.chainId !== CHAIN_IDS.LINEA_TESTNET, ) .map(([, network]) => network), ); @@ -1198,18 +1205,20 @@ export function getAllNetworks(state) { nickname: GOERLI_DISPLAY_NAME, rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.GOERLI], providerType: NETWORK_TYPES.GOERLI, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], }, { chainId: CHAIN_IDS.SEPOLIA, nickname: SEPOLIA_DISPLAY_NAME, rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.SEPOLIA], providerType: NETWORK_TYPES.SEPOLIA, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA], }, { chainId: CHAIN_IDS.LINEA_TESTNET, nickname: LINEA_TESTNET_DISPLAY_NAME, rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_TESTNET], - provderType: NETWORK_TYPES.LINEA_TESTNET, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_TESTNET], }, ], // Localhosts ...Object.entries(networkConfigurations)