diff --git a/package.json b/package.json index 93df7fc..84a8ea0 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/contracts": "^5.7.0", + "@ethersproject/hash": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@metamask/jazzicon": "^2.0.0", "@self.id/core": "^0.3.0", diff --git a/src/addressResolvers/utils.ts b/src/addressResolvers/utils.ts index 4362303..da105b6 100644 --- a/src/addressResolvers/utils.ts +++ b/src/addressResolvers/utils.ts @@ -2,7 +2,7 @@ import snapshot from '@snapshot-labs/snapshot.js'; import { getAddress } from '@ethersproject/address'; import { Address, Handle } from '../utils'; -const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000'; +export const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000'; const broviderUrl = process.env.BROVIDER_URL || 'https://rpc.snapshot.org'; diff --git a/src/lookupDomains.ts b/src/lookupDomains.ts index df6c8c3..0eb5886 100644 --- a/src/lookupDomains.ts +++ b/src/lookupDomains.ts @@ -1,7 +1,10 @@ +import snapshot from '@snapshot-labs/snapshot.js'; import { capture } from '@snapshot-labs/snapshot-sentry'; -import { FetchError, isSilencedError } from './addressResolvers/utils'; +import { EMPTY_ADDRESS, isSilencedError } from './addressResolvers/utils'; import { Address, graphQlCall } from './utils'; +import { provider as getProvider } from './addressResolvers/utils'; import { isAddress } from '@ethersproject/address'; +import { namehash } from '@ethersproject/hash'; import constants from './constants.json'; const DEFAULT_CHAIN_ID = '1'; @@ -38,12 +41,42 @@ async function fetchDomainData(domain: Domain, chainId: string): Promise }; } +/* + * see https://docs.ens.domains/registry/reverse + */ +async function getDomainFromReverseRegistrar( + address: Address, + chainId: string +): Promise { + const provider = getProvider(chainId); + const abi = ['function name(bytes32 node) view returns (string r)']; + const reverseName = `${address.toLowerCase().substring(2)}.addr.reverse`; + const hash = namehash(reverseName); + const resolver = await provider.getResolver(reverseName); + + if (!resolver) { + return null; + } + + const resolverAddress = await resolver.address; + + if (!resolverAddress || resolverAddress === EMPTY_ADDRESS) { + return null; + } + + const domainName = await snapshot.utils.call(provider, abi, [resolverAddress, 'name', [hash]]); + + return { name: domainName }; +} + export default async function lookupDomains( address: Address, chainId = DEFAULT_CHAIN_ID ): Promise { if (!isAddress(address) || !constants.ensSubgraph[chainId]) return []; + let domains: Domain[] = []; + try { const { data: { @@ -66,25 +99,30 @@ export default async function lookupDomains( ); const now = (Date.now() / 1000).toFixed(0); - const domains: Domain[] = [ - ...(account?.domains || []), - ...(account?.wrappedDomains || []) - ].filter( + domains = [...(account?.domains || []), ...(account?.wrappedDomains || [])].filter( domain => (!domain.expiryDate || domain.expiryDate === '0' || domain.expiryDate > now) && !domain.name.endsWith('.addr.reverse') ); + } catch (e) { + if (!isSilencedError(e)) { + capture(e, { input: { address } }); + } + } - return ( - (await Promise.all(domains.map(domain => fetchDomainData(domain, chainId)))).map( - domain => domain.name - ) || [] - ); + try { + const results = await Promise.allSettled([ + ...domains.map(domain => fetchDomainData(domain, chainId)), + getDomainFromReverseRegistrar(address, chainId) + ]); + + return results + .filter(result => result.status === 'fulfilled' && result.value !== null) + .map(result => (result as PromiseFulfilledResult).value.name); } catch (e) { - console.log(e); if (!isSilencedError(e)) { capture(e, { input: { address } }); } - throw new FetchError(); + return []; } }