routes.component.tsx
587 lines
/* eslint-disable jsdoc/check-tag-names */
/* eslint-disable import/no-useless-path-segments */
/* eslint-disable import/extensions */
import classnames from 'classnames';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import React, { Suspense, useCallback, useEffect, useRef } from 'react';
import React, { Component, Suspense } from 'react';
import { useDispatch } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
import { Route, Switch, useHistory, useLocation } from 'react-router-dom';
import IdleTimer from 'react-idle-timer';
import IdleTimer from 'react-idle-timer';
import type { ApprovalType } from '@metamask/controller-utils';
import { useAppSelector } from '../../store/store';
import Authenticated from '../../helpers/higher-order-components/authenticated';
import Authenticated from '../../helpers/higher-order-components/authenticated';
import Initialized from '../../helpers/higher-order-components/initialized';
import Initialized from '../../helpers/higher-order-components/initialized';
import PermissionsConnect from '../permissions-connect';
import PermissionsConnect from '../permissions-connect';
import Loading from '../../components/ui/loading-screen';
import Loading from '../../components/ui/loading-screen';
import LoadingNetwork from '../../components/app/loading-network-screen';
import LoadingNetwork from '../../components/app/loading-network-screen';
import { Modal } from '../../components/app/modals';
import { Modal } from '../../components/app/modals';
import Alert from '../../components/ui/alert';
import Alert from '../../components/ui/alert';
import {
import {
AppHeader,
AppHeader,
AccountListMenu,
AccountListMenu,
NetworkListMenu,
NetworkListMenu,
AccountDetails,
AccountDetails,
ImportNftsModal,
ImportNftsModal,
ImportTokensModal,
ImportTokensModal,
} from '../../components/multichain';
} from '../../components/multichain';
import Alerts from '../../components/app/alerts';
import Alerts from '../../components/app/alerts';
import {
import {
ASSET_ROUTE,
ASSET_ROUTE,
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
CONFIRM_ADD_SUGGESTED_NFT_ROUTE,
CONFIRM_ADD_SUGGESTED_NFT_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
CONNECT_ROUTE,
CONNECT_ROUTE,
DEFAULT_ROUTE,
DEFAULT_ROUTE,
LOCK_ROUTE,
LOCK_ROUTE,
NEW_ACCOUNT_ROUTE,
NEW_ACCOUNT_ROUTE,
RESTORE_VAULT_ROUTE,
RESTORE_VAULT_ROUTE,
REVEAL_SEED_ROUTE,
REVEAL_SEED_ROUTE,
SEND_ROUTE,
SEND_ROUTE,
SWAPS_ROUTE,
SWAPS_ROUTE,
SETTINGS_ROUTE,
SETTINGS_ROUTE,
UNLOCK_ROUTE,
UNLOCK_ROUTE,
CONFIRMATION_V_NEXT_ROUTE,
CONFIRMATION_V_NEXT_ROUTE,
ONBOARDING_ROUTE,
ONBOARDING_ROUTE,
CONNECTIONS,
CONNECTIONS,
PERMISSIONS,
PERMISSIONS,
REVIEW_PERMISSIONS,
REVIEW_PERMISSIONS,
SNAPS_ROUTE,
SNAPS_ROUTE,
SNAPS_VIEW_ROUTE,
SNAPS_VIEW_ROUTE,
NOTIFICATIONS_ROUTE,
NOTIFICATIONS_ROUTE,
NOTIFICATIONS_SETTINGS_ROUTE,
NOTIFICATIONS_SETTINGS_ROUTE,
CROSS_CHAIN_SWAP_ROUTE,
CROSS_CHAIN_SWAP_ROUTE,
CROSS_CHAIN_SWAP_TX_DETAILS_ROUTE,
CROSS_CHAIN_SWAP_TX_DETAILS_ROUTE,
IMPORT_SRP_ROUTE,
IMPORT_SRP_ROUTE,
DEFI_ROUTE,
DEFI_ROUTE,
DEEP_LINK_ROUTE,
DEEP_LINK_ROUTE,
SMART_ACCOUNT_UPDATE,
SMART_ACCOUNT_UPDATE,
WALLET_DETAILS_ROUTE,
WALLET_DETAILS_ROUTE,
ACCOUNT_DETAILS_ROUTE,
ACCOUNT_DETAILS_ROUTE,
ACCOUNT_DETAILS_QR_CODE_ROUTE,
ACCOUNT_DETAILS_QR_CODE_ROUTE,
ACCOUNT_LIST_PAGE_ROUTE,
} from '../../helpers/constants/routes';
} from '../../helpers/constants/routes';
import {
getProviderConfig,
isNetworkLoading as getIsNetworkLoading,
} from '../../../shared/modules/selectors/networks';
import {
getNetworkIdentifier,
getPreferences,
getTheme,
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
getUnapprovedConfirmations,
///: END:ONLY_INCLUDE_IF
getShowExtensionInFullSizeView,
getNetworkToAutomaticallySwitchTo,
getNumberOfAllUnapprovedTransactionsAndMessages,
getSelectedInternalAccount,
oldestPendingConfirmationSelector,
getUnapprovedTransactions,
getPendingApprovals,
getIsMultichainAccountsState1Enabled,
} from '../../selectors';
import {
hideImportNftsModal,
hideIpfsModal,
setCurrentCurrency,
setLastActiveTime,
toggleAccountMenu,
toggleNetworkMenu,
hideImportTokensModal,
hideDeprecatedNetworkModal,
automaticallySwitchNetwork,
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
hideKeyringRemovalResultModal,
///: END:ONLY_INCLUDE_IF
setEditedNetwork,
} from '../../store/actions';
import { pageChanged } from '../../ducks/history/history';
import {
getCompletedOnboarding,
getIsUnlocked,
} from '../../ducks/metamask/metamask';
import { useI18nContext } from '../../hooks/useI18nContext';
import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../shared/constants/preferences';
import { getShouldShowSeedPhraseReminder } from '../../selectors/multi-srp/multi-srp';
import {
import {
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_POPUP,
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES,
SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES,
///: END:ONLY_INCLUDE_IF
///: END:ONLY_INCLUDE_IF
} from '../../../shared/constants/app';
} from '../../../shared/constants/app';
// TODO: Remove restricted import
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
// eslint-disable-next-line import/no-restricted-paths
import { getEnvironmentType } from '../../../app/scripts/lib/util';
import { getEnvironmentType } from '../../../app/scripts/lib/util';
import QRHardwarePopover from '../../components/app/qr-hardware-popover';
import QRHardwarePopover from '../../components/app/qr-hardware-popover';
import DeprecatedNetworks from '../../components/ui/deprecated-networks/deprecated-networks';
import DeprecatedNetworks from '../../components/ui/deprecated-networks/deprecated-networks';
import { Box } from '../../components/component-library';
import { Box } from '../../components/component-library';
import { ToggleIpfsModal } from '../../components/app/assets/nfts/nft-default-image/toggle-ipfs-modal';
import { ToggleIpfsModal } from '../../components/app/assets/nfts/nft-default-image/toggle-ipfs-modal';
import { BasicConfigurationModal } from '../../components/app/basic-configuration-modal';
import { BasicConfigurationModal } from '../../components/app/basic-configuration-modal';
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
import KeyringSnapRemovalResult from '../../components/app/modals/keyring-snap-removal-modal';
import KeyringSnapRemovalResult from '../../components/app/modals/keyring-snap-removal-modal';
///: END:ONLY_INCLUDE_IF
///: END:ONLY_INCLUDE_IF
import { MultichainAccountListMenu } from '../../components/multichain-accounts/multichain-account-list-menu';
import { DeprecatedNetworkModal } from '../settings/deprecated-network-modal/DeprecatedNetworkModal';
import { DeprecatedNetworkModal } from '../settings/deprecated-network-modal/DeprecatedNetworkModal';
import { MultichainMetaFoxLogo } from '../../components/multichain/app-header/multichain-meta-fox-logo';
import { MultichainMetaFoxLogo } from '../../components/multichain/app-header/multichain-meta-fox-logo';
import NetworkConfirmationPopover from '../../components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover';
import NetworkConfirmationPopover from '../../components/multichain/network-list-menu/network-confirmation-popover/network-confirmation-popover';
import { ToastMaster } from '../../components/app/toast-master/toast-master';
import { ToastMaster } from '../../components/app/toast-master/toast-master';
import { mmLazy } from '../../helpers/utils/mm-lazy';
import { type DynamicImportType, mmLazy } from '../../helpers/utils/mm-lazy';
import CrossChainSwapTxDetails from '../bridge/transaction-details/transaction-details';
import CrossChainSwapTxDetails from '../bridge/transaction-details/transaction-details';
import {
import {
isCorrectDeveloperTransactionType,
isCorrectDeveloperTransactionType,
isCorrectSignatureApprovalType,
isCorrectSignatureApprovalType,
} from '../../../shared/lib/confirmation.utils';
} from '../../../shared/lib/confirmation.utils';
import { MultichainAccountListMenu } from '../../components/multichain-accounts/multichain-account-list-menu';
import { type Confirmation } from '../confirmations/types/confirm';
import { SmartAccountUpdate } from '../confirmations/components/confirm/smart-account-update';
import { SmartAccountUpdate } from '../confirmations/components/confirm/smart-account-update';
import { MultichainAccountDetails } from '../multichain-accounts/account-details';
import { MultichainAccountDetails } from '../multichain-accounts/account-details';
import { AddressQRCode } from '../multichain-accounts/address-qr-code';
import { AddressQRCode } from '../multichain-accounts/address-qr-code';
import { AccountList } from '../multichain-accounts/account-list';
import {
import {
getConnectingLabel,
getConnectingLabel,
hideAppHeader,
isConfirmTransactionRoute,
isConfirmTransactionRoute,
setTheme,
setTheme,
showAppHeader,
showAppHeader,
} from './utils';
} from './utils';
// TODO: Fix `as unknown as` casting once `mmLazy` is updated to handle named exports, wrapped components, and other React module types.
// Casting is preferable over `@ts-expect-error` annotations in this case,
// because it doesn't suppress competing error messages e.g. "Cannot find module..."
// Begin Lazy Routes
// Begin Lazy Routes
const OnboardingFlow = mmLazy(
const OnboardingFlow = mmLazy(
() => import('../onboarding-flow/onboarding-flow'),
(() =>
import(
'../onboarding-flow/onboarding-flow.js'
)) as unknown as DynamicImportType,
);
);
const Lock = mmLazy(() => import('../lock'));
const Lock = mmLazy(
const UnlockPage = mmLazy(() => import('../unlock-page'));
(() => import('../lock/index.js')) as unknown as DynamicImportType,
const RestoreVaultPage = mmLazy(() => import('../keychains/restore-vault'));
);
const ImportSrpPage = mmLazy(() => import('../multi-srp/import-srp'));
const UnlockPage = mmLazy(
const RevealSeedConfirmation = mmLazy(() => import('../keychains/reveal-seed'));
(() => import('../unlock-page/index.js')) as unknown as DynamicImportType,
const Settings = mmLazy(() => import('../settings'));
);
const NotificationsSettings = mmLazy(() => import('../notifications-settings'));
const RestoreVaultPage = mmLazy(
const NotificationDetails = mmLazy(() => import('../notification-details'));
(() =>
const Notifications = mmLazy(() => import('../notifications'));
import('../keychains/restore-vault.js')) as unknown as DynamicImportType,
const SnapList = mmLazy(() => import('../snaps/snaps-list'));
);
const SnapView = mmLazy(() => import('../snaps/snap-view'));
const ImportSrpPage = mmLazy(
// TODO: This is a named export. Fix incorrect type casting once `mmLazy` is updated to handle non-default export types.
(() =>
import('../multi-srp/import-srp/index.ts')) as unknown as DynamicImportType,
);
const RevealSeedConfirmation = mmLazy(
(() => import('../keychains/reveal-seed.js')) as unknown as DynamicImportType,
);
const Settings = mmLazy(
(() => import('../settings/index.js')) as unknown as DynamicImportType,
);
const NotificationsSettings = mmLazy(
(() =>
import(
'../notifications-settings/index.js'
)) as unknown as DynamicImportType,
);
const NotificationDetails = mmLazy(
(() =>
import('../notification-details/index.js')) as unknown as DynamicImportType,
);
const Notifications = mmLazy(
(() => import('../notifications/index.js')) as unknown as DynamicImportType,
);
const SnapList = mmLazy(
(() =>
import('../snaps/snaps-list/index.js')) as unknown as DynamicImportType,
);
const SnapView = mmLazy(
(() => import('../snaps/snap-view/index.js')) as unknown as DynamicImportType,
);
const ConfirmTransaction = mmLazy(
const ConfirmTransaction = mmLazy(
() => import('../confirmations/confirm-transaction'),
(() =>
import(
'../confirmations/confirm-transaction/index.js'
)) as unknown as DynamicImportType,
);
);
const SendPage = mmLazy(() => import('../../components/multichain/pages/send'));
const SendPage = mmLazy(
const Swaps = mmLazy(() => import('../swaps'));
// TODO: This is a named export. Fix incorrect type casting once `mmLazy` is updated to handle non-default export types.
const CrossChainSwap = mmLazy(() => import('../bridge'));
(() =>
import(
'../../components/multichain/pages/send/index.js'
)) as unknown as DynamicImportType,
);
const Swaps = mmLazy(
(() => import('../swaps/index.js')) as unknown as DynamicImportType,
);
const CrossChainSwap = mmLazy(
(() => import('../bridge/index.tsx')) as unknown as DynamicImportType,
);
const ConfirmAddSuggestedTokenPage = mmLazy(
const ConfirmAddSuggestedTokenPage = mmLazy(
() => import('../confirm-add-suggested-token'),
(() =>
import(
'../confirm-add-suggested-token/index.js'
)) as unknown as DynamicImportType,
);
);
const ConfirmAddSuggestedNftPage = mmLazy(
const ConfirmAddSuggestedNftPage = mmLazy(
() => import('../confirm-add-suggested-nft'),
(() =>
import(
'../confirm-add-suggested-nft/index.js'
)) as unknown as DynamicImportType,
);
);
const ConfirmationPage = mmLazy(() => import('../confirmations/confirmation'));
const ConfirmationPage = mmLazy(
(() =>
import(
'../confirmations/confirmation/index.js'
)) as unknown as DynamicImportType,
);
const CreateAccountPage = mmLazy(
const CreateAccountPage = mmLazy(
() => import('../create-account/create-account.component'),
(() =>
import(
'../create-account/create-account.component.js'
)) as unknown as DynamicImportType,
);
);
const NftFullImage = mmLazy(
const NftFullImage = mmLazy(
() => import('../../components/app/assets/nfts/nft-details/nft-full-image'),
(() =>
import(
'../../components/app/assets/nfts/nft-details/nft-full-image.tsx'
)) as unknown as DynamicImportType,
);
);
const Asset = mmLazy(() => import('../asset'));
const Asset = mmLazy(
const DeFiPage = mmLazy(() => import('../defi'));
(() => import('../asset/index.js')) as unknown as DynamicImportType,
);
const DeFiPage = mmLazy(
(() => import('../defi/index.ts')) as unknown as DynamicImportType,
);
const PermissionsPage = mmLazy(
const PermissionsPage = mmLazy(
() =>
// TODO: This is a named export. Fix incorrect type casting once `mmLazy` is updated to handle non-default export types.
(() =>
import(
import(
'../../components/multichain/pages/permissions-page/permissions-page'
'../../components/multichain/pages/permissions-page/permissions-page.js'
),
)) as unknown as DynamicImportType,
);
);
const Connections = mmLazy(
const Connections = mmLazy(
() => import('../../components/multichain/pages/connections'),
// TODO: This is a named export. Fix incorrect type casting once `mmLazy` is updated to handle non-default export types.
(() =>
import(
'../../components/multichain/pages/connections/index.js'
)) as unknown as DynamicImportType,
);
);
const ReviewPermissions = mmLazy(
const ReviewPermissions = mmLazy(
() =>
// TODO: This is a named export. Fix incorrect type casting once `mmLazy` is updated to handle non-default export types.
(() =>
import(
import(
'../../components/multichain/pages/review-permissions-page/review-permissions-page'
'../../components/multichain/pages/review-permissions-page/review-permissions-page.tsx'
),
)) as unknown as DynamicImportType,
);
);
const Home = mmLazy(() => import('../home'));
const DeepLink = mmLazy(() => import('../deep-link/deep-link'));
const Home = mmLazy(
(() => import('../home/index.js')) as unknown as DynamicImportType,
);
const DeepLink = mmLazy(
// TODO: This is a named export. Fix incorrect type casting once `mmLazy` is updated to handle non-default export types.
(() => import('../deep-link/deep-link.tsx')) as unknown as DynamicImportType,
);
const WalletDetails = mmLazy(
const WalletDetails = mmLazy(
() => import('../multichain-accounts/wallet-details'),
(() =>
import(
'../multichain-accounts/wallet-details/index.ts'
)) as unknown as DynamicImportType,
);
);
// End Lazy Routes
// End Lazy Routes
export default class Routes extends Component {
// eslint-disable-next-line @typescript-eslint/naming-convention
static propTypes = {
export default function Routes() {
currentCurrency: PropTypes.string,
const dispatch = useDispatch();
setCurrentCurrencyToUSD: PropTypes.func,
const history = useHistory();
isLoading: PropTypes.bool,
const location = useLocation();
loadingMessage: PropTypes.string,
alertMessage: PropTypes.string,
textDirection: PropTypes.string,
isNetworkLoading: PropTypes.bool,
alertOpen: PropTypes.bool,
isUnlocked: PropTypes.bool,
setLastActiveTime: PropTypes.func,
history: PropTypes.object,
location: PropTypes.object,
autoLockTimeLimit: PropTypes.number,
privacyMode: PropTypes.bool,
pageChanged: PropTypes.func.isRequired,
browserEnvironmentOs: PropTypes.string,
browserEnvironmentBrowser: PropTypes.string,
theme: PropTypes.string,
showExtensionInFullSizeView: PropTypes.bool,
shouldShowSeedPhraseReminder: PropTypes.bool,
forgottenPassword: PropTypes.bool,
completedOnboarding: PropTypes.bool,
isAccountMenuOpen: PropTypes.bool,
toggleAccountMenu: PropTypes.func,
isNetworkMenuOpen: PropTypes.bool,
networkMenuClose: PropTypes.func,
accountDetailsAddress: PropTypes.string,
isImportNftsModalOpen: PropTypes.bool.isRequired,
hideImportNftsModal: PropTypes.func.isRequired,
isIpfsModalOpen: PropTypes.bool.isRequired,
isBasicConfigurationModalOpen: PropTypes.bool.isRequired,
hideIpfsModal: PropTypes.func.isRequired,
isImportTokensModalOpen: PropTypes.bool.isRequired,
hideImportTokensModal: PropTypes.func.isRequired,
isDeprecatedNetworkModalOpen: PropTypes.bool.isRequired,
hideDeprecatedNetworkModal: PropTypes.func.isRequired,
networkToAutomaticallySwitchTo: PropTypes.object,
automaticallySwitchNetwork: PropTypes.func.isRequired,
totalUnapprovedConfirmationCount: PropTypes.number.isRequired,
currentExtensionPopupId: PropTypes.number,
oldestPendingApproval: PropTypes.object,
pendingApprovals: PropTypes.arrayOf(PropTypes.object).isRequired,
transactionsMetadata: PropTypes.object.isRequired,
isMultichainAccountsState1Enabled: PropTypes.bool.isRequired,
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
isShowKeyringSnapRemovalResultModal: PropTypes.bool.isRequired,
hideShowKeyringSnapRemovalResultModal: PropTypes.func.isRequired,
pendingConfirmations: PropTypes.array.isRequired,
///: END:ONLY_INCLUDE_IF
};
static contextTypes = {
const alertOpen = useAppSelector((state) => state.appState.alertOpen);
t: PropTypes.func,
const alertMessage = useAppSelector((state) => state.appState.alertMessage);
metricsEvent: PropTypes.func,
const isLoading = useAppSelector((state) => state.appState.isLoading);
};
const loadingMessage = useAppSelector(
(state) => state.appState.loadingMessage,
);
const { autoLockTimeLimit = DEFAULT_AUTO_LOCK_TIME_LIMIT, privacyMode } =
useAppSelector(getPreferences);
const completedOnboarding = useAppSelector(getCompletedOnboarding);
componentDidUpdate(prevProps) {
// If there is more than one connected account to activeTabOrigin,
const {
// *BUT* the current account is not one of them, show the banner
theme,
const account = useAppSelector(getSelectedInternalAccount);
networkToAutomaticallySwitchTo,
const isNetworkLoading = useAppSelector(getIsNetworkLoading);
totalUnapprovedConfirmationCount,
isUnlocked,
const networkToAutomaticallySwitchTo = useAppSelector(
currentExtensionPopupId,
getNetworkToAutomaticallySwitchTo,
} = this.props;
);
if (theme !== prevProps.theme) {
const oldestPendingApproval = useAppSelector(
setTheme(theme);
oldestPendingConfirmationSelector,
}
);
const pendingApprovals = useAppSelector(getPendingApprovals);
const transactionsMetadata = useAppSelector(getUnapprovedTransactions);
const shouldShowSeedPhraseReminder = useAppSelector((state) =>
getShouldShowSeedPhraseReminder(state, account),
);
const textDirection = useAppSelector((state) => state.metamask.textDirection);
const isUnlocked = useAppSelector(getIsUnlocked);
const currentCurrency = useAppSelector(
(state) => state.metamask.currentCurrency,
);
const os = useAppSelector((state) => state.metamask.browserEnvironment?.os);
const browser = useAppSelector(
(state) => state.metamask.browserEnvironment?.browser,
);
const providerId = useAppSelector(getNetworkIdentifier);
const { type: providerType } = useAppSelector(getProviderConfig);
const theme = useAppSelector(getTheme);
const showExtensionInFullSizeView = useAppSelector(
getShowExtensionInFullSizeView,
);
const forgottenPassword = useAppSelector(
(state) => state.metamask.forgottenPassword,
);
const isAccountMenuOpen = useAppSelector(
(state) => state.appState.isAccountMenuOpen,
);
const isNetworkMenuOpen = useAppSelector(
(state) => state.appState.isNetworkMenuOpen,
);
const isImportTokensModalOpen = useAppSelector(
(state) => state.appState.importTokensModalOpen,
);
const isBasicConfigurationModalOpen = useAppSelector(
(state) => state.appState.showBasicFunctionalityModal,
);
const isDeprecatedNetworkModalOpen = useAppSelector(
(state) => state.appState.deprecatedNetworkModalOpen,
);
const accountDetailsAddress = useAppSelector(
(state) => state.appState.accountDetailsAddress,
);
const isImportNftsModalOpen = useAppSelector(
(state) => state.appState.importNftsModal.open,
);
const isIpfsModalOpen = useAppSelector(
(state) => state.appState.showIpfsModalOpen,
);
const totalUnapprovedConfirmationCount = useAppSelector(
getNumberOfAllUnapprovedTransactionsAndMessages,
);
const currentExtensionPopupId = useAppSelector(
(state) => state.metamask.currentExtensionPopupId,
);
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
const isShowKeyringSnapRemovalResultModal = useAppSelector(
(state) => state.appState.showKeyringRemovalSnapModal,
);
const pendingConfirmations = useAppSelector(getUnapprovedConfirmations);
const hideShowKeyringSnapRemovalResultModal = () =>
dispatch(hideKeyringRemovalResultModal());
///: END:ONLY_INCLUDE_IF
const isMultichainAccountsState1Enabled = useAppSelector(
getIsMultichainAccountsState1Enabled,
);
const prevPropsRef = useRef({ isUnlocked, totalUnapprovedConfirmationCount });
useEffect(() => {
const prevProps = prevPropsRef.current;
// Automatically switch the network if the user
// Automatically switch the network if the user
// no longer has unapproved transactions and they
// no longer has unapproved transactions and they
// should be on a different network for the
// should be on a different network for the
// currently active tab's dapp
// currently active tab's dapp
if (
if (
networkToAutomaticallySwitchTo &&
networkToAutomaticallySwitchTo &&
totalUnapprovedConfirmationCount === 0 &&
totalUnapprovedConfirmationCount === 0 &&
(prevProps.totalUnapprovedConfirmationCount > 0 ||
(prevProps.totalUnapprovedConfirmationCount > 0 ||
(prevProps.isUnlocked === false && isUnlocked))
(prevProps.isUnlocked === false && isUnlocked))
) {
) {
this.props.automaticallySwitchNetwork(networkToAutomaticallySwitchTo);
dispatch(automaticallySwitchNetwork(networkToAutomaticallySwitchTo));
}
}
prevPropsRef.current = { isUnlocked, totalUnapprovedConfirmationCount };
}, [
networkToAutomaticallySwitchTo,
isUnlocked,
totalUnapprovedConfirmationCount,
dispatch,
]);
useEffect(() => {
// Terminate the popup when another popup is opened
// Terminate the popup when another popup is opened
// if the user is using RPC queueing
// if the user is using RPC queueing
if (
if (
currentExtensionPopupId !== undefined &&
currentExtensionPopupId !== undefined &&
'metamask' in global &&
typeof global.metamask === 'object' &&
global.metamask &&
'id' in global.metamask &&
global.metamask.id !== undefined &&
global.metamask.id !== undefined &&
currentExtensionPopupId !== global.metamask.id
currentExtensionPopupId !== global.metamask.id
) {
) {
window.close();
window.close();
}
}
}
}, [currentExtensionPopupId]);
UNSAFE_componentWillMount() {
const {
currentCurrency,
pageChanged,
setCurrentCurrencyToUSD,
history,
showExtensionInFullSizeView,
} = this.props;
useEffect(() => {
const windowType = getEnvironmentType();
const windowType = getEnvironmentType();
if (showExtensionInFullSizeView && windowType === ENVIRONMENT_TYPE_POPUP) {
const { openExtensionInBrowser } = globalThis.platform ?? {};
global.platform.openExtensionInBrowser();
if (
}
showExtensionInFullSizeView &&
windowType === ENVIRONMENT_TYPE_POPUP &&
if (!currentCurrency) {
openExtensionInBrowser
setCurrentCurrencyToUSD();
) {
openExtensionInBrowser();
}
}
}, [showExtensionInFullSizeView]);
history.listen((locationObj, action) => {
useEffect(() => {
const unlisten = history.listen((locationObj: Location, action: 'PUSH') => {
if (action === 'PUSH') {
if (action === 'PUSH') {
pageChanged(locationObj.pathname);
dispatch(pageChanged(locationObj.pathname));
}
}
});
});
setTheme(this.props.theme);
return () => {
}
unlisten();
};
}, [history, dispatch]);
renderRoutes() {
useEffect(() => {
const { autoLockTimeLimit, setLastActiveTime, forgottenPassword } =
setTheme(theme);
this.props;
}, [theme]);
useEffect(() => {
if (!currentCurrency) {
dispatch(setCurrentCurrency('usd'));
}
}, [currentCurrency, dispatch]);
const renderRoutes = useCallback(() => {
const RestoreVaultComponent = forgottenPassword ? Route : Initialized;
const RestoreVaultComponent = forgottenPassword ? Route : Initialized;
const routes = (
const routes = (
<Suspense fallback={null}>
<Suspense fallback={null}>
{/* since the loading time is less than 200ms, we decided not to show a spinner fallback or anything */}
{/* since the loading time is less than 200ms, we decided not to show a spinner fallback or anything */}
<Switch>
<Switch>
{/** @ts-expect-error TODO: Replace `component` prop with `element` once `react-router` is upgraded to v6 */}
<Route path={ONBOARDING_ROUTE} component={OnboardingFlow} />
<Route path={ONBOARDING_ROUTE} component={OnboardingFlow} />
{/** @ts-expect-error TODO: Replace `component` prop with `element` once `react-router` is upgraded to v6 */}
<Route path={LOCK_ROUTE} component={Lock} exact />
<Route path={LOCK_ROUTE} component={Lock} exact />
<Initialized path={UNLOCK_ROUTE} component={UnlockPage} exact />
<Initialized path={UNLOCK_ROUTE} component={UnlockPage} exact />
{/** @ts-expect-error TODO: Replace `component` prop with `element` once `react-router` is upgraded to v6 */}
<Route path={DEEP_LINK_ROUTE} component={DeepLink} />
<Route path={DEEP_LINK_ROUTE} component={DeepLink} />
<RestoreVaultComponent
<RestoreVaultComponent
path={RESTORE_VAULT_ROUTE}
path={RESTORE_VAULT_ROUTE}
component={RestoreVaultPage}
component={RestoreVaultPage}
exact
exact
/>
/>
<Authenticated
<Authenticated
path={SMART_ACCOUNT_UPDATE}
path={SMART_ACCOUNT_UPDATE}
component={SmartAccountUpdate}
component={SmartAccountUpdate}
/>
/>
<Authenticated
<Authenticated
// `:keyringId` is optional here, if not provided, this will fallback
// `:keyringId` is optional here, if not provided, this will fallback
// to the main seed phrase.
// to the main seed phrase.
path={`${REVEAL_SEED_ROUTE}/:keyringId?`}
path={`${REVEAL_SEED_ROUTE}/:keyringId?`}
component={RevealSeedConfirmation}
component={RevealSeedConfirmation}
/>
/>
<Authenticated path={IMPORT_SRP_ROUTE} component={ImportSrpPage} />
<Authenticated path={IMPORT_SRP_ROUTE} component={ImportSrpPage} />
<Authenticated path={SETTINGS_ROUTE} component={Settings} />
<Authenticated path={SETTINGS_ROUTE} component={Settings} />
<Authenticated
<Authenticated
path={NOTIFICATIONS_SETTINGS_ROUTE}
path={NOTIFICATIONS_SETTINGS_ROUTE}
component={NotificationsSettings}
component={NotificationsSettings}
/>
/>
<Authenticated
<Authenticated
path={`${NOTIFICATIONS_ROUTE}/:uuid`}
path={`${NOTIFICATIONS_ROUTE}/:uuid`}
component={NotificationDetails}
component={NotificationDetails}
/>
/>
<Authenticated path={NOTIFICATIONS_ROUTE} component={Notifications} />
<Authenticated path={NOTIFICATIONS_ROUTE} component={Notifications} />
<Authenticated exact path={SNAPS_ROUTE} component={SnapList} />
<Authenticated path={SNAPS_ROUTE} component={SnapList} exact />
<Authenticated path={SNAPS_VIEW_ROUTE} component={SnapView} />
<Authenticated path={SNAPS_VIEW_ROUTE} component={SnapView} />
<Authenticated
<Authenticated
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?`}
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?`}
component={ConfirmTransaction}
component={ConfirmTransaction}
/>
/>
<Authenticated path={SEND_ROUTE} component={SendPage} exact />
<Authenticated path={SEND_ROUTE} component={SendPage} exact />
<Authenticated path={SWAPS_ROUTE} component={Swaps} />
<Authenticated path={SWAPS_ROUTE} component={Swaps} />
<Authenticated
<Authenticated
path={`${CROSS_CHAIN_SWAP_TX_DETAILS_ROUTE}/:srcTxMetaId`}
path={`${CROSS_CHAIN_SWAP_TX_DETAILS_ROUTE}/:srcTxMetaId`}
component={CrossChainSwapTxDetails}
component={CrossChainSwapTxDetails}
exact
exact
/>
/>
<Authenticated
<Authenticated
path={CROSS_CHAIN_SWAP_ROUTE}
path={CROSS_CHAIN_SWAP_ROUTE}
component={CrossChainSwap}
component={CrossChainSwap}
/>
/>
<Authenticated
<Authenticated
path={CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE}
path={CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE}
component={ConfirmAddSuggestedTokenPage}
component={ConfirmAddSuggestedTokenPage}
exact
exact
/>
/>
<Authenticated
<Authenticated
path={CONFIRM_ADD_SUGGESTED_NFT_ROUTE}
path={CONFIRM_ADD_SUGGESTED_NFT_ROUTE}
component={ConfirmAddSuggestedNftPage}
component={ConfirmAddSuggestedNftPage}
exact
exact
/>
/>
<Authenticated
<Authenticated
path={`${CONFIRMATION_V_NEXT_ROUTE}/:id?`}
path={`${CONFIRMATION_V_NEXT_ROUTE}/:id?`}
component={ConfirmationPage}
component={ConfirmationPage}
/>
/>
<Authenticated
<Authenticated
path={NEW_ACCOUNT_ROUTE}
path={NEW_ACCOUNT_ROUTE}
component={CreateAccountPage}
component={CreateAccountPage}
/>
/>
<Authenticated
<Authenticated
path={`${CONNECT_ROUTE}/:id`}
path={`${CONNECT_ROUTE}/:id`}
component={PermissionsConnect}
component={PermissionsConnect}
/>
/>
<Authenticated
<Authenticated
path={`${ASSET_ROUTE}/image/:asset/:id`}
path={`${ASSET_ROUTE}/image/:asset/:id`}
component={NftFullImage}
component={NftFullImage}
/>
/>
<Authenticated
<Authenticated
path={`${ASSET_ROUTE}/:chainId/:asset/:id`}
path={`${ASSET_ROUTE}/:chainId/:asset/:id`}
component={Asset}
component={Asset}
/>
/>
<Authenticated
<Authenticated
path={`${ASSET_ROUTE}/:chainId/:asset/`}
path={`${ASSET_ROUTE}/:chainId/:asset/`}
component={Asset}
component={Asset}
/>
/>
<Authenticated path={`${ASSET_ROUTE}/:chainId`} component={Asset} />
<Authenticated path={`${ASSET_ROUTE}/:chainId`} component={Asset} />
<Authenticated
<Authenticated
path={`${DEFI_ROUTE}/:chainId/:protocolId`}
path={`${DEFI_ROUTE}/:chainId/:protocolId`}
component={DeFiPage}
component={DeFiPage}
/>
/>
<Authenticated
<Authenticated
path={`${CONNECTIONS}/:origin`}
path={`${CONNECTIONS}/:origin`}
component={Connections}
component={Connections}
/>
/>
<Authenticated path={PERMISSIONS} component={PermissionsPage} exact />
<Authenticated path={PERMISSIONS} component={PermissionsPage} exact />
<Authenticated
<Authenticated
path={`${REVIEW_PERMISSIONS}/:origin`}
path={`${REVIEW_PERMISSIONS}/:origin`}
component={ReviewPermissions}
component={ReviewPermissions}
exact
exact
/>
/>
<Authenticated
<Authenticated
path={ACCOUNT_LIST_PAGE_ROUTE}
component={AccountList}
exact
/>
<Authenticated
path={WALLET_DETAILS_ROUTE}
path={WALLET_DETAILS_ROUTE}
component={WalletDetails}
component={WalletDetails}
exact
exact
/>
/>
<Authenticated
<Authenticated
path={`${ACCOUNT_DETAILS_ROUTE}/:address`}
path={`${ACCOUNT_DETAILS_ROUTE}/:address`}
component={MultichainAccountDetails}
component={MultichainAccountDetails}
exact
exact
/>
/>
<Authenticated
<Authenticated
path={`${ACCOUNT_DETAILS_QR_CODE_ROUTE}/:address`}
path={`${ACCOUNT_DETAILS_QR_CODE_ROUTE}/:address`}
component={AddressQRCode}
component={AddressQRCode}
exact
exact
/>
/>
<Authenticated path={DEFAULT_ROUTE} component={Home} />
<Authenticated path={DEFAULT_ROUTE} component={Home} />
</Switch>
</Switch>
</Suspense>
</Suspense>
);
);
if (autoLockTimeLimit > 0) {
if (autoLockTimeLimit > 0) {
return (
return (
<IdleTimer onAction={setLastActiveTime} throttle={1000}>
<IdleTimer
onAction={() => dispatch(setLastActiveTime())}
throttle={1000}
>
{routes}
{routes}
</IdleTimer>
</IdleTimer>
);
);
}
}
return routes;
return routes;
}
}, [autoLockTimeLimit, forgottenPassword, dispatch]);
renderAccountDetails() {
const t = useI18nContext();
const { accountDetailsAddress, isMultichainAccountsState1Enabled } =
this.props;
const renderAccountDetails = () => {
if (!accountDetailsAddress || isMultichainAccountsState1Enabled) {
if (!accountDetailsAddress || isMultichainAccountsState1Enabled) {
return null;
return null;
}
}
return <AccountDetails address={accountDetailsAddress} />;
return <AccountDetails address={accountDetailsAddress} />;
}
};
render() {
const {
isLoading,
isUnlocked,
alertMessage,
textDirection,
loadingMessage,
isNetworkLoading,
browserEnvironmentOs: os,
browserEnvironmentBrowser: browser,
shouldShowSeedPhraseReminder,
completedOnboarding,
isAccountMenuOpen,
toggleAccountMenu,
isNetworkMenuOpen,
isImportTokensModalOpen,
isDeprecatedNetworkModalOpen,
location,
isImportNftsModalOpen,
hideImportNftsModal,
isIpfsModalOpen,
isBasicConfigurationModalOpen,
hideIpfsModal,
hideImportTokensModal,
hideDeprecatedNetworkModal,
networkMenuClose,
privacyMode,
oldestPendingApproval,
pendingApprovals,
transactionsMetadata,
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
isShowKeyringSnapRemovalResultModal,
hideShowKeyringSnapRemovalResultModal,
pendingConfirmations,
///: END:ONLY_INCLUDE_IF
} = this.props;
const loadMessage =
loadingMessage || isNetworkLoading
? getConnectingLabel(loadingMessage, this.props, this.context)
: null;
const windowType = getEnvironmentType();
const loadMessage =
loadingMessage || isNetworkLoading
? getConnectingLabel(loadingMessage, { providerType, providerId }, { t })
: null;
const shouldShowNetworkDeprecationWarning =
const windowType = getEnvironmentType();
windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
isUnlocked &&
!shouldShowSeedPhraseReminder;
const paramsConfirmationId = location.pathname.split(
const shouldShowNetworkDeprecationWarning =
'/confirm-transaction/',
windowType !== ENVIRONMENT_TYPE_NOTIFICATION &&
)[1];
isUnlocked &&
const confirmationId = paramsConfirmationId ?? oldestPendingApproval?.id;
!shouldShowSeedPhraseReminder;
const pendingApproval = pendingApprovals.find(
(approval) => approval.id === confirmationId,
);
const isCorrectApprovalType = isCorrectSignatureApprovalType(
pendingApproval?.type,
);
const isCorrectTransactionType = isCorrectDeveloperTransactionType(
transactionsMetadata[confirmationId]?.type,
);
const isShowingDeepLinkRoute = location.pathname === DEEP_LINK_ROUTE;
const paramsConfirmationId: string = location.pathname.split(
'/confirm-transaction/',
)[1];
const confirmationId = paramsConfirmationId ?? oldestPendingApproval?.id;
const pendingApproval = pendingApprovals.find(
(approval) => approval.id === confirmationId,
);
const isCorrectApprovalType = isCorrectSignatureApprovalType(
pendingApproval?.type as ApprovalType | undefined,
);
const isCorrectTransactionType = isCorrectDeveloperTransactionType(
transactionsMetadata[confirmationId]?.type,
);
let isLoadingShown =
const isShowingDeepLinkRoute = location.pathname === DEEP_LINK_ROUTE;
isLoading &&
completedOnboarding &&
// In the redesigned screens, we hide the general loading spinner and the
// loading states are on a component by component basis.
!isCorrectApprovalType &&
!isCorrectTransactionType &&
// We don't want to show the loading screen on the deep link route, as it
// is already a fullscreen interface.
!isShowingDeepLinkRoute;
const isLoadingShown =
isLoading &&
completedOnboarding &&
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
isLoadingShown =
!pendingConfirmations.some(
isLoading &&
(confirmation: Confirmation) =>
completedOnboarding &&
confirmation.type ===
!pendingConfirmations.some(
SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect,
(confirmation) =>
) &&
confirmation.type ===
SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES.showSnapAccountRedirect,
) &&
// In the redesigned screens, we hide the general loading spinner and the
// loading states are on a component by component basis.
!isCorrectApprovalType &&
!isCorrectTransactionType &&
// We don't want to show the loading spinner on the deep link route, as it
// is already a fullscreen interface.
!isShowingDeepLinkRoute;
///: END:ONLY_INCLUDE_IF
///: END:ONLY_INCLUDE_IF
// In the redesigned screens, we hide the general loading spinner and the
const accountListMenu = this.props.isMultichainAccountsState1Enabled ? (
// loading states are on a component by component basis.
<MultichainAccountListMenu
!isCorrectApprovalType &&
onClose={toggleAccountMenu}
!isCorrectTransactionType &&
privacyMode={privacyMode}
// We don't want to show the loading screen on the deep link route, as it
/>
// is already a fullscreen interface.
) : (
!isShowingDeepLinkRoute;
<AccountListMenu onClose={toggleAccountMenu} privacyMode={privacyMode} />
);
return (
const accountListMenu = isMultichainAccountsState1Enabled ? (
<div
<MultichainAccountListMenu
className={classnames('app', {
onClose={() => dispatch(toggleAccountMenu())}
[`os-${os}`]: os,
privacyMode={privacyMode}
[`browser-${browser}`]: browser,
/>
})}
) : (
dir={textDirection}
<AccountListMenu
>
onClose={() => dispatch(toggleAccountMenu())}
{shouldShowNetworkDeprecationWarning ? <DeprecatedNetworks /> : null}
privacyMode={privacyMode}
<QRHardwarePopover />
/>
<Modal />
);
<Alert visible={this.props.alertOpen} msg={alertMessage} />
{showAppHeader(this.props) && <AppHeader location={location} />}
{isConfirmTransactionRoute(this.pathname) && <MultichainMetaFoxLogo />}
{isAccountMenuOpen ? accountListMenu : null}
{isNetworkMenuOpen ? (
<NetworkListMenu onClose={networkMenuClose} />
) : null}
<NetworkConfirmationPopover />
{this.renderAccountDetails()}
{isImportNftsModalOpen ? (
<ImportNftsModal onClose={hideImportNftsModal} />
) : null}
{isIpfsModalOpen ? <ToggleIpfsModal onClose={hideIpfsModal} /> : null}
return (
{isBasicConfigurationModalOpen ? <BasicConfigurationModal /> : null}
<div
{isImportTokensModalOpen ? (
className={classnames('app', {
<ImportTokensModal onClose={hideImportTokensModal} />
[`os-${os}`]: Boolean(os),
) : null}
[`browser-${browser}`]: Boolean(browser),
{isDeprecatedNetworkModalOpen ? (
})}
<DeprecatedNetworkModal onClose={hideDeprecatedNetworkModal} />
dir={textDirection}
) : null}
>
{
{shouldShowNetworkDeprecationWarning ? <DeprecatedNetworks /> : null}
///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps)
<QRHardwarePopover />
isShowKeyringSnapRemovalResultModal && (
<Modal />
<KeyringSnapRemovalResult
<Alert visible={alertOpen} msg={alertMessage} />
isOpen={isShowKeyringSnapRemovalResultModal}
{process.env.REMOVE_GNS
onClose={hideShowKeyringSnapRemovalResultModal}
? showAppHeader({ location }) && <AppHeader location={location} />
/>
: !hideAppHeader({ location }) && <AppHeader location={location} />}
)
{isConfirmTransactionRoute(location.pathname) && (
///: END:ONLY_INCLUDE_IF
<MultichainMetaFoxLogo />
}
)}
<Box className="main-container-wrapper">
{isAccountMenuOpen ? accountListMenu : null}
{isLoadingShown ? <Loading loadingMessage={loadMessage} /> : null}
{isNetworkMenuOpen ? (
{!isLoading &&
<NetworkListMenu
isUnlocked &&
onClose={() => {
isNetworkLoading &&
dispatch(toggleNetworkMenu());
completedOnboarding &&
dispatch(setEditedNetwork());
!isShowingDeepLinkRoute ? (
}}
<LoadingNetwork />
/>
) : null}
) : null}
{this.renderRoutes()}
<NetworkConfirmationPopover />
</Box>
{renderAccountDetails()}
{isUnlocked ? <Alerts history={this.props.history} /> : null}
{isImportNftsModalOpen ? (
<ToastMaster />
<ImportNftsModal onClose={() => dispatch(hideImportNftsModal())} />
</div>
) : null}
);
}
}
{isIpfsModalOpen ? (
<ToggleIpfsModal onClose={() => dispatch(hideIpfsModal())} />
) : null}
{isBasicConfigurati