diff --git a/src/hooks/useENSAddress.ts b/src/hooks/useENSAddress.ts index a4565d9e95a..8c5496a59ee 100644 --- a/src/hooks/useENSAddress.ts +++ b/src/hooks/useENSAddress.ts @@ -1,5 +1,5 @@ -import { namehash } from '@ethersproject/hash' import { useMemo } from 'react' +import { safeNamehash } from 'utils/safeNamehash' import { useSingleCallResult } from '../state/multicall/hooks' import isZero from '../utils/isZero' @@ -11,14 +11,10 @@ import useDebounce from './useDebounce' */ export default function useENSAddress(ensName?: string | null): { loading: boolean; address: string | null } { const debouncedName = useDebounce(ensName, 200) - const ensNodeArgument = useMemo(() => { - if (!debouncedName) return [undefined] - try { - return debouncedName ? [namehash(debouncedName)] : [undefined] - } catch (error) { - return [undefined] - } - }, [debouncedName]) + const ensNodeArgument = useMemo( + () => [debouncedName === null ? undefined : safeNamehash(debouncedName)], + [debouncedName] + ) const registrarContract = useENSRegistrarContract(false) const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) const resolverAddressResult = resolverAddress.result?.[0] diff --git a/src/hooks/useENSAvatar.ts b/src/hooks/useENSAvatar.ts index 8e6d07d16bc..c7b584d24c3 100644 --- a/src/hooks/useENSAvatar.ts +++ b/src/hooks/useENSAvatar.ts @@ -1,5 +1,6 @@ import { namehash } from '@ethersproject/hash' import { useEffect, useMemo, useState } from 'react' +import { safeNamehash } from 'utils/safeNamehash' import uriToHttp from 'utils/uriToHttp' import { useSingleCallResult } from '../state/multicall/hooks' @@ -21,15 +22,12 @@ export default function useENSAvatar( 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 - } + return namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`) }, [debouncedAddress]) const addressAvatar = useAvatarFromNode(node) - const nameAvatar = useAvatarFromNode(namehash(useENSName(address).ENSName ?? '')) + const ENSName = useENSName(address).ENSName + const nameAvatar = useAvatarFromNode(ENSName === null ? undefined : safeNamehash(ENSName)) let avatar = addressAvatar.avatar || nameAvatar.avatar const nftAvatar = useAvatarFromNFT(avatar, enforceOwnership) diff --git a/src/hooks/useENSContentHash.ts b/src/hooks/useENSContentHash.ts index 9f002d092a5..612e7d7e7a8 100644 --- a/src/hooks/useENSContentHash.ts +++ b/src/hooks/useENSContentHash.ts @@ -1,5 +1,5 @@ -import { namehash } from '@ethersproject/hash' import { useMemo } from 'react' +import { safeNamehash } from 'utils/safeNamehash' import { useSingleCallResult } from '../state/multicall/hooks' import isZero from '../utils/isZero' @@ -9,14 +9,7 @@ import { useENSRegistrarContract, useENSResolverContract } from './useContract' * Does a lookup for an ENS name to find its contenthash. */ export default function useENSContentHash(ensName?: string | null): { loading: boolean; contenthash: string | null } { - const ensNodeArgument = useMemo(() => { - if (!ensName) return [undefined] - try { - return ensName ? [namehash(ensName)] : [undefined] - } catch (error) { - return [undefined] - } - }, [ensName]) + const ensNodeArgument = useMemo(() => [ensName === null ? undefined : safeNamehash(ensName)], [ensName]) const registrarContract = useENSRegistrarContract(false) const resolverAddressResult = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) const resolverAddress = resolverAddressResult.result?.[0] diff --git a/src/hooks/useENSName.ts b/src/hooks/useENSName.ts index f0b7d1d8bf0..414ce9c68f9 100644 --- a/src/hooks/useENSName.ts +++ b/src/hooks/useENSName.ts @@ -15,11 +15,7 @@ export default function useENSName(address?: string): { ENSName: string | null; const debouncedAddress = useDebounce(address, 200) const ensNodeArgument = useMemo(() => { if (!debouncedAddress || !isAddress(debouncedAddress)) return [undefined] - try { - return debouncedAddress ? [namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)] : [undefined] - } catch (error) { - return [undefined] - } + return [namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`)] }, [debouncedAddress]) const registrarContract = useENSRegistrarContract(false) const resolverAddress = useSingleCallResult(registrarContract, 'resolver', ensNodeArgument) diff --git a/src/utils/safeNamehash.test.ts b/src/utils/safeNamehash.test.ts new file mode 100644 index 00000000000..babcd089cfa --- /dev/null +++ b/src/utils/safeNamehash.test.ts @@ -0,0 +1,21 @@ +import { namehash } from '@ethersproject/hash' + +import { safeNamehash } from './safeNamehash' + +describe('#safeNamehash', () => { + const emoji = '🤔' + + it('#namehash fails', () => { + expect(() => namehash(emoji)).toThrow('STRINGPREP_CONTAINS_UNASSIGNED') + }) + + // suppress console.debug for the next test + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, 'debug').mockImplementation(() => {}) + }) + + it('works', () => { + expect(safeNamehash(emoji)).toEqual(undefined) + }) +}) diff --git a/src/utils/safeNamehash.ts b/src/utils/safeNamehash.ts new file mode 100644 index 00000000000..0dc17076a5b --- /dev/null +++ b/src/utils/safeNamehash.ts @@ -0,0 +1,12 @@ +import { namehash } from '@ethersproject/hash' + +export function safeNamehash(name?: string): string | undefined { + if (name === undefined) return undefined + + try { + return namehash(name) + } catch (error) { + console.debug(error) + return undefined + } +}