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)