Commit 8ed6481f authored by Brendan Wong's avatar Brendan Wong Committed by GitHub

fix: display ens name/avatar on other chains (#6981)

* maintain ens info on other chains

* update ens hook

* feedback!

* initial test

* test progress

* e2e test?

* final feedback

* fix(lint): rm unused test var

* fix final bugs

* last lint issue

* fix: update catches

* fix: define catch handlers

* fix: use error in catch

* empty commit

---------
Co-authored-by: default avatarZach Pomerantz <zzmp@uniswap.org>
parent d160d1a9
......@@ -77,4 +77,36 @@ describe('Mini Portfolio account drawer', () => {
})
})
})
it('fetches ENS name', () => {
cy.hardhat().then(() => {
const haydenAccount = '0x50EC05ADe8280758E2077fcBC08D878D4aef79C3'
const haydenENS = 'hayden.eth'
// Opens the account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// Simulate wallet changing to Hayden's account
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
// Hayden's ENS name should be shown
cy.contains(haydenENS).should('exist')
// Close account drawer
cy.get(getTestSelector('close-account-drawer')).click()
// Switch chain to Polygon
cy.get(getTestSelector('chain-selector')).eq(1).click()
cy.contains('Polygon').click()
//Reopen account drawer
cy.get(getTestSelector('web3-status-connected')).click()
// Simulate wallet changing to Hayden's account
cy.window().then((win) => win.ethereum.emit('accountsChanged', [haydenAccount]))
// Hayden's ENS name should be shown
cy.contains(haydenENS).should('exist')
})
})
})
......@@ -12,6 +12,7 @@ import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import Tooltip from 'components/Tooltip'
import { getConnection } from 'connection'
import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes'
import useENSName from 'hooks/useENSName'
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
import { ProfilePageStateType } from 'nft/types'
......@@ -161,7 +162,8 @@ const LogOutCentered = styled(LogOut)`
`
export default function AuthenticatedHeader({ account, openSettings }: { account: string; openSettings: () => void }) {
const { connector, ENSName } = useWeb3React()
const { connector } = useWeb3React()
const { ENSName } = useENSName(account)
const dispatch = useAppDispatch()
const navigate = useNavigate()
const closeModal = useCloseModal()
......
......@@ -7,6 +7,7 @@ import PrefetchBalancesWrapper from 'components/AccountDrawer/PrefetchBalancesWr
import Loader from 'components/Icons/LoadingSpinner'
import { IconWrapper } from 'components/Identicon/StatusIcon'
import { getConnection } from 'connection'
import useENSName from 'hooks/useENSName'
import useLast from 'hooks/useLast'
import { navSearchInputVisibleSize } from 'hooks/useScreenSize'
import { Portal } from 'nft/components/common/Portal'
......@@ -135,7 +136,8 @@ const StyledConnectButton = styled.button`
function Web3StatusInner() {
const switchingChain = useAppSelector((state) => state.wallets.switchingChain)
const ignoreWhileSwitchingChain = useCallback(() => !switchingChain, [switchingChain])
const { account, connector, ENSName } = useLast(useWeb3React(), ignoreWhileSwitchingChain)
const { account, connector } = useLast(useWeb3React(), ignoreWhileSwitchingChain)
const { ENSName } = useENSName(account)
const connection = getConnection(connector)
const [, toggleAccountDrawer] = useAccountDrawer()
......
import { Contract } from '@ethersproject/contracts'
import {
ARGENT_WALLET_DETECTOR_ADDRESS,
ChainId,
ENS_REGISTRAR_ADDRESSES,
MULTICALL_ADDRESSES,
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
......@@ -28,6 +29,7 @@ import ERC721_ABI from 'abis/erc721.json'
import ERC1155_ABI from 'abis/erc1155.json'
import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Erc721, Erc1155, Weth } from 'abis/types'
import WETH_ABI from 'abis/weth.json'
import { RPC_PROVIDERS } from 'constants/providers'
import { WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { useMemo } from 'react'
import { NonfungiblePositionManager, Quoter, QuoterV2, TickLens, UniswapInterfaceMulticall } from 'types/v3'
......@@ -66,6 +68,23 @@ export function useContract<T extends Contract = Contract>(
}, [addressOrAddressMap, ABI, provider, chainId, withSignerIfPossible, account]) as T
}
function useMainnetContract<T extends Contract = Contract>(address: string | undefined, ABI: any): T | null {
const { chainId } = useWeb3React()
const isMainnet = chainId === ChainId.MAINNET
const contract = useContract(isMainnet ? address : undefined, ABI, false)
return useMemo(() => {
if (isMainnet) return contract
if (!address) return null
const provider = RPC_PROVIDERS[ChainId.MAINNET]
try {
return getContract(address, ABI, provider)
} catch (error) {
console.error('Failed to get mainnet contract', error)
return null
}
}, [address, ABI, contract, isMainnet]) as T
}
export function useV2MigratorContract() {
return useContract<V3Migrator>(V3_MIGRATOR_ADDRESSES, V2MigratorABI, true)
}
......@@ -95,12 +114,12 @@ export function useArgentWalletDetectorContract() {
return useContract<ArgentWalletDetector>(ARGENT_WALLET_DETECTOR_ADDRESS, ARGENT_WALLET_DETECTOR_ABI, false)
}
export function useENSRegistrarContract(withSignerIfPossible?: boolean) {
return useContract<EnsRegistrar>(ENS_REGISTRAR_ADDRESSES, ENS_ABI, withSignerIfPossible)
export function useENSRegistrarContract() {
return useMainnetContract<EnsRegistrar>(ENS_REGISTRAR_ADDRESSES[ChainId.MAINNET], ENS_ABI)
}
export function useENSResolverContract(address: string | undefined, withSignerIfPossible?: boolean) {
return useContract<EnsPublicResolver>(address, ENS_PUBLIC_RESOLVER_ABI, withSignerIfPossible)
export function useENSResolverContract(address: string | undefined) {
return useMainnetContract<EnsPublicResolver>(address, ENS_PUBLIC_RESOLVER_ABI)
}
export function useBytes32TokenContract(tokenAddress?: string, withSignerIfPossible?: boolean): Contract | null {
......@@ -123,6 +142,13 @@ export function useInterfaceMulticall() {
return useContract<UniswapInterfaceMulticall>(MULTICALL_ADDRESSES, MulticallABI, false) as UniswapInterfaceMulticall
}
export function useMainnetInterfaceMulticall() {
return useMainnetContract<UniswapInterfaceMulticall>(
MULTICALL_ADDRESSES[ChainId.MAINNET],
MulticallABI
) as UniswapInterfaceMulticall
}
export function useV3NFTPositionManagerContract(withSignerIfPossible?: boolean): NonfungiblePositionManager | null {
return useContract<NonfungiblePositionManager>(
NONFUNGIBLE_POSITION_MANAGER_ADDRESSES,
......
import { namehash } from '@ethersproject/hash'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { NEVER_RELOAD, useMainnetSingleCallResult } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import isZero from '../utils/isZero'
......@@ -12,14 +12,13 @@ import useDebounce from './useDebounce'
export default function useENSAddress(ensName?: string | null): { loading: boolean; address: string | null } {
const debouncedName = useDebounce(ensName, 200)
const ensNodeArgument = useMemo(() => [debouncedName ? namehash(debouncedName) : undefined], [debouncedName])
const registrarContract = useENSRegistrarContract(false)
const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument)
const registrarContract = useENSRegistrarContract()
const resolverAddress = useMainnetSingleCallResult(registrarContract, 'resolver', ensNodeArgument, NEVER_RELOAD)
const resolverAddressResult = resolverAddress.result?.[0]
const resolverContract = useENSResolverContract(
resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined,
false
resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined
)
const addr = useSingleCallResult(resolverContract, 'addr', ensNodeArgument)
const addr = useMainnetSingleCallResult(resolverContract, 'addr', ensNodeArgument, NEVER_RELOAD)
const changed = debouncedName !== ensName
return useMemo(
......
......@@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { hexZeroPad } from '@ethersproject/bytes'
import { namehash } from '@ethersproject/hash'
import { useWeb3React } from '@web3-react/core'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { NEVER_RELOAD, useMainnetSingleCallResult } from 'lib/hooks/multicall'
import uriToHttp from 'lib/utils/uriToHttp'
import { useEffect, useMemo, useState } from 'react'
......@@ -49,14 +49,13 @@ export default function useENSAvatar(
function useAvatarFromNode(node?: string): { avatar?: string; loading: boolean } {
const nodeArgument = useMemo(() => [node], [node])
const textArgument = useMemo(() => [node, 'avatar'], [node])
const registrarContract = useENSRegistrarContract(false)
const resolverAddress = useSingleCallResult(registrarContract, 'resolver', nodeArgument)
const registrarContract = useENSRegistrarContract()
const resolverAddress = useMainnetSingleCallResult(registrarContract, 'resolver', nodeArgument, NEVER_RELOAD)
const resolverAddressResult = resolverAddress.result?.[0]
const resolverContract = useENSResolverContract(
resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined,
false
resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined
)
const avatar = useSingleCallResult(resolverContract, 'text', textArgument)
const avatar = useMainnetSingleCallResult(resolverContract, 'text', textArgument, NEVER_RELOAD)
return useMemo(
() => ({
......@@ -113,8 +112,8 @@ function useERC721Uri(
const idArgument = useMemo(() => [id], [id])
const { account } = useWeb3React()
const contract = useERC721Contract(contractAddress)
const owner = useSingleCallResult(contract, 'ownerOf', idArgument)
const uri = useSingleCallResult(contract, 'tokenURI', idArgument)
const owner = useMainnetSingleCallResult(contract, 'ownerOf', idArgument, NEVER_RELOAD)
const uri = useMainnetSingleCallResult(contract, 'tokenURI', idArgument, NEVER_RELOAD)
return useMemo(
() => ({
uri: !enforceOwnership || account === owner.result?.[0] ? uri.result?.[0] : undefined,
......@@ -133,8 +132,8 @@ function useERC1155Uri(
const idArgument = useMemo(() => [id], [id])
const accountArgument = useMemo(() => [account || '', id], [account, id])
const contract = useERC1155Contract(contractAddress)
const balance = useSingleCallResult(contract, 'balanceOf', accountArgument)
const uri = useSingleCallResult(contract, 'uri', idArgument)
const balance = useMainnetSingleCallResult(contract, 'balanceOf', accountArgument, NEVER_RELOAD)
const uri = useMainnetSingleCallResult(contract, 'uri', idArgument, NEVER_RELOAD)
return useMemo(() => {
try {
// ERC-1155 allows a generic {id} in the URL, so prepare to replace if relevant,
......
import { namehash } from '@ethersproject/hash'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { NEVER_RELOAD, useMainnetSingleCallResult } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import isZero from '../utils/isZero'
......@@ -10,14 +10,13 @@ import { useENSRegistrarContract, useENSResolverContract } from './useContract'
*/
export default function useENSContentHash(ensName?: string | null): { loading: boolean; contenthash: string | null } {
const ensNodeArgument = useMemo(() => [ensName ? namehash(ensName) : undefined], [ensName])
const registrarContract = useENSRegistrarContract(false)
const resolverAddressResult = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument)
const registrarContract = useENSRegistrarContract()
const resolverAddressResult = useMainnetSingleCallResult(registrarContract, 'resolver', ensNodeArgument, NEVER_RELOAD)
const resolverAddress = resolverAddressResult.result?.[0]
const resolverContract = useENSResolverContract(
resolverAddress && isZero(resolverAddress) ? undefined : resolverAddress,
false
resolverAddress && isZero(resolverAddress) ? undefined : resolverAddress
)
const contenthash = useSingleCallResult(resolverContract, 'contenthash', ensNodeArgument)
const contenthash = useMainnetSingleCallResult(resolverContract, 'contenthash', ensNodeArgument, NEVER_RELOAD)
return useMemo(
() => ({
......
import { namehash } from '@ethersproject/hash'
import { useSingleCallResult } from 'lib/hooks/multicall'
import { NEVER_RELOAD, useMainnetSingleCallResult } from 'lib/hooks/multicall'
import { useMemo } from 'react'
import { isAddress } from '../utils'
......@@ -18,14 +18,13 @@ export default function useENSName(address?: string): { ENSName: string | null;
if (!debouncedAddress || !isAddress(debouncedAddress)) return [undefined]
return [namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)]
}, [debouncedAddress])
const registrarContract = useENSRegistrarContract(false)
const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument)
const registrarContract = useENSRegistrarContract()
const resolverAddress = useMainnetSingleCallResult(registrarContract, 'resolver', ensNodeArgument, NEVER_RELOAD)
const resolverAddressResult = resolverAddress.result?.[0]
const resolverContract = useENSResolverContract(
resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined,
false
resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined
)
const nameCallRes = useSingleCallResult(resolverContract, 'name', ensNodeArgument)
const nameCallRes = useMainnetSingleCallResult(resolverContract, 'name', ensNodeArgument, NEVER_RELOAD)
const name = nameCallRes.result?.[0]
// ENS does not enforce that an address owns a .eth domain before setting it as a reverse proxy
......
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import useBlockNumber, { useMainnetBlockNumber } from 'lib/hooks/useBlockNumber'
import multicall from 'lib/state/multicall'
import { SkipFirst } from 'types/tuple'
......@@ -22,6 +23,11 @@ export function useSingleCallResult(...args: SkipFirstTwoParams<typeof multicall
return multicall.hooks.useSingleCallResult(chainId, latestBlock, ...args)
}
export function useMainnetSingleCallResult(...args: SkipFirstTwoParams<typeof multicall.hooks.useSingleCallResult>) {
const latestMainnetBlock = useMainnetBlockNumber()
return multicall.hooks.useSingleCallResult(ChainId.MAINNET, latestMainnetBlock, ...args)
}
export function useSingleContractMultipleData(
...args: SkipFirstTwoParams<typeof multicall.hooks.useSingleContractMultipleData>
) {
......
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { RPC_PROVIDERS } from 'constants/providers'
import useIsWindowVisible from 'hooks/useIsWindowVisible'
import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'
......@@ -7,6 +9,7 @@ const BlockNumberContext = createContext<
| {
value?: number
fastForward(block: number): void
mainnetValue?: number
}
| typeof MISSING_PROVIDER
>(MISSING_PROVIDER)
......@@ -28,23 +31,34 @@ export function useFastForwardBlockNumber(): (block: number) => void {
return useBlockNumberContext().fastForward
}
export function useMainnetBlockNumber(): number | undefined {
return useBlockNumberContext().mainnetValue
}
export function BlockNumberProvider({ children }: { children: ReactNode }) {
const { chainId: activeChainId, provider } = useWeb3React()
const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId })
const [{ chainId, block, mainnetBlock }, setChainBlock] = useState<{
chainId?: number
block?: number
mainnetBlock?: number
}>({
chainId: activeChainId,
})
const onBlock = useCallback(
(block: number) => {
setChainBlock((chainBlock) => {
if (chainBlock.chainId === activeChainId) {
if (!chainBlock.block || chainBlock.block < block) {
return { chainId: activeChainId, block }
}
const onChainBlock = useCallback((chainId: number, block: number) => {
setChainBlock((chainBlock) => {
if (chainBlock.chainId === chainId) {
if (!chainBlock.block || chainBlock.block < block) {
return { chainId, block, mainnetBlock: chainId === ChainId.MAINNET ? block : chainBlock.mainnetBlock }
}
return chainBlock
})
},
[activeChainId, setChainBlock]
)
} else if (chainId === ChainId.MAINNET) {
if (!chainBlock.mainnetBlock || chainBlock.mainnetBlock < block) {
return { ...chainBlock, mainnetBlock: block }
}
}
return chainBlock
})
}, [])
const windowVisible = useIsWindowVisible()
useEffect(() => {
......@@ -52,17 +66,22 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
if (provider && activeChainId && windowVisible) {
// If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data.
setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId }))
setChainBlock((chainBlock) =>
chainBlock.chainId === activeChainId
? chainBlock
: { chainId: activeChainId, mainnetBlock: chainBlock.mainnetBlock }
)
provider
.getBlockNumber()
.then((block) => {
if (!stale) onBlock(block)
if (!stale) onChainBlock(activeChainId, block)
})
.catch((error) => {
console.error(`Failed to get block number for chainId ${activeChainId}`, error)
})
const onBlock = (block: number) => onChainBlock(activeChainId, block)
provider.on('block', onBlock)
return () => {
stale = true
......@@ -71,7 +90,19 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
}
return void 0
}, [activeChainId, provider, onBlock, setChainBlock, windowVisible])
}, [activeChainId, provider, windowVisible, onChainBlock])
useEffect(() => {
if (mainnetBlock === undefined) {
RPC_PROVIDERS[ChainId.MAINNET]
.getBlockNumber()
.then((block) => {
onChainBlock(ChainId.MAINNET, block)
})
// swallow errors - it's ok if this fails, as we'll try again if we activate mainnet
.catch(() => undefined)
}
}, [mainnetBlock, onChainBlock])
const value = useMemo(
() => ({
......@@ -81,8 +112,9 @@ export function BlockNumberProvider({ children }: { children: ReactNode }) {
setChainBlock({ chainId: activeChainId, block: update })
}
},
mainnetValue: mainnetBlock,
}),
[activeChainId, block, chainId]
[activeChainId, block, chainId, mainnetBlock]
)
return <BlockNumberContext.Provider value={value}>{children}</BlockNumberContext.Provider>
}
import { createMulticall, ListenerOptions } from '@uniswap/redux-multicall'
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { useInterfaceMulticall } from 'hooks/useContract'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import { useInterfaceMulticall, useMainnetInterfaceMulticall } from 'hooks/useContract'
import useBlockNumber, { useMainnetBlockNumber } from 'lib/hooks/useBlockNumber'
import { useMemo } from 'react'
const multicall = createMulticall()
......@@ -33,20 +33,38 @@ function getBlocksPerFetchForChainId(chainId: number | undefined): number {
export function MulticallUpdater() {
const { chainId } = useWeb3React()
const latestBlockNumber = useBlockNumber()
const latestMainnetBlockNumber = useMainnetBlockNumber()
const contract = useInterfaceMulticall()
const mainnetContract = useMainnetInterfaceMulticall()
const listenerOptions: ListenerOptions = useMemo(
() => ({
blocksPerFetch: getBlocksPerFetchForChainId(chainId),
}),
[chainId]
)
const mainnetListener: ListenerOptions = useMemo(
() => ({
blocksPerFetch: getBlocksPerFetchForChainId(ChainId.MAINNET),
}),
[]
)
return (
<multicall.Updater
chainId={chainId}
latestBlockNumber={latestBlockNumber}
contract={contract}
listenerOptions={listenerOptions}
/>
<>
<multicall.Updater
chainId={ChainId.MAINNET}
latestBlockNumber={latestMainnetBlockNumber}
contract={mainnetContract}
listenerOptions={mainnetListener}
/>
{chainId !== ChainId.MAINNET && (
<multicall.Updater
chainId={chainId}
latestBlockNumber={latestBlockNumber}
contract={contract}
listenerOptions={listenerOptions}
/>
)}
</>
)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment