import { namehash } from '@ethersproject/hash' import { useEffect, useMemo, useState } from 'react' import uriToHttp from 'utils/uriToHttp' import { useSingleCallResult } from '../state/multicall/hooks' import { isAddress } from '../utils' import isZero from '../utils/isZero' import { useENSRegistrarContract, useENSResolverContract, useERC721Contract, useERC1155Contract } from './useContract' import useDebounce from './useDebounce' import useENSName from './useENSName' import { useActiveWeb3React } from './web3' /** * Returns the ENS avatar URI, if available. * Spec: https://gist.github.com/Arachnid/9db60bd75277969ee1689c8742b75182. */ export default function useENSAvatar( address?: string, enforceOwnership = true ): { avatar: string | null; loading: boolean } { const debouncedAddress = useDebounce(address, 200) const node = useMemo(() => { if (!debouncedAddress || !isAddress(debouncedAddress)) return undefined try { return debouncedAddress ? namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`) : undefined } catch (error) { return undefined } }, [debouncedAddress]) const addressAvatar = useAvatarFromNode(node) const nameAvatar = useAvatarFromNode(namehash(useENSName(address).ENSName ?? '')) let avatar = addressAvatar.avatar || nameAvatar.avatar const nftAvatar = useAvatarFromNFT(avatar, enforceOwnership) avatar = nftAvatar.avatar || avatar const http = avatar && uriToHttp(avatar)[0] const changed = debouncedAddress !== address return { avatar: changed ? null : http ?? null, loading: changed || addressAvatar.loading || nameAvatar.loading || nftAvatar.loading, } } 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 resolverAddressResult = resolverAddress.result?.[0] const resolverContract = useENSResolverContract( resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined, false ) const avatar = useSingleCallResult(resolverContract, 'text', textArgument) return { avatar: avatar.result?.[0], loading: resolverAddress.loading || avatar.loading, } } function useAvatarFromNFT(nftUri = '', enforceOwnership: boolean): { avatar?: string; loading: boolean } { const parts = nftUri.toLowerCase().split(':') const protocol = parts[0] // ignore the chain from eip155 // TODO: when we are able, pull only from the specified chain const [, erc] = parts[1]?.split('/') ?? [] const [contractAddress, id] = parts[2]?.split('/') ?? [] const isERC721 = protocol === 'eip155' && erc === 'erc721' const isERC1155 = protocol === 'eip155' && erc === 'erc1155' const erc721 = useERC721Uri(isERC721 ? contractAddress : undefined, id, enforceOwnership) const erc1155 = useERC1155Uri(isERC1155 ? contractAddress : undefined, id, enforceOwnership) const uri = erc721.uri || erc1155.uri const http = uri && uriToHttp(uri)[0] const [loading, setLoading] = useState(false) const [avatar, setAvatar] = useState(undefined) useEffect(() => { setAvatar(undefined) if (http) { setLoading(true) fetch(http) .then((res) => res.json()) .then(({ image }) => { setAvatar(image) }) .catch((e) => console.warn(e)) .finally(() => { setLoading(false) }) } }, [http]) return { avatar, loading: erc721.loading || erc1155.loading || loading } } function useERC721Uri( contractAddress: string | undefined, id: string | undefined, enforceOwnership: boolean ): { uri?: string; loading: boolean } { const idArgument = useMemo(() => [id], [id]) const { account } = useActiveWeb3React() const contract = useERC721Contract(contractAddress) const owner = useSingleCallResult(contract, 'ownerOf', idArgument) const uri = useSingleCallResult(contract, 'tokenURI', idArgument) return { uri: !enforceOwnership || account === owner.result?.[0] ? uri.result?.[0] : undefined, loading: owner.loading || uri.loading, } } function useERC1155Uri( contractAddress: string | undefined, id: string | undefined, enforceOwnership: boolean ): { uri?: string; loading: boolean } { const { account } = useActiveWeb3React() 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) return { uri: !enforceOwnership || balance.result?.[0] > 0 ? uri.result?.[0] : undefined, loading: balance.loading || uri.loading, } }