From 96aa5ca82bb2716678396764cbe3c2756b78bfbb Mon Sep 17 00:00:00 2001 From: Yash Goyal Date: Fri, 5 Jun 2026 15:30:49 +0200 Subject: [PATCH] fix: make ENS resolution ENSv2-ready (#3008) --- .../WalletConnection/ReadOnlyModal.tsx | 24 ++------ .../Bridge/BridgeDestinationInput.tsx | 8 +-- src/libs/hooks/use-get-ens.tsx | 16 ++--- src/store/utils/domain-fetchers/ens.ts | 16 +---- src/utils/ensClient.ts | 58 +++++++++++++++++++ src/utils/marketsAndNetworksConfig.ts | 6 -- 6 files changed, 78 insertions(+), 50 deletions(-) create mode 100644 src/utils/ensClient.ts diff --git a/src/components/WalletConnection/ReadOnlyModal.tsx b/src/components/WalletConnection/ReadOnlyModal.tsx index b3db05c6a1..7bc6731e8e 100644 --- a/src/components/WalletConnection/ReadOnlyModal.tsx +++ b/src/components/WalletConnection/ReadOnlyModal.tsx @@ -8,15 +8,14 @@ import { useMediaQuery, useTheme, } from '@mui/material'; -import { utils } from 'ethers'; import { useState } from 'react'; import { ReadOnlyModeTooltip } from 'src/components/infoTooltips/ReadOnlyModeTooltip'; import { ModalType, useModalContext } from 'src/hooks/useModal'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; import { useRootStore } from 'src/store/root'; +import { resolveEnsAddress } from 'src/utils/ensClient'; import { AUTH } from 'src/utils/events'; -import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; -import { normalize } from 'viem/ens'; +import { isAddress } from 'viem'; import { useAccount, useDisconnect } from 'wagmi'; import { BasicModal } from '../primitives/BasicModal'; @@ -31,24 +30,17 @@ export const ReadOnlyModal = () => { const { type, close } = useModalContext(); const { breakpoints } = useTheme(); const sm = useMediaQuery(breakpoints.down('sm')); - const mainnetProvider = getENSProvider(); const trackEvent = useRootStore((store) => store.trackEvent); const handleReadAddress = async (inputMockWalletAddress: string): Promise => { if (validAddressError) setValidAddressError(false); - if (utils.isAddress(inputMockWalletAddress)) { + if (isAddress(inputMockWalletAddress)) { saveAndClose(inputMockWalletAddress); } else { // Check if address could be valid ENS before trying to resolve - if (inputMockWalletAddress.slice(-4) === '.eth') { - const normalizedENS = normalize(inputMockWalletAddress); - // Attempt to resolve ENS name and use resolved address if valid - const resolvedAddress = await mainnetProvider.resolveName(normalizedENS); - if (resolvedAddress && utils.isAddress(resolvedAddress)) { - saveAndClose(resolvedAddress); - } else { - setValidAddressError(true); - } + const resolvedAddress = await resolveEnsAddress(inputMockWalletAddress); + if (resolvedAddress && isAddress(resolvedAddress)) { + saveAndClose(resolvedAddress); } else { setValidAddressError(true); } @@ -122,10 +114,6 @@ export const ReadOnlyModal = () => { size="large" fullWidth onClick={() => trackEvent(AUTH.MOCK_WALLET)} - disabled={ - !utils.isAddress(inputMockWalletAddress) && - inputMockWalletAddress.slice(-4) !== '.eth' - } aria-label="read-only mode address" > Track wallet diff --git a/src/components/transactions/Bridge/BridgeDestinationInput.tsx b/src/components/transactions/Bridge/BridgeDestinationInput.tsx index 5c05568b5e..b87fd36ecb 100644 --- a/src/components/transactions/Bridge/BridgeDestinationInput.tsx +++ b/src/components/transactions/Bridge/BridgeDestinationInput.tsx @@ -7,10 +7,10 @@ import { Switch, Typography, } from '@mui/material'; -import { isAddress } from 'ethers/lib/utils'; import { useEffect, useState } from 'react'; import { useIsContractAddress } from 'src/hooks/useIsContractAddress'; -import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; +import { resolveEnsAddress } from 'src/utils/ensClient'; +import { isAddress } from 'viem'; export const BridgeDestinationInput = ({ connectedAccount, @@ -49,14 +49,14 @@ export const BridgeDestinationInput = ({ useEffect(() => { const checkENS = async () => { setValidatingENS(true); - const resolvedAddress = await getENSProvider().resolveName(destinationAccount); + const resolvedAddress = await resolveEnsAddress(destinationAccount); if (resolvedAddress) { setDestinationAccount(resolvedAddress.toLowerCase()); } setValidatingENS(false); }; - if (destinationAccount.slice(-4) === '.eth') { + if (!isAddress(destinationAccount)) { checkENS(); } }, [destinationAccount]); diff --git a/src/libs/hooks/use-get-ens.tsx b/src/libs/hooks/use-get-ens.tsx index 66abfba7ba..5a9a3b0f8f 100644 --- a/src/libs/hooks/use-get-ens.tsx +++ b/src/libs/hooks/use-get-ens.tsx @@ -1,9 +1,8 @@ import { blo } from 'blo'; -import { utils } from 'ethers'; import { useEffect, useState } from 'react'; -import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; - -const mainnetProvider = getENSProvider(); +import { lookupEnsName } from 'src/utils/ensClient'; +import { keccak256, toBytes } from 'viem'; +import { normalize } from 'viem/ens'; interface EnsResponse { name?: string; @@ -13,10 +12,11 @@ interface EnsResponse { const useGetEns = (address: string): EnsResponse => { const [ensName, setEnsName] = useState(undefined); const [ensAvatar, setEnsAvatar] = useState(undefined); + const getName = async (address: string) => { try { - const name = await mainnetProvider.lookupAddress(address); - setEnsName(name ? name : undefined); + const name = await lookupEnsName(address); + setEnsName(name ?? undefined); } catch (error) { console.error('ENS name lookup error', error); } @@ -24,14 +24,14 @@ const useGetEns = (address: string): EnsResponse => { const getAvatar = async (name: string) => { try { - const labelHash = utils.keccak256(utils.toUtf8Bytes(name?.replace('.eth', ''))); + const labelHash = keccak256(toBytes(normalize(name).replace('.eth', ''))); const result: { background_image: string } = await ( await fetch( `https://metadata.ens.domains/mainnet/0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85/${labelHash}/` ) ).json(); setEnsAvatar( - result && result.background_image ? result.background_image : blo(address as `0x${string}`) + result?.background_image ? result.background_image : blo(address as `0x${string}`) ); } catch (error) { console.error('ENS avatar lookup error', error); diff --git a/src/store/utils/domain-fetchers/ens.ts b/src/store/utils/domain-fetchers/ens.ts index b51d00e4e9..85e3c8a3ba 100644 --- a/src/store/utils/domain-fetchers/ens.ts +++ b/src/store/utils/domain-fetchers/ens.ts @@ -1,19 +1,7 @@ import { DomainType, WalletDomain } from 'src/store/walletDomains'; -import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; +import { lookupEnsName } from 'src/utils/ensClient'; import { tFetch } from 'src/utils/tFetch'; -const mainnetProvider = getENSProvider(); - -const getEnsName = async (address: string): Promise => { - try { - const name = await mainnetProvider.lookupAddress(address); - return name; - } catch (error) { - console.error('ENS name lookup error', error); - } - return null; -}; - const getEnsAvatar = async (name: string): Promise => { try { const image = `https://metadata.ens.domains/mainnet/avatar/${name}/`; @@ -25,7 +13,7 @@ const getEnsAvatar = async (name: string): Promise => { }; export const getEnsDomain = async (address: string): Promise => { - const name = await getEnsName(address); + const name = await lookupEnsName(address); if (!name) return null; const avatar = await getEnsAvatar(name); return { name, avatar, type: DomainType.ENS }; diff --git a/src/utils/ensClient.ts b/src/utils/ensClient.ts new file mode 100644 index 0000000000..ede5290a5d --- /dev/null +++ b/src/utils/ensClient.ts @@ -0,0 +1,58 @@ +import { + type Address, + type Chain, + type PublicClient, + createPublicClient, + http, + isAddress, +} from 'viem'; +import { mainnet } from 'viem/chains'; +import { normalize } from 'viem/ens'; + +import { getNetworkConfig } from './marketsAndNetworksConfig'; + +let ensPublicClient: PublicClient | undefined; + +const getEnsPublicClient = (): PublicClient => { + if (!ensPublicClient) { + const { publicJsonRPCUrl } = getNetworkConfig(mainnet.id); + ensPublicClient = createPublicClient({ + transport: http(publicJsonRPCUrl[0]), + chain: mainnet as Chain, + }); + } + return ensPublicClient; +}; + +export const lookupEnsName = async (address: string): Promise => { + if (!isAddress(address)) return null; + + try { + const name = await getEnsPublicClient().getEnsName({ + address, + }); + return name ?? null; + } catch (error) { + console.error('ENS name lookup error', error); + return null; + } +}; + +export const resolveEnsAddress = async (name: string): Promise
=> { + let normalizedName: string; + try { + normalizedName = normalize(name); + } catch { + return null; + } + + try { + const address = await getEnsPublicClient().getEnsAddress({ + name: normalizedName, + }); + return address ?? null; + } catch (error) { + console.error('ENS address lookup error', error); + return null; + } +}; diff --git a/src/utils/marketsAndNetworksConfig.ts b/src/utils/marketsAndNetworksConfig.ts index 28447d104e..37a977ca2f 100644 --- a/src/utils/marketsAndNetworksConfig.ts +++ b/src/utils/marketsAndNetworksConfig.ts @@ -188,12 +188,6 @@ export const getProvider = (chainId: ChainId): ProviderWithSend => { return providers[chainId]; }; -export const getENSProvider = () => { - const chainId = 1; - const config = getNetworkConfig(chainId); - return new StaticJsonRpcProvider(config.publicJsonRPCUrl[0], chainId); -}; - const ammDisableProposal = 'https://governance-v2.aave.com/governance/proposal/44'; const ustDisableProposal = 'https://governance-v2.aave.com/governance/proposal/75'; const kncDisableProposal = 'https://governance-v2.aave.com/governance/proposal/69';