diff --git a/app/components/UI/Transactions/TransactionsFooter.tsx b/app/components/UI/Transactions/TransactionsFooter.tsx
index e2fd4bb9713..18caad4182c 100644
--- a/app/components/UI/Transactions/TransactionsFooter.tsx
+++ b/app/components/UI/Transactions/TransactionsFooter.tsx
@@ -93,7 +93,7 @@ const TransactionsFooter = ({
return null;
}
- if (isMainnetByChainId(chainId) || providerType !== RPC) {
+ if (isMainnetByChainId(chainId) || (providerType && providerType !== RPC)) {
return strings('transactions.view_full_history_on_etherscan');
}
diff --git a/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.test.tsx b/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.test.tsx
index 141f185ea5e..0391156d894 100644
--- a/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.test.tsx
+++ b/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.test.tsx
@@ -98,6 +98,7 @@ jest.mock('../../../selectors/networkController', () => ({
selectNetworkConfigurations: jest.fn(),
selectProviderType: jest.fn(),
selectRpcUrl: jest.fn(),
+ selectProviderConfig: jest.fn(),
}));
jest.mock('../../../selectors/networkEnablementController', () => ({
selectEVMEnabledNetworks: jest.fn(),
@@ -145,6 +146,16 @@ jest.mock('../../UI/Bridge/hooks/useBridgeHistoryItemBySrcTxHash', () => ({
}),
}));
+const mockGetBlockExplorerUrl = jest.fn(() => undefined);
+const mockGetBlockExplorerName = jest.fn(() => 'Explorer');
+jest.mock('../../hooks/useBlockExplorer', () => ({
+ __esModule: true,
+ default: () => ({
+ getBlockExplorerUrl: mockGetBlockExplorerUrl,
+ getBlockExplorerName: mockGetBlockExplorerName,
+ }),
+}));
+
const mockTransactionsFooter = jest.fn((props: unknown) => {
const ReactActual = jest.requireActual('react');
const { Text } = jest.requireActual('react-native');
@@ -183,6 +194,7 @@ jest.mock('../../../core/Multichain/utils', () => ({
__esModule: true,
getAddressUrl: (address: string, chainId: string) =>
mockGetAddressUrl(address, chainId),
+ isNonEvmChainId: jest.fn((chainId: string) => chainId.includes(':')),
}));
// Mock refresh util
@@ -292,6 +304,7 @@ const {
selectNetworkConfigurations,
selectProviderType,
selectRpcUrl,
+ selectProviderConfig,
} = jest.requireMock('../../../selectors/networkController');
const { selectEVMEnabledNetworks, selectNonEVMEnabledNetworks } =
jest.requireMock('../../../selectors/networkEnablementController');
@@ -312,6 +325,10 @@ describe('UnifiedTransactionsView', () => {
mockTransactionsFooter.mockClear();
mockMultichainTransactionsFooter.mockClear();
mockGetAddressUrl.mockClear();
+ mockGetBlockExplorerUrl.mockClear();
+ mockGetBlockExplorerUrl.mockReturnValue(undefined);
+ mockGetBlockExplorerName.mockClear();
+ mockGetBlockExplorerName.mockReturnValue('Explorer');
mockGetAddressUrl.mockImplementation(
(address?: string) => `https://solscan.io/account/${address}`,
);
@@ -350,6 +367,8 @@ describe('UnifiedTransactionsView', () => {
if (selector === selectNetworkConfigurations) return {};
if (selector === selectProviderType) return 'rpc';
if (selector === selectRpcUrl) return 'https://rpc.example';
+ if (selector === selectProviderConfig)
+ return { type: 'rpc', rpcUrl: 'https://rpc.example' };
if (selector === selectEVMEnabledNetworks) return ['0x1'];
if (selector === selectNonEVMEnabledNetworks) return ['solana:mainnet'];
if (selector === selectCurrentCurrency) return 'USD';
@@ -410,8 +429,10 @@ describe('UnifiedTransactionsView', () => {
it('pull-to-refresh calls updateIncomingTransactions', async () => {
const { UNSAFE_getAllByType } = render();
+
const [rc] = UNSAFE_getAllByType(RefreshControl);
- rc.props.onRefresh();
+ await rc.props.onRefresh();
+
expect(updateIncomingTransactions).toHaveBeenCalled();
});
@@ -583,6 +604,8 @@ describe('UnifiedTransactionsView', () => {
if (selector === selectNetworkConfigurations) return {};
if (selector === selectProviderType) return 'rpc';
if (selector === selectRpcUrl) return 'https://rpc.example';
+ if (selector === selectProviderConfig)
+ return { type: 'rpc', rpcUrl: 'https://rpc.example' };
if (selector === selectEVMEnabledNetworks) return ['0x1', '0x5'];
if (selector === selectNonEVMEnabledNetworks) return [];
if (selector === selectCurrentCurrency) return 'USD';
@@ -599,12 +622,9 @@ describe('UnifiedTransactionsView', () => {
expect(footerProps.rpcBlockExplorer).toBeUndefined();
footerProps.onViewBlockExplorer?.();
- expect(networksMock.getBlockExplorerAddressUrl).toHaveBeenCalledWith(
- 'rpc',
- '0xabc',
- undefined,
- );
- expect(networksMock.getBlockExplorerAddressUrl).toHaveBeenCalledTimes(1);
+ // When configBlockExplorerUrl is undefined (multiple chains case),
+ // the component uses blockExplorerUrl directly without calling getBlockExplorerAddressUrl
+ expect(networksMock.getBlockExplorerAddressUrl).not.toHaveBeenCalled();
isRemoveGlobalNetworkSelectorEnabled.mockReturnValue(false);
});
});
@@ -635,6 +655,8 @@ describe('UnifiedTransactionsView', () => {
if (selector === selectNetworkConfigurations) return {};
if (selector === selectProviderType) return 'rpc';
if (selector === selectRpcUrl) return 'https://rpc.example';
+ if (selector === selectProviderConfig)
+ return { type: 'rpc', rpcUrl: 'https://rpc.example' };
if (selector === selectEVMEnabledNetworks) return [];
if (selector === selectNonEVMEnabledNetworks) return ['solana:mainnet'];
if (selector === selectCurrentCurrency) return 'USD';
@@ -682,6 +704,8 @@ describe('UnifiedTransactionsView', () => {
if (selector === selectNetworkConfigurations) return {};
if (selector === selectProviderType) return 'rpc';
if (selector === selectRpcUrl) return 'https://rpc.example';
+ if (selector === selectProviderConfig)
+ return { type: 'rpc', rpcUrl: 'https://rpc.example' };
if (selector === selectEVMEnabledNetworks) return [];
if (selector === selectNonEVMEnabledNetworks)
return ['bip122:000000000019d6689c085ae165831e93'];
@@ -720,6 +744,8 @@ describe('UnifiedTransactionsView', () => {
if (selector === selectTokens) return [];
if (selector === selectChainId) return '0x1';
if (selector === selectIsPopularNetwork) return false;
+ if (selector === selectProviderConfig)
+ return { type: 'rpc', rpcUrl: 'https://rpc.example' };
if (selector === selectEVMEnabledNetworks) return [];
if (selector === selectNonEVMEnabledNetworks) return [];
if (selector === selectCurrentCurrency) return 'USD';
@@ -754,6 +780,8 @@ describe('UnifiedTransactionsView', () => {
if (selector === selectTokens) return [];
if (selector === selectChainId) return '0x1';
if (selector === selectIsPopularNetwork) return false;
+ if (selector === selectProviderConfig)
+ return { type: 'rpc', rpcUrl: 'https://rpc.example' };
if (selector === selectEVMEnabledNetworks) return [];
if (selector === selectNonEVMEnabledNetworks) return [];
if (selector === selectCurrentCurrency) return 'USD';
diff --git a/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx b/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx
index 5a75b545e3f..3713ae78c0a 100644
--- a/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx
+++ b/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx
@@ -61,6 +61,7 @@ import { getAddressUrl } from '../../../core/Multichain/utils';
import UpdateEIP1559Tx from '../confirmations/legacy/components/UpdateEIP1559Tx';
import styleSheet from './UnifiedTransactionsView.styles';
import { useUnifiedTxActions } from './useUnifiedTxActions';
+import useBlockExplorer from '../../hooks/useBlockExplorer';
type SmartTransactionWithId = SmartTransaction & { id: string };
type EvmTransaction = TransactionMeta | SmartTransactionWithId;
@@ -357,7 +358,12 @@ const UnifiedTransactionsView = ({
currentEvmChainId,
]);
- const blockExplorerUrl = useMemo(() => {
+ const hasEvmChainsEnabled = enabledEVMChainIds.length > 0;
+ const popularListBlockExplorer = useBlockExplorer(
+ hasEvmChainsEnabled ? enabledEVMChainIds[0] : undefined,
+ );
+
+ const configBlockExplorerUrl = useMemo(() => {
// When using the per-dapp/multiselect network selector, only return a block
// explorer if exactly one EVM chain is selected. Otherwise, undefined.
if (isRemoveGlobalNetworkSelectorEnabled()) {
@@ -377,7 +383,24 @@ const UnifiedTransactionsView = ({
return config.blockExplorerUrls?.[index];
}, [enabledEVMChainIds, evmNetworkConfigurationsByChainId]);
- const hasEvmChainsEnabled = enabledEVMChainIds.length > 0;
+ const blockExplorerUrl = useMemo(() => {
+ // configBlockExplorerUrl contains block explorer urls only for networks added by default after fresh install
+ // other networks should use PopularList, which is used by useBlockExplorer hook
+ if (configBlockExplorerUrl) {
+ return configBlockExplorerUrl;
+ }
+ return hasEvmChainsEnabled
+ ? popularListBlockExplorer.getBlockExplorerUrl(
+ selectedAccountGroupEvmAddress,
+ ) || undefined
+ : undefined;
+ }, [
+ configBlockExplorerUrl,
+ popularListBlockExplorer,
+ selectedAccountGroupEvmAddress,
+ hasEvmChainsEnabled,
+ ]);
+
const hasNonEvmChainsEnabled = enabledNonEVMChainIds.length > 0;
const showEvmFooter = hasEvmChainsEnabled && !hasNonEvmChainsEnabled;
@@ -388,14 +411,25 @@ const UnifiedTransactionsView = ({
return;
}
- const { url, title } = getBlockExplorerAddressUrl(
- providerType,
- selectedAccountGroupEvmAddress,
- blockExplorerUrl,
- );
+ let url;
+ let title;
+ if (configBlockExplorerUrl) {
+ const result = getBlockExplorerAddressUrl(
+ providerType,
+ selectedAccountGroupEvmAddress,
+ blockExplorerUrl,
+ );
+ url = result.url;
+ title = result.title;
- if (!url) {
- return;
+ if (!url) {
+ return;
+ }
+ } else {
+ url = blockExplorerUrl;
+ title = hasEvmChainsEnabled
+ ? popularListBlockExplorer.getBlockExplorerName(enabledEVMChainIds[0])
+ : undefined;
}
navigation.navigate('Webview', {
@@ -410,6 +444,10 @@ const UnifiedTransactionsView = ({
providerType,
blockExplorerUrl,
selectedAccountGroupEvmAddress,
+ popularListBlockExplorer,
+ enabledEVMChainIds,
+ configBlockExplorerUrl,
+ hasEvmChainsEnabled,
]);
const allNonEvmChainsAreSolana = useMemo(
@@ -461,7 +499,7 @@ const UnifiedTransactionsView = ({
return (
@@ -494,6 +532,7 @@ const UnifiedTransactionsView = ({
showEvmFooter,
showNonEvmExplorerLink,
showNonEvmFooter,
+ configBlockExplorerUrl,
]);
const [refreshing, setRefreshing] = useState(false);
diff --git a/app/components/Views/confirmations/hooks/send/useNameValidation.test.ts b/app/components/Views/confirmations/hooks/send/useNameValidation.test.ts
index ac8b27e606c..e7a3c69cc7b 100644
--- a/app/components/Views/confirmations/hooks/send/useNameValidation.test.ts
+++ b/app/components/Views/confirmations/hooks/send/useNameValidation.test.ts
@@ -7,16 +7,41 @@ import * as SnapNameResolution from '../../../../Snaps/hooks/useSnapNameResoluti
import * as SendValidationUtils from '../../utils/send-address-validations';
import { evmSendStateMock } from '../../__mocks__/send.mock';
import { useNameValidation } from './useNameValidation';
+import { useSendType } from './useSendType';
+import { useSendFlowEnsResolutions } from './useSendFlowEnsResolutions';
jest.mock('@metamask/bridge-controller', () => ({
formatChainIdToCaip: jest.fn(),
}));
+jest.mock('./useSendType', () => ({
+ useSendType: jest.fn(),
+}));
+
+jest.mock('./useSendFlowEnsResolutions', () => ({
+ useSendFlowEnsResolutions: jest.fn(() => ({
+ setResolvedAddress: jest.fn(),
+ })),
+}));
+
const mockState = {
state: evmSendStateMock,
};
describe('useNameValidation', () => {
+ const mockUseSendType = jest.mocked(useSendType);
+ const mockUseSendFlowEnsResolutions = jest.mocked(useSendFlowEnsResolutions);
+ const mockSetResolvedAddress = jest.fn();
+
+ beforeEach(() => {
+ mockUseSendType.mockReturnValue({
+ isEvmSendType: false,
+ } as ReturnType);
+ mockUseSendFlowEnsResolutions.mockReturnValue({
+ setResolvedAddress: mockSetResolvedAddress,
+ } as unknown as ReturnType);
+ });
+
it('return function to validate name', () => {
const { result } = renderHookWithProvider(
() => useNameValidation(),
@@ -44,6 +69,36 @@ describe('useNameValidation', () => {
).toStrictEqual({
resolvedAddress: 'dummy_address',
});
+ expect(mockSetResolvedAddress).not.toHaveBeenCalled();
+ });
+
+ it('calls setResolvedAddress when name is resolved and isEvmSendType is true', async () => {
+ mockUseSendType.mockReturnValue({
+ isEvmSendType: true,
+ } as ReturnType);
+ jest.spyOn(SnapNameResolution, 'useSnapNameResolution').mockReturnValue({
+ fetchResolutions: () =>
+ Promise.resolve([
+ { resolvedAddress: 'dummy_address' } as unknown as AddressResolution,
+ ]),
+ });
+ const { result } = renderHookWithProvider(
+ () => useNameValidation(),
+ mockState,
+ );
+ expect(
+ await result.current.validateName(
+ '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
+ 'test.eth',
+ ),
+ ).toStrictEqual({
+ resolvedAddress: 'dummy_address',
+ });
+ expect(mockSetResolvedAddress).toHaveBeenCalledWith(
+ '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
+ 'test.eth',
+ 'dummy_address',
+ );
});
it('return confusable error and warning as name is resolved', async () => {
diff --git a/app/components/Views/confirmations/hooks/send/useNameValidation.ts b/app/components/Views/confirmations/hooks/send/useNameValidation.ts
index 2585de5c384..f4ae7a25bc7 100644
--- a/app/components/Views/confirmations/hooks/send/useNameValidation.ts
+++ b/app/components/Views/confirmations/hooks/send/useNameValidation.ts
@@ -5,9 +5,13 @@ import { strings } from '../../../../../../locales/i18n';
import { isENS } from '../../../../../util/address';
import { useSnapNameResolution } from '../../../../Snaps/hooks/useSnapNameResolution';
import { getConfusableCharacterInfo } from '../../utils/send-address-validations';
+import { useSendFlowEnsResolutions } from './useSendFlowEnsResolutions';
+import { useSendType } from './useSendType';
export const useNameValidation = () => {
const { fetchResolutions } = useSnapNameResolution();
+ const { setResolvedAddress } = useSendFlowEnsResolutions();
+ const { isEvmSendType } = useSendType();
const validateName = useCallback(
async (chainId: string, to: string) => {
@@ -24,6 +28,11 @@ export const useNameValidation = () => {
}
const resolvedAddress = resolutions[0]?.resolvedAddress;
+ if (resolvedAddress && isEvmSendType) {
+ // Set short living cache of ENS resolution for the given chain and address for confirmation screen
+ setResolvedAddress(chainId, to, resolvedAddress);
+ }
+
return {
resolvedAddress,
...getConfusableCharacterInfo(to),
@@ -34,7 +43,7 @@ export const useNameValidation = () => {
error: strings('send.could_not_resolve_name'),
};
},
- [fetchResolutions],
+ [fetchResolutions, isEvmSendType, setResolvedAddress],
);
return {
diff --git a/app/components/Views/confirmations/hooks/send/useSendFlowEnsResolutions.test.ts b/app/components/Views/confirmations/hooks/send/useSendFlowEnsResolutions.test.ts
new file mode 100644
index 00000000000..0f6305d5d79
--- /dev/null
+++ b/app/components/Views/confirmations/hooks/send/useSendFlowEnsResolutions.test.ts
@@ -0,0 +1,93 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { useSendFlowEnsResolutions } from './useSendFlowEnsResolutions';
+
+describe('useSendFlowEnsResolutions', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('stores and retrieves ENS name for an address', () => {
+ const { result } = renderHook(() => useSendFlowEnsResolutions());
+ const chainId = '0x1';
+ const ensName = 'vitalik.eth';
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
+
+ result.current.setResolvedAddress(chainId, ensName, address);
+ const retrieved = result.current.getResolvedENSName(chainId, address);
+
+ expect(retrieved).toBe(ensName);
+ });
+
+ it('returns undefined for non-existent address', () => {
+ const { result } = renderHook(() => useSendFlowEnsResolutions());
+ const chainId = '0x1';
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96046';
+
+ const retrieved = result.current.getResolvedENSName(chainId, address);
+
+ expect(retrieved).toBeUndefined();
+ });
+
+ it('differentiates between different chain IDs', () => {
+ const { result } = renderHook(() => useSendFlowEnsResolutions());
+ const chainId1 = '0x1';
+ const chainId2 = '0x89';
+ const ensName = 'vitalik.eth';
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
+
+ result.current.setResolvedAddress(chainId1, ensName, address);
+ const retrieved1 = result.current.getResolvedENSName(chainId1, address);
+ const retrieved2 = result.current.getResolvedENSName(chainId2, address);
+
+ expect(retrieved1).toBe(ensName);
+ expect(retrieved2).toBeUndefined();
+ });
+
+ it('returns undefined for expired cache entries', () => {
+ jest.useFakeTimers();
+ const { result } = renderHook(() => useSendFlowEnsResolutions());
+ const chainId = '0x1';
+ const ensName = 'vitalik.eth';
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
+
+ result.current.setResolvedAddress(chainId, ensName, address);
+
+ jest.advanceTimersByTime(5 * 60 * 1000 + 1);
+
+ const retrieved = result.current.getResolvedENSName(chainId, address);
+
+ expect(retrieved).toBeUndefined();
+ jest.useRealTimers();
+ });
+
+ it('returns cached entry before expiration', () => {
+ jest.useFakeTimers();
+ const { result } = renderHook(() => useSendFlowEnsResolutions());
+ const chainId = '0x1';
+ const ensName = 'vitalik.eth';
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
+
+ result.current.setResolvedAddress(chainId, ensName, address);
+
+ jest.advanceTimersByTime(5 * 60 * 1000 - 1000);
+
+ const retrieved = result.current.getResolvedENSName(chainId, address);
+
+ expect(retrieved).toBe(ensName);
+ jest.useRealTimers();
+ });
+
+ it('overwrites existing cache entry for same chain and address', () => {
+ const { result } = renderHook(() => useSendFlowEnsResolutions());
+ const chainId = '0x1';
+ const ensName1 = 'vitalik.eth';
+ const ensName2 = 'newname.eth';
+ const address = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
+
+ result.current.setResolvedAddress(chainId, ensName1, address);
+ result.current.setResolvedAddress(chainId, ensName2, address);
+ const retrieved = result.current.getResolvedENSName(chainId, address);
+
+ expect(retrieved).toBe(ensName2);
+ });
+});
diff --git a/app/components/Views/confirmations/hooks/send/useSendFlowEnsResolutions.ts b/app/components/Views/confirmations/hooks/send/useSendFlowEnsResolutions.ts
new file mode 100644
index 00000000000..05ea8fd19df
--- /dev/null
+++ b/app/components/Views/confirmations/hooks/send/useSendFlowEnsResolutions.ts
@@ -0,0 +1,80 @@
+import { useCallback } from 'react';
+
+interface CacheEntry {
+ ensName: string;
+ timestamp: number;
+}
+
+const CACHE_TTL = 5 * 60 * 1000;
+
+const ensResolutionCache = new Map();
+
+const createCacheKey = (chainId: string, address: string) =>
+ `${chainId}:${address}`;
+
+const isCacheEntryValid = (entry: CacheEntry): boolean =>
+ Date.now() - entry.timestamp < CACHE_TTL;
+
+// This hook is used to store and retrieve ENS resolutions for a given chain and address with short living cache
+// especially to keep consistency of Name component in send flow
+export const useSendFlowEnsResolutions = () => {
+ const setResolvedAddress = useCallback(
+ (chainId: string, ensName: string, address: string) => {
+ if (
+ !isValidString(chainId) ||
+ !isValidString(address) ||
+ !isValidString(ensName)
+ ) {
+ return;
+ }
+
+ const lowerCaseChainId = chainId.toLowerCase();
+ const lowerCaseAddress = address.toLowerCase();
+ const lowerCaseEnsName = ensName.toLowerCase();
+
+ ensResolutionCache.set(
+ createCacheKey(lowerCaseChainId, lowerCaseAddress),
+ {
+ ensName: lowerCaseEnsName,
+ timestamp: Date.now(),
+ },
+ );
+ },
+ [],
+ );
+
+ const getResolvedENSName = useCallback(
+ (chainId: string, address: string): string | undefined => {
+ if (!isValidString(chainId) || !isValidString(address)) {
+ return undefined;
+ }
+
+ const lowerCaseChainId = chainId.toLowerCase();
+ const lowerCaseAddress = address.toLowerCase();
+
+ const entry = ensResolutionCache.get(
+ createCacheKey(lowerCaseChainId, lowerCaseAddress),
+ );
+ if (entry && isCacheEntryValid(entry)) {
+ return entry.ensName;
+ }
+
+ if (entry) {
+ ensResolutionCache.delete(
+ createCacheKey(lowerCaseChainId, lowerCaseAddress),
+ );
+ }
+ return undefined;
+ },
+ [],
+ );
+
+ return {
+ setResolvedAddress,
+ getResolvedENSName,
+ };
+};
+
+function isValidString(value: unknown) {
+ return typeof value === 'string' && value.length > 0;
+}
diff --git a/app/components/hooks/DisplayName/useDisplayName.test.ts b/app/components/hooks/DisplayName/useDisplayName.test.ts
index 96356b2888f..811ff74d674 100644
--- a/app/components/hooks/DisplayName/useDisplayName.test.ts
+++ b/app/components/hooks/DisplayName/useDisplayName.test.ts
@@ -8,6 +8,7 @@ import { useWatchedNFTNames } from './useWatchedNFTNames';
import { useNftNames } from './useNftName';
import { useAccountNames } from './useAccountNames';
import { useAccountWalletNames } from './useAccountWalletNames';
+import { useSendFlowEnsResolutions } from '../../Views/confirmations/hooks/send/useSendFlowEnsResolutions';
const UNKNOWN_ADDRESS_CHECKSUMMED =
'0x299007B3F9E23B8d432D5f545F8a4a2B3E9A5B4e';
@@ -43,6 +44,15 @@ jest.mock('./useAccountWalletNames', () => ({
useAccountWalletNames: jest.fn(),
}));
+jest.mock(
+ '../../Views/confirmations/hooks/send/useSendFlowEnsResolutions',
+ () => ({
+ useSendFlowEnsResolutions: jest.fn(() => ({
+ getResolvedENSName: jest.fn(),
+ })),
+ }),
+);
+
describe('useDisplayName', () => {
const mockUseWatchedNFTNames = jest.mocked(useWatchedNFTNames);
const mockUseFirstPartyContractNames = jest.mocked(
@@ -52,6 +62,8 @@ describe('useDisplayName', () => {
const mockUseNFTNames = jest.mocked(useNftNames);
const mockUseAccountNames = jest.mocked(useAccountNames);
const mockUseAccountWalletNames = jest.mocked(useAccountWalletNames);
+ const mockUseSendFlowEnsResolutions = jest.mocked(useSendFlowEnsResolutions);
+ const mockGetResolvedENSName = jest.fn();
beforeEach(() => {
jest.resetAllMocks();
@@ -61,6 +73,9 @@ describe('useDisplayName', () => {
mockUseNFTNames.mockReturnValue([]);
mockUseAccountNames.mockReturnValue([]);
mockUseAccountWalletNames.mockReturnValue([]);
+ mockUseSendFlowEnsResolutions.mockReturnValue({
+ getResolvedENSName: mockGetResolvedENSName,
+ } as unknown as ReturnType);
});
describe('unknown address', () => {
@@ -189,5 +204,23 @@ describe('useDisplayName', () => {
}),
);
});
+
+ it('returns ENS name', () => {
+ mockUseSendFlowEnsResolutions.mockReturnValue({
+ getResolvedENSName: jest.fn().mockReturnValue('ensname.eth'),
+ } as unknown as ReturnType);
+
+ const displayName = useDisplayName({
+ type: NameType.EthereumAddress,
+ value: KNOWN_NFT_ADDRESS_CHECKSUMMED,
+ variation: CHAIN_IDS.MAINNET,
+ });
+
+ expect(displayName).toEqual(
+ expect.objectContaining({
+ name: 'ensname.eth',
+ }),
+ );
+ });
});
});
diff --git a/app/components/hooks/DisplayName/useDisplayName.ts b/app/components/hooks/DisplayName/useDisplayName.ts
index 8b55a09d126..61ea33dc828 100644
--- a/app/components/hooks/DisplayName/useDisplayName.ts
+++ b/app/components/hooks/DisplayName/useDisplayName.ts
@@ -5,6 +5,7 @@ import { useERC20Tokens } from './useERC20Tokens';
import { useNftNames } from './useNftName';
import { useAccountNames } from './useAccountNames';
import { useAccountWalletNames } from './useAccountWalletNames';
+import { useSendFlowEnsResolutions } from '../../Views/confirmations/hooks/send/useSendFlowEnsResolutions';
export interface UseDisplayNameRequest {
preferContractSymbol?: boolean;
@@ -99,8 +100,9 @@ export function useDisplayNames(
const nftNames = useNftNames(requests);
const accountNames = useAccountNames(requests);
const accountWalletNames = useAccountWalletNames(requests);
+ const { getResolvedENSName } = useSendFlowEnsResolutions();
- return requests.map((_request, index) => {
+ return requests.map(({ value, variation }, index) => {
const watchedNftName = watchedNftNames[index];
const firstPartyContractName = firstPartyContractNames[index];
const erc20Token = erc20Tokens[index];
@@ -108,9 +110,11 @@ export function useDisplayNames(
nftNames[index] || {};
const accountName = accountNames[index];
const subtitle = accountWalletNames[index];
+ const ensName = getResolvedENSName(variation, value);
const name =
accountName ||
+ ensName ||
firstPartyContractName ||
watchedNftName ||
erc20Token?.name ||
diff --git a/app/util/networks/customNetworks.tsx b/app/util/networks/customNetworks.tsx
index 15eb6068caa..4f4eef579bb 100644
--- a/app/util/networks/customNetworks.tsx
+++ b/app/util/networks/customNetworks.tsx
@@ -106,7 +106,7 @@ export const PopularList = [
rpcUrl: `https://palm-mainnet.infura.io/v3/${infuraProjectId}`,
ticker: 'PALM',
rpcPrefs: {
- blockExplorerUrl: 'https://explorer.palm.io',
+ blockExplorerUrl: 'https://palm.chainlens.com',
imageUrl: 'PALM',
imageSource: require('../../images/palm.png'),
},