diff --git a/app/components/UI/AddressInputs/index.test.jsx b/app/components/UI/AddressInputs/index.test.jsx
index a32e4577c0d..cc3a6d2bfc5 100644
--- a/app/components/UI/AddressInputs/index.test.jsx
+++ b/app/components/UI/AddressInputs/index.test.jsx
@@ -23,6 +23,7 @@ const initialState = {
name: 'Account 2',
},
},
+ useTokenDetection: false,
},
AddressBookController: {
addressBook: {
@@ -61,7 +62,7 @@ describe('AddressInputs', () => {
fromAccountBalance="0x5"
fromAccountName="DUMMY_ACCOUNT"
/>,
- {},
+ { state: initialState },
);
expect(container).toMatchSnapshot();
});
@@ -74,7 +75,7 @@ describe('AddressInputs', () => {
fromAccountName="DUMMY_ACCOUNT"
layout="vertical"
/>,
- {},
+ { state: initialState },
);
expect(container).toMatchSnapshot();
});
diff --git a/app/components/UI/Identicon/__snapshots__/index.test.tsx.snap b/app/components/UI/Identicon/__snapshots__/index.test.tsx.snap
index 18c2cca7567..9707813bf99 100644
--- a/app/components/UI/Identicon/__snapshots__/index.test.tsx.snap
+++ b/app/components/UI/Identicon/__snapshots__/index.test.tsx.snap
@@ -1,5 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Identicon should render correctly when provided address found in tokenList and iconUrl is available 1`] = `
+
+`;
+
exports[`Identicon should render correctly when useBlockieIcon is false 1`] = `
{
const mockStore = configureMockStore();
+ const mockUseTokenList = jest
+ .mocked(useTokenList)
+ .mockImplementation(() => ({}));
+
+ it('should render correctly when provided address found in tokenList and iconUrl is available', () => {
+ const addressMock = '0x0439e60f02a8900a951603950d8d4527f400c3f1';
+ mockUseTokenList.mockImplementation(() => [
+ {
+ address: addressMock,
+ iconUrl: 'https://example.com/icon.png',
+ },
+ ]);
+
+ const initialState = {
+ settings: { useBlockieIcon: true },
+ };
+ const store = mockStore(initialState);
+
+ const wrapper = render(
+
+
+ ,
+ );
+ expect(wrapper).toMatchSnapshot();
+ });
it('should render correctly when useBlockieIcon is true', () => {
const initialState = {
settings: { useBlockieIcon: true },
diff --git a/app/components/UI/Identicon/index.tsx b/app/components/UI/Identicon/index.tsx
index 945419c69b5..aaac02b3ef4 100644
--- a/app/components/UI/Identicon/index.tsx
+++ b/app/components/UI/Identicon/index.tsx
@@ -6,6 +6,8 @@ import FadeIn from 'react-native-fade-in-image';
import Jazzicon from 'react-native-jazzicon';
import { connect } from 'react-redux';
import { useTheme } from '../../../util/theme';
+import { useTokenListEntry } from '../../../components/hooks/DisplayName/useTokenListEntry';
+import { NameType } from '../../UI/Name/Name.types';
interface IdenticonProps {
/**
@@ -43,23 +45,35 @@ const Identicon: React.FC = ({
useBlockieIcon = true,
}) => {
const { colors } = useTheme();
+ const tokenListIcon = useTokenListEntry(
+ address as string,
+ NameType.EthereumAddress,
+ )?.iconUrl;
if (!address) return null;
const uri = useBlockieIcon && toDataUrl(address);
+ const styleForBlockieAndTokenIcon = [
+ {
+ height: diameter,
+ width: diameter,
+ borderRadius: diameter / 2,
+ },
+ customStyle,
+ ];
+
+ if (tokenListIcon) {
+ return (
+
+ );
+ }
+
const image = useBlockieIcon ? (
-
+
) : (
diff --git a/app/components/UI/Name/Name.test.tsx b/app/components/UI/Name/Name.test.tsx
index b927c680ba8..a233e535f09 100644
--- a/app/components/UI/Name/Name.test.tsx
+++ b/app/components/UI/Name/Name.test.tsx
@@ -14,6 +14,11 @@ jest.mock('../../hooks/DisplayName/useDisplayName', () => ({
default: jest.fn(),
}));
+jest.mock('../Identicon', () => ({
+ __esModule: true,
+ default: () => 'Identicon',
+}));
+
const UNKNOWN_ADDRESS_CHECKSUMMED =
'0x299007B3F9E23B8d432D5f545F8a4a2B3E9A5B4e';
const EXPECTED_UNKNOWN_ADDRESS_CHECKSUMMED = '0x29900...A5B4e';
diff --git a/app/components/UI/Name/__snapshots__/Name.test.tsx.snap b/app/components/UI/Name/__snapshots__/Name.test.tsx.snap
index 75d9eb71ec6..2d61fb1f825 100644
--- a/app/components/UI/Name/__snapshots__/Name.test.tsx.snap
+++ b/app/components/UI/Name/__snapshots__/Name.test.tsx.snap
@@ -16,64 +16,7 @@ exports[`Name recognized address should return name 1`] = `
}
}
>
-
-
-
-
-
-
+ Identicon
-`;
\ No newline at end of file
+`;
diff --git a/app/components/Views/confirmations/Send/index.test.tsx b/app/components/Views/confirmations/Send/index.test.tsx
index 6432727b813..309da7b89ec 100644
--- a/app/components/Views/confirmations/Send/index.test.tsx
+++ b/app/components/Views/confirmations/Send/index.test.tsx
@@ -50,6 +50,9 @@ const initialState = {
TokenBalancesController: {
contractBalances: {},
},
+ TokenListController: {
+ tokenList: [],
+ },
PreferencesController: {
featureFlags: {},
identities: {
diff --git a/app/components/hooks/DisplayName/useTokenList.test.ts b/app/components/hooks/DisplayName/useTokenList.test.ts
index 856ed1c042d..acd96d4e973 100644
--- a/app/components/hooks/DisplayName/useTokenList.test.ts
+++ b/app/components/hooks/DisplayName/useTokenList.test.ts
@@ -1,19 +1,19 @@
import React from 'react';
-import { type TokenListMap } from '@metamask/assets-controllers';
+import { type TokenListToken } from '@metamask/assets-controllers';
import { selectChainId } from '../../../selectors/networkController';
import { selectUseTokenDetection } from '../../../selectors/preferencesController';
-import { selectTokenList } from '../../../selectors/tokenListController';
+import { selectTokenListArray } from '../../../selectors/tokenListController';
import { isMainnetByChainId } from '../../../util/networks';
import useTokenList from './useTokenList';
const MAINNET_TOKEN_ADDRESS_MOCK = '0xdAC17F958D2ee523a2206206994597C13D831ec7';
const MAINNET_TOKEN_NAME_MOCK = 'Tether USD';
-const normalizedMainnetTokenListMock = {
- [MAINNET_TOKEN_ADDRESS_MOCK.toLowerCase()]: {
+const normalizedMainnetTokenListMock = [
+ {
name: MAINNET_TOKEN_NAME_MOCK,
},
-};
+];
jest.mock('@metamask/contract-metadata', () => ({
__esModule: true,
default: {
@@ -38,7 +38,7 @@ jest.mock('../../../selectors/preferencesController', () => ({
}));
jest.mock('../../../selectors/tokenListController', () => ({
- selectTokenList: jest.fn(),
+ selectTokenListArray: jest.fn(),
}));
jest.mock('../../../util/networks', () => ({
@@ -48,27 +48,29 @@ jest.mock('../../../util/networks', () => ({
const CHAIN_ID_MOCK = '0x1';
const TOKEN_NAME_MOCK = 'MetaMask Token';
const TOKEN_ADDRESS_MOCK = '0x0439e60F02a8900a951603950d8D4527f400C3f1';
-const TOKEN_LIST_MOCK = {
- [TOKEN_ADDRESS_MOCK]: {
+const TOKEN_LIST_ARRAY_MOCK = [
+ {
name: TOKEN_NAME_MOCK,
+ address: TOKEN_ADDRESS_MOCK,
},
-} as unknown as TokenListMap;
-const normalizedTokenListMock = {
- [TOKEN_ADDRESS_MOCK.toLowerCase()]: {
+] as unknown as TokenListToken[];
+const normalizedTokenListMock = [
+ {
+ address: TOKEN_ADDRESS_MOCK,
name: TOKEN_NAME_MOCK,
},
-};
+];
describe('useTokenList', () => {
const selectChainIdMock = jest.mocked(selectChainId);
const selectUseTokenDetectionMock = jest.mocked(selectUseTokenDetection);
- const selectTokenListMock = jest.mocked(selectTokenList);
+ const selectTokenListArrayMock = jest.mocked(selectTokenListArray);
const isMainnetByChainIdMock = jest.mocked(isMainnetByChainId);
beforeEach(() => {
jest.resetAllMocks();
selectChainIdMock.mockReturnValue(CHAIN_ID_MOCK);
selectUseTokenDetectionMock.mockReturnValue(true);
- selectTokenListMock.mockReturnValue(TOKEN_LIST_MOCK);
+ selectTokenListArrayMock.mockReturnValue(TOKEN_LIST_ARRAY_MOCK);
isMainnetByChainIdMock.mockReturnValue(true);
const memoizedValues = new Map();
diff --git a/app/components/hooks/DisplayName/useTokenList.ts b/app/components/hooks/DisplayName/useTokenList.ts
index 6726fffbbf3..3e99976658e 100644
--- a/app/components/hooks/DisplayName/useTokenList.ts
+++ b/app/components/hooks/DisplayName/useTokenList.ts
@@ -1,38 +1,27 @@
import { useMemo } from 'react';
-import { type TokenListMap } from '@metamask/assets-controllers';
import contractMap from '@metamask/contract-metadata';
-
+import { TokenListToken } from '@metamask/assets-controllers';
import { useSelector } from 'react-redux';
import { selectChainId } from '../../../selectors/networkController';
import { selectUseTokenDetection } from '../../../selectors/preferencesController';
-import { selectTokenList } from '../../../selectors/tokenListController';
+import { selectTokenListArray } from '../../../selectors/tokenListController';
import { isMainnetByChainId } from '../../../util/networks';
-function normalizeTokenAddresses(tokenMap: TokenListMap) {
- return Object.keys(tokenMap).reduce((acc, address) => {
- const tokenMetadata = tokenMap[address];
- return {
- ...acc,
- [address.toLowerCase()]: {
- ...tokenMetadata,
- },
- };
- }, {});
-}
-
-const NORMALIZED_MAINNET_TOKEN_LIST = normalizeTokenAddresses(contractMap);
+const NORMALIZED_MAINNET_TOKEN_ARRAY = Object.values(
+ contractMap,
+) as TokenListToken[];
-export default function useTokenList(): TokenListMap {
+export default function useTokenList(): TokenListToken[] {
const chainId = useSelector(selectChainId);
const isMainnet = isMainnetByChainId(chainId);
const isTokenDetectionEnabled = useSelector(selectUseTokenDetection);
- const tokenList = useSelector(selectTokenList);
+ const tokenListArray = useSelector(selectTokenListArray);
const shouldUseStaticList = !isTokenDetectionEnabled && isMainnet;
return useMemo(() => {
if (shouldUseStaticList) {
- return NORMALIZED_MAINNET_TOKEN_LIST;
+ return NORMALIZED_MAINNET_TOKEN_ARRAY;
}
- return normalizeTokenAddresses(tokenList);
- }, [shouldUseStaticList, tokenList]);
+ return tokenListArray;
+ }, [shouldUseStaticList, tokenListArray]);
}
diff --git a/app/components/hooks/DisplayName/useTokenListEntry.test.ts b/app/components/hooks/DisplayName/useTokenListEntry.test.ts
index 7aeb4144129..bd576c0bb28 100644
--- a/app/components/hooks/DisplayName/useTokenListEntry.test.ts
+++ b/app/components/hooks/DisplayName/useTokenListEntry.test.ts
@@ -1,4 +1,4 @@
-import { TokenListMap } from '@metamask/assets-controllers';
+import { TokenListToken } from '@metamask/assets-controllers';
import { NameType } from '../../UI/Name/Name.types';
import { useTokenListEntry } from './useTokenListEntry';
import useTokenList from './useTokenList';
@@ -18,12 +18,13 @@ describe('useTokenListEntry', () => {
beforeEach(() => {
jest.resetAllMocks();
- useTokenListMock.mockReturnValue({
- [TOKEN_ADDRESS_MOCK.toLowerCase()]: {
+ useTokenListMock.mockReturnValue([
+ {
+ address: TOKEN_ADDRESS_MOCK.toLowerCase(),
name: TOKEN_NAME_MOCK,
symbol: TOKEN_SYMBOL_MOCK,
},
- } as TokenListMap);
+ ] as unknown as TokenListToken[]);
});
it('returns undefined if no token found', () => {
diff --git a/app/components/hooks/DisplayName/useTokenListEntry.ts b/app/components/hooks/DisplayName/useTokenListEntry.ts
index 6fecba6303f..6cfe75a87d1 100644
--- a/app/components/hooks/DisplayName/useTokenListEntry.ts
+++ b/app/components/hooks/DisplayName/useTokenListEntry.ts
@@ -1,3 +1,4 @@
+import { TokenListToken } from '@metamask/assets-controllers';
import { NameType } from '../../UI/Name/Name.types';
import useTokenList from './useTokenList';
@@ -7,7 +8,7 @@ export interface UseTokenListEntriesRequest {
}
export function useTokenListEntries(requests: UseTokenListEntriesRequest[]) {
- const tokenList = useTokenList();
+ const tokenListArray = useTokenList();
return requests.map(({ value, type }) => {
if (type !== NameType.EthereumAddress) {
@@ -16,7 +17,9 @@ export function useTokenListEntries(requests: UseTokenListEntriesRequest[]) {
const normalizedValue = value.toLowerCase();
- return tokenList[normalizedValue];
+ return tokenListArray.find(
+ (token: TokenListToken) => token.address === normalizedValue,
+ );
});
}
diff --git a/app/selectors/tokenListController.ts b/app/selectors/tokenListController.ts
index 4cdee2c6586..294c3a6ed3e 100644
--- a/app/selectors/tokenListController.ts
+++ b/app/selectors/tokenListController.ts
@@ -2,6 +2,7 @@ import { createSelector } from 'reselect';
import { TokenListState } from '@metamask/assets-controllers';
import { RootState } from '../reducers';
import { tokenListToArray } from '../util/tokens';
+import { createDeepEqualSelector } from '../selectors/util';
const selectTokenLIstConstrollerState = (state: RootState) =>
state.engine.backgroundState.TokenListController;
@@ -20,7 +21,7 @@ export const selectTokenList = createSelector(
* Return token list array from TokenListController.
* Can pass directly into useSelector.
*/
-export const selectTokenListArray = createSelector(
+export const selectTokenListArray = createDeepEqualSelector(
selectTokenList,
tokenListToArray,
);