diff --git a/ui/components/app/confirm/info/row/address.tsx b/ui/components/app/confirm/info/row/address.tsx
index 693dc41ea899..8165205ce9a9 100644
--- a/ui/components/app/confirm/info/row/address.tsx
+++ b/ui/components/app/confirm/info/row/address.tsx
@@ -1,5 +1,5 @@
import { NameType } from '@metamask/name-controller';
-import React, { useState } from 'react';
+import React, { memo, useState } from 'react';
import { useSelector } from 'react-redux';
import {
AlignItems,
@@ -24,55 +24,57 @@ export type ConfirmInfoRowAddressProps = {
isSnapUsingThis?: boolean;
};
-export const ConfirmInfoRowAddress = ({
- address,
- isSnapUsingThis,
-}: ConfirmInfoRowAddressProps) => {
- const isPetNamesEnabled = useSelector(getPetnamesEnabled);
- const { displayName, hexAddress } = useFallbackDisplayName(address);
- const [isNicknamePopoverShown, setIsNicknamePopoverShown] = useState(false);
- const handleDisplayNameClick = () => setIsNicknamePopoverShown(true);
- const onCloseHandler = () => setIsNicknamePopoverShown(false);
+export const ConfirmInfoRowAddress = memo(
+ ({ address, isSnapUsingThis }: ConfirmInfoRowAddressProps) => {
+ const isPetNamesEnabled = useSelector(getPetnamesEnabled);
+ const { displayName, hexAddress } = useFallbackDisplayName(address);
+ const [isNicknamePopoverShown, setIsNicknamePopoverShown] = useState(false);
+ const handleDisplayNameClick = () => setIsNicknamePopoverShown(true);
+ const onCloseHandler = () => setIsNicknamePopoverShown(false);
- return (
-
- {
- // PetNames on this component are disabled for snaps until the ``
- // component can support variations. See this comment for context: //
- // https://github.com/MetaMask/metamask-extension/pull/23487#discussion_r1525055546
- isPetNamesEnabled && !isSnapUsingThis ? (
-
- ) : (
- <>
-
-
-
+ {
+ // PetNames on this component are disabled for snaps until the ``
+ // component can support variations. See this comment for context: //
+ // https://github.com/MetaMask/metamask-extension/pull/23487#discussion_r1525055546
+ isPetNamesEnabled && !isSnapUsingThis ? (
+
+ ) : (
+ <>
+
- {displayName}
-
-
- {isNicknamePopoverShown ? (
-
- ) : null}
- >
- )
- }
-
- );
-};
+
+
+ {displayName}
+
+
+ {isNicknamePopoverShown ? (
+
+ ) : null}
+ >
+ )
+ }
+
+ );
+ },
+);
diff --git a/ui/components/app/name/name.tsx b/ui/components/app/name/name.tsx
index 3559181ae8ff..a5accdc5306e 100644
--- a/ui/components/app/name/name.tsx
+++ b/ui/components/app/name/name.tsx
@@ -1,4 +1,10 @@
-import React, { useCallback, useContext, useEffect, useState } from 'react';
+import React, {
+ memo,
+ useCallback,
+ useContext,
+ useEffect,
+ useState,
+} from 'react';
import { NameType } from '@metamask/name-controller';
import classnames from 'classnames';
import { toChecksumAddress } from 'ethereumjs-util';
@@ -44,81 +50,85 @@ function formatValue(value: string, type: NameType): string {
}
}
-export default function Name({
- value,
- type,
- disableEdit,
- internal,
- preferContractSymbol = false,
-}: NameProps) {
- const [modalOpen, setModalOpen] = useState(false);
- const trackEvent = useContext(MetaMetricsContext);
-
- const { name, hasPetname } = useDisplayName(
+const Name = memo(
+ ({
value,
type,
- preferContractSymbol,
- );
-
- useEffect(() => {
- if (internal) {
- return;
- }
-
- trackEvent({
- event: MetaMetricsEventName.PetnameDisplayed,
- category: MetaMetricsEventCategory.Petnames,
- properties: {
- petname_category: type,
- has_petname: Boolean(name?.length),
- },
- });
- }, []);
-
- const handleClick = useCallback(() => {
- setModalOpen(true);
- }, [setModalOpen]);
-
- const handleModalClose = useCallback(() => {
- setModalOpen(false);
- }, [setModalOpen]);
-
- const formattedValue = formatValue(value, type);
- const hasDisplayName = Boolean(name);
-
- return (
-
- {!disableEdit && modalOpen && (
-
- )}
-
- {hasDisplayName ? (
-
- ) : (
-
- )}
- {hasDisplayName ? (
-
- {name}
-
- ) : (
-
- {formattedValue}
-
+ disableEdit,
+ internal,
+ preferContractSymbol = false,
+ }: NameProps) => {
+ const [modalOpen, setModalOpen] = useState(false);
+ const trackEvent = useContext(MetaMetricsContext);
+
+ const { name, hasPetname } = useDisplayName(
+ value,
+ type,
+ preferContractSymbol,
+ );
+
+ useEffect(() => {
+ if (internal) {
+ return;
+ }
+
+ trackEvent({
+ event: MetaMetricsEventName.PetnameDisplayed,
+ category: MetaMetricsEventCategory.Petnames,
+ properties: {
+ petname_category: type,
+ has_petname: Boolean(name?.length),
+ },
+ });
+ }, []);
+
+ const handleClick = useCallback(() => {
+ setModalOpen(true);
+ }, [setModalOpen]);
+
+ const handleModalClose = useCallback(() => {
+ setModalOpen(false);
+ }, [setModalOpen]);
+
+ const formattedValue = formatValue(value, type);
+ const hasDisplayName = Boolean(name);
+
+ return (
+
+ {!disableEdit && modalOpen && (
+
)}
+
+ {hasDisplayName ? (
+
+ ) : (
+
+ )}
+ {hasDisplayName ? (
+
+ {name}
+
+ ) : (
+
+ {formattedValue}
+
+ )}
+
-
- );
-}
+ );
+ },
+);
+
+export default Name;
diff --git a/ui/hooks/useDisplayName.test.ts b/ui/hooks/useDisplayName.test.ts
index 771cb9773144..1e2e4ea19900 100644
--- a/ui/hooks/useDisplayName.test.ts
+++ b/ui/hooks/useDisplayName.test.ts
@@ -1,6 +1,6 @@
import { NameEntry, NameType } from '@metamask/name-controller';
import { NftContract } from '@metamask/assets-controllers';
-import { getMemoizedMetadataContracts } from '../selectors';
+import { getRemoteTokens } from '../selectors';
import { getNftContractsByAddressOnCurrentChain } from '../selectors/nft';
import { useDisplayName } from './useDisplayName';
import { useNames } from './useName';
@@ -21,7 +21,7 @@ jest.mock('./useFirstPartyContractName', () => ({
}));
jest.mock('../selectors', () => ({
- getMemoizedMetadataContracts: jest.fn(),
+ getRemoteTokens: jest.fn(),
getCurrentChainId: jest.fn(),
}));
@@ -55,9 +55,7 @@ const WATCHED_NFT_FOUND_RETURN_VALUE = {
describe('useDisplayName', () => {
const useNamesMock = jest.mocked(useNames);
- const getMemoizedMetadataContractsMock = jest.mocked(
- getMemoizedMetadataContracts,
- );
+ const getRemoteTokensMock = jest.mocked(getRemoteTokens);
const useFirstPartyContractNamesMock = jest.mocked(
useFirstPartyContractNames,
);
@@ -72,7 +70,7 @@ describe('useDisplayName', () => {
useFirstPartyContractNamesMock.mockReturnValue([
NO_FIRST_PARTY_CONTRACT_NAME_FOUND_RETURN_VALUE,
]);
- getMemoizedMetadataContractsMock.mockReturnValue([
+ getRemoteTokensMock.mockReturnValue([
{
name: NO_CONTRACT_NAME_FOUND_RETURN_VALUE,
},
@@ -94,7 +92,7 @@ describe('useDisplayName', () => {
useFirstPartyContractNamesMock.mockReturnValue([
FIRST_PARTY_CONTRACT_NAME_MOCK,
]);
- getMemoizedMetadataContractsMock.mockReturnValue([
+ getRemoteTokensMock.mockReturnValue([
{
name: CONTRACT_NAME_MOCK,
},
@@ -114,7 +112,7 @@ describe('useDisplayName', () => {
useFirstPartyContractNamesMock.mockReturnValue([
FIRST_PARTY_CONTRACT_NAME_MOCK,
]);
- getMemoizedMetadataContractsMock.mockReturnValue({
+ getRemoteTokensMock.mockReturnValue({
name: CONTRACT_NAME_MOCK,
});
getNftContractsByAddressOnCurrentChainMock.mockReturnValue(
@@ -128,7 +126,7 @@ describe('useDisplayName', () => {
});
it('prioritizes a contract name over a watched NFT name', () => {
- getMemoizedMetadataContractsMock.mockReturnValue([
+ getRemoteTokensMock.mockReturnValue([
{
name: CONTRACT_NAME_MOCK,
},
diff --git a/ui/hooks/useDisplayName.ts b/ui/hooks/useDisplayName.ts
index 96e1f48c84e9..13dc005dac00 100644
--- a/ui/hooks/useDisplayName.ts
+++ b/ui/hooks/useDisplayName.ts
@@ -1,6 +1,6 @@
import { NameType } from '@metamask/name-controller';
import { useSelector } from 'react-redux';
-import { getMemoizedMetadataContracts } from '../selectors';
+import { getRemoteTokens } from '../selectors';
import { getNftContractsByAddressOnCurrentChain } from '../selectors/nft';
import { useNames } from './useName';
import { useFirstPartyContractNames } from './useFirstPartyContractName';
@@ -32,7 +32,7 @@ export function useDisplayNames(
const contractInfo = useSelector((state) =>
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- (getMemoizedMetadataContracts as any)(state, values, true),
+ (getRemoteTokens as any)(state, values),
);
const watchedNftNames = useSelector(getNftContractsByAddressOnCurrentChain);
diff --git a/ui/pages/confirmations/components/transaction-decoding/components/decoding/address/address.component.js b/ui/pages/confirmations/components/transaction-decoding/components/decoding/address/address.component.js
index 8f97629ca07d..57f50e468757 100644
--- a/ui/pages/confirmations/components/transaction-decoding/components/decoding/address/address.component.js
+++ b/ui/pages/confirmations/components/transaction-decoding/components/decoding/address/address.component.js
@@ -6,8 +6,8 @@ import { shortenAddress } from '../../../../../../../helpers/utils/util';
import Identicon from '../../../../../../../components/ui/identicon';
import { useI18nContext } from '../../../../../../../hooks/useI18nContext';
import {
- getMemoizedMetadataContractName,
getMemoizedAddressBook,
+ getMetadataContractName,
} from '../../../../../../../selectors';
import NicknamePopovers from '../../../../../../../components/app/modals/nickname-popovers';
import { COPY_OPTIONS } from '../../../../../../../../shared/constants/copy';
@@ -29,7 +29,7 @@ const Address = ({
);
const recipientNickname = addressBookEntryObject?.name;
const recipientMetadataName = useSelector((state) =>
- getMemoizedMetadataContractName(state, checksummedRecipientAddress),
+ getMetadataContractName(state, checksummedRecipientAddress),
);
const recipientToRender = addressOnly
diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js
index c9cc9c47d95f..28ca05d585d8 100644
--- a/ui/selectors/selectors.js
+++ b/ui/selectors/selectors.js
@@ -582,14 +582,6 @@ export function getAccountName(accounts, accountAddress) {
return account && account.metadata.name !== '' ? account.metadata.name : '';
}
-export function getMetadataContractName(state, address) {
- const tokenList = getTokenList(state);
- const entry = Object.values(tokenList).find((token) =>
- isEqualCaseInsensitive(token.address, address),
- );
- return entry && entry.name !== '' ? entry.name : '';
-}
-
export function accountsWithSendEtherInfoSelector(state) {
const accounts = getMetaMaskAccounts(state);
const internalAccounts = getInternalAccounts(state);
@@ -1363,38 +1355,50 @@ export const getMemoizedAddressBook = createDeepEqualSelector(
(addressBook) => addressBook,
);
+export const getRemoteTokenList = createDeepEqualSelector(
+ (state) => state.metamask.tokenList,
+ (remoteTokenList) => remoteTokenList,
+);
+
+/**
+ * To retrieve the token list for use throughout the UI. Will return the remotely fetched list
+ * from the tokens controller if token detection is enabled, or the static list if not.
+ *
+ * @type {() => object}
+ */
+export const getTokenList = createSelector(
+ getRemoteTokenList,
+ getIsTokenDetectionInactiveOnMainnet,
+ (remoteTokenList, isTokenDetectionInactiveOnMainnet) =>
+ isTokenDetectionInactiveOnMainnet
+ ? STATIC_MAINNET_TOKEN_LIST
+ : remoteTokenList,
+);
+
/**
* Returns a memoized selector that gets contract info.
*
* @param state - The Redux store state.
* @param addresses - An array of contract addresses.
- * @param forceRemoteTokenList - Whether to force the use of the remote token list.
* @returns {Array} A map of contract info, keyed by address.
*/
-export const getMemoizedMetadataContracts = createDeepEqualSelector(
- (state, _addresses, forceRemoteTokenList) =>
- getTokenList(state, forceRemoteTokenList),
- (_tokenList, addresses) => addresses,
- (tokenList, addresses) => {
- return addresses.map((address) =>
- Object.values(tokenList).find((identity) =>
- isEqualCaseInsensitive(identity.address, address),
- ),
- );
- },
+export const getRemoteTokens = createSelector(
+ getRemoteTokenList,
+ (_state, addresses) => addresses,
+ (remoteTokenList, addresses) =>
+ addresses.map((address) => remoteTokenList[address?.toLowerCase()]),
);
-export const getMemoizedMetadataContract = createDeepEqualSelector(
- getTokenList,
- (_tokenList, address) => address,
- (tokenList, address) => {
- return Object.values(tokenList).find((identity) =>
- isEqualCaseInsensitive(identity.address, address),
- );
- },
+export const getMemoizedMetadataContract = createSelector(
+ (state, _address) => getTokenList(state),
+ (_state, address) => address,
+ (tokenList, address) => tokenList[address?.toLowerCase()],
);
-export const getMemoizedMetadataContractName = createDeepEqualSelector(
+/**
+ * @type (state: any, address: string) => string
+ */
+export const getMetadataContractName = createSelector(
getMemoizedMetadataContract,
(entry) => entry?.name ?? '',
);
@@ -2006,25 +2010,6 @@ export function getTheme(state) {
return state.metamask.theme;
}
-/**
- * To retrieve the token list for use throughout the UI. Will return the remotely fetched list
- * from the tokens controller if token detection is enabled, or the static list if not.
- *
- * @param {*} state
- * @param {boolean} forceRemote - Whether to force the use of the remote token list regardless of the user preference. Defaults to false.
- * @returns {object}
- */
-export function getTokenList(state, forceRemote = false) {
- const isTokenDetectionInactiveOnMainnet =
- getIsTokenDetectionInactiveOnMainnet(state);
-
- if (isTokenDetectionInactiveOnMainnet && !forceRemote) {
- return STATIC_MAINNET_TOKEN_LIST;
- }
-
- return state.metamask.tokenList;
-}
-
export function doesAddressRequireLedgerHidConnection(state, address) {
const addressIsLedger = isAddressLedger(state, address);
const transportTypePreferenceIsWebHID =