diff --git a/src/components/WalletConnection/ReadOnlyModal.tsx b/src/components/WalletConnection/ReadOnlyModal.tsx index b3db05c6a1..4f0c82208a 100644 --- a/src/components/WalletConnection/ReadOnlyModal.tsx +++ b/src/components/WalletConnection/ReadOnlyModal.tsx @@ -15,13 +15,15 @@ import { ModalType, useModalContext } from 'src/hooks/useModal'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; import { useRootStore } from 'src/store/root'; import { AUTH } from 'src/utils/events'; -import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; +import { getENSClient } from 'src/utils/marketsAndNetworksConfig'; import { normalize } from 'viem/ens'; import { useAccount, useDisconnect } from 'wagmi'; import { BasicModal } from '../primitives/BasicModal'; import { TxModalTitle } from '../transactions/FlowCommons/TxModalTitle'; +const viemClient = getENSClient(); + export const ReadOnlyModal = () => { const { disconnectAsync } = useDisconnect(); const { isConnected } = useAccount(); @@ -31,7 +33,6 @@ 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 => { @@ -43,7 +44,7 @@ export const ReadOnlyModal = () => { 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); + const resolvedAddress = await viemClient.getEnsAddress({ name: normalizedENS }); if (resolvedAddress && utils.isAddress(resolvedAddress)) { saveAndClose(resolvedAddress); } else { diff --git a/src/components/transactions/Bridge/BridgeDestinationInput.tsx b/src/components/transactions/Bridge/BridgeDestinationInput.tsx index 5c05568b5e..0bad1f28f7 100644 --- a/src/components/transactions/Bridge/BridgeDestinationInput.tsx +++ b/src/components/transactions/Bridge/BridgeDestinationInput.tsx @@ -10,7 +10,9 @@ import { import { isAddress } from 'ethers/lib/utils'; import { useEffect, useState } from 'react'; import { useIsContractAddress } from 'src/hooks/useIsContractAddress'; -import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; +import { getENSClient } from 'src/utils/marketsAndNetworksConfig'; + +const viemClient = getENSClient(); export const BridgeDestinationInput = ({ connectedAccount, @@ -49,7 +51,7 @@ export const BridgeDestinationInput = ({ useEffect(() => { const checkENS = async () => { setValidatingENS(true); - const resolvedAddress = await getENSProvider().resolveName(destinationAccount); + const resolvedAddress = await viemClient.getEnsAddress({ name: destinationAccount }); if (resolvedAddress) { setDestinationAccount(resolvedAddress.toLowerCase()); } diff --git a/src/hooks/governance/useGovernanceProposals.ts b/src/hooks/governance/useGovernanceProposals.ts index bb3c100f1d..17fb6f4ee7 100644 --- a/src/hooks/governance/useGovernanceProposals.ts +++ b/src/hooks/governance/useGovernanceProposals.ts @@ -1,7 +1,7 @@ import { ChainId } from '@aave/contract-helpers'; import { normalizeBN } from '@aave/math-utils'; import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; -import { constants, Contract } from 'ethers'; +import { constants } from 'ethers'; import { gql } from 'graphql-request'; import { adaptCacheProposalToDetail, @@ -26,7 +26,7 @@ import { import { useRootStore } from 'src/store/root'; import { governanceV3Config } from 'src/ui-config/governanceConfig'; import { useSharedDependencies } from 'src/ui-config/SharedDependenciesProvider'; -import { getProvider } from 'src/utils/marketsAndNetworksConfig'; +import { getENSClient } from 'src/utils/marketsAndNetworksConfig'; import { subgraphRequest } from 'src/utils/subgraphRequest'; import { getProposal } from './useProposal'; @@ -42,7 +42,7 @@ const USE_GOVERNANCE_CACHE = process.env.NEXT_PUBLIC_USE_GOVERNANCE_CACHE === 't const PAGE_SIZE = 10; const VOTES_PAGE_SIZE = 50; const SEARCH_RESULTS_LIMIT = 10; -export const ENS_REVERSE_REGISTRAR = '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C'; +const viemClient = getENSClient(); // ============================================ // Subgraph search query @@ -78,16 +78,6 @@ const getProposalVotesQuery = gql` } `; -const ensAbi = [ - { - inputs: [{ internalType: 'address[]', name: 'addresses', type: 'address[]' }], - name: 'getNames', - outputs: [{ internalType: 'string[]', name: 'r', type: 'string[]' }], - stateMutability: 'view', - type: 'function', - }, -]; - type SubgraphVote = { proposalId: string; support: boolean; @@ -335,13 +325,14 @@ export const useGovernanceVotersSplit = ( queryFn: async () => { const votes = await fetchSubgraphVotes(proposalId, votingChainId as ChainId); try { - const provider = getProvider(governanceV3Config.coreChainId); - const contract = new Contract(ENS_REVERSE_REGISTRAR, ensAbi); - const connectedContract = contract.connect(provider); - const ensNames: string[] = await connectedContract.getNames(votes.map((v) => v.voter)); + const ensNames = await Promise.all( + votes.map((v) => + viemClient.getEnsName({ address: v.voter as `0x${string}` }).catch(() => null) + ) + ); return votes.map((vote, i) => ({ ...vote, - ensName: ensNames[i] || undefined, + ensName: ensNames[i] ?? undefined, })); } catch { return votes; @@ -364,10 +355,11 @@ export const useGovernanceVotersSplit = ( const { data: cacheEnsNames } = useQuery({ queryFn: async () => { - const provider = getProvider(governanceV3Config.coreChainId); - const contract = new Contract(ENS_REVERSE_REGISTRAR, ensAbi); - const connectedContract = contract.connect(provider); - const names: string[] = await connectedContract.getNames(cacheVoterAddresses); + const names = await Promise.all( + cacheVoterAddresses.map((addr) => + viemClient.getEnsName({ address: addr as `0x${string}` }).catch(() => null) + ) + ); const map: Record = {}; cacheVoterAddresses.forEach((addr, i) => { if (names[i]) map[addr.toLowerCase()] = names[i]; diff --git a/src/hooks/governance/useProposalVotes.ts b/src/hooks/governance/useProposalVotes.ts index 4dc0c46fcc..6e40455322 100644 --- a/src/hooks/governance/useProposalVotes.ts +++ b/src/hooks/governance/useProposalVotes.ts @@ -1,14 +1,11 @@ import { ChainId } from '@aave/contract-helpers'; import { normalizeBN } from '@aave/math-utils'; import { useQuery, UseQueryResult } from '@tanstack/react-query'; -import { Contract } from 'ethers'; import { gql } from 'graphql-request'; import { governanceV3Config } from 'src/ui-config/governanceConfig'; -import { getProvider } from 'src/utils/marketsAndNetworksConfig'; +import { getENSClient } from 'src/utils/marketsAndNetworksConfig'; import { subgraphRequest } from 'src/utils/subgraphRequest'; -import { ENS_REVERSE_REGISTRAR } from './useGovernanceProposals'; - export type ProposalVote = { proposalId: string; support: boolean; @@ -27,20 +24,7 @@ export interface ProposalVotes { isFetching: boolean; } -const abi = [ - { - inputs: [{ internalType: 'contract ENS', name: '_ens', type: 'address' }], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [{ internalType: 'address[]', name: 'addresses', type: 'address[]' }], - name: 'getNames', - outputs: [{ internalType: 'string[]', name: 'r', type: 'string[]' }], - stateMutability: 'view', - type: 'function', - }, -]; +const viemClient = getENSClient(); const getProposalVotes = gql` query getProposalVotes($proposalId: Int!) { @@ -71,11 +55,13 @@ const fetchProposalVotes = async ( })); }; -const fetchProposalVotesEnsNames = async (addresses: string[]) => { - const provider = getProvider(governanceV3Config.coreChainId); - const contract = new Contract(ENS_REVERSE_REGISTRAR, abi); - const connectedContract = contract.connect(provider); - return connectedContract.getNames(addresses) as Promise; +const fetchProposalVotesEnsNames = async (addresses: string[]): Promise => { + const names = await Promise.all( + addresses.map((addr) => + viemClient.getEnsName({ address: addr as `0x${string}` }).catch(() => null) + ) + ); + return names.map((name) => name ?? ''); }; export const useProposalVotesQuery = ({ diff --git a/src/libs/hooks/use-get-ens.tsx b/src/libs/hooks/use-get-ens.tsx index 66abfba7ba..a186e847fd 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'; +import { getENSClient } from 'src/utils/marketsAndNetworksConfig'; -const mainnetProvider = getENSProvider(); +const viemClient = getENSClient(); 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 viemClient.getEnsName({ address: address as `0x${string}` }); + setEnsName(name ?? undefined); } catch (error) { console.error('ENS name lookup error', error); } @@ -24,15 +24,8 @@ const useGetEns = (address: string): EnsResponse => { const getAvatar = async (name: string) => { try { - const labelHash = utils.keccak256(utils.toUtf8Bytes(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}`) - ); + const avatar = await viemClient.getEnsAvatar({ name }); + setEnsAvatar(avatar ?? blo(address as `0x${string}`)); } catch (error) { console.error('ENS avatar lookup error', error); } diff --git a/src/modules/governance/proposal/VotersListItem.tsx b/src/modules/governance/proposal/VotersListItem.tsx index 8e9aa1c446..94a702dc69 100644 --- a/src/modules/governance/proposal/VotersListItem.tsx +++ b/src/modules/governance/proposal/VotersListItem.tsx @@ -1,6 +1,7 @@ import { ExternalLinkIcon } from '@heroicons/react/solid'; import { Avatar, Box, SvgIcon, Typography } from '@mui/material'; import { blo } from 'blo'; +import React, { useEffect, useState } from 'react'; import { FormattedNumber } from 'src/components/primitives/FormattedNumber'; import { Link } from 'src/components/primitives/Link'; import { VoteDisplay } from 'src/modules/governance/types'; @@ -21,6 +22,13 @@ type VotersListItemProps = { export const VotersListItem = ({ compact, voter }: VotersListItemProps): JSX.Element | null => { const { voter: address, ensName } = voter; const blockieAvatar = blo(address !== '' ? (address as `0x${string}`) : '0x'); + const [avatar, setAvatar] = useState(blockieAvatar); + + useEffect(() => { + if (ensName) { + setAvatar(`https://metadata.ens.domains/mainnet/avatar/${ensName}`); + } + }, [ensName]); const trackEvent = useRootStore((store) => store.trackEvent); const displayName = (name?: string) => { @@ -55,7 +63,11 @@ export const VotersListItem = ({ compact, voter }: VotersListItemProps): JSX.Ele - + setAvatar(blockieAvatar) } }} + /> diff --git a/src/store/utils/domain-fetchers/ens.ts b/src/store/utils/domain-fetchers/ens.ts index b51d00e4e9..b3958b6a0f 100644 --- a/src/store/utils/domain-fetchers/ens.ts +++ b/src/store/utils/domain-fetchers/ens.ts @@ -1,13 +1,13 @@ import { DomainType, WalletDomain } from 'src/store/walletDomains'; -import { getENSProvider } from 'src/utils/marketsAndNetworksConfig'; +import { getENSClient } from 'src/utils/marketsAndNetworksConfig'; import { tFetch } from 'src/utils/tFetch'; -const mainnetProvider = getENSProvider(); +const viemClient = getENSClient(); const getEnsName = async (address: string): Promise => { try { - const name = await mainnetProvider.lookupAddress(address); - return name; + const name = await viemClient.getEnsName({ address: address as `0x${string}` }); + return name ?? null; } catch (error) { console.error('ENS name lookup error', error); } diff --git a/src/utils/marketsAndNetworksConfig.ts b/src/utils/marketsAndNetworksConfig.ts index 28447d104e..9925666904 100644 --- a/src/utils/marketsAndNetworksConfig.ts +++ b/src/utils/marketsAndNetworksConfig.ts @@ -1,6 +1,8 @@ import { ChainId, ChainIdToNetwork } from '@aave/contract-helpers'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { ProviderWithSend } from 'src/components/transactions/GovVote/temporary/VotingMachineService'; +import { createPublicClient, http, PublicClient } from 'viem'; +import { mainnet } from 'viem/chains'; import { CustomMarket, @@ -188,10 +190,13 @@ 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); +export const getENSClient = (): PublicClient => { + const config = getNetworkConfig(ChainId.mainnet); + return createPublicClient({ + chain: mainnet, + transport: http(config.publicJsonRPCUrl[0]), + batch: { multicall: true }, + }); }; const ammDisableProposal = 'https://governance-v2.aave.com/governance/proposal/44';