@@ -175,11 +175,21 @@ export class EnsResolver {
175175 // For EIP-2544 names, the ancestor that provided the resolver
176176 #supports2544: null | Promise < boolean > ;
177177
178- #resolver: Contract ;
178+ readonly #resolver: Contract ;
179179
180- constructor ( provider : AbstractProvider , address : string , name : string ) {
180+ readonly #universal: null | Contract ;
181+
182+ constructor ( provider : AbstractProvider , address : string , name : string , universalResolver ?: boolean ) {
181183 defineProperties < EnsResolver > ( this , { provider, address, name } ) ;
182- this . #supports2544 = null ;
184+
185+ if ( universalResolver ) {
186+ this . #supports2544 = Promise . resolve ( true ) ;
187+ this . #universal = createUniversal ( provider , address ) ;
188+ } else {
189+ this . #supports2544 = null ;
190+ this . #universal = null ;
191+ }
192+
183193
184194 this . #resolver = new Contract ( address , [
185195 "function supportsInterface(bytes4) view returns (bool)" ,
@@ -238,18 +248,22 @@ export class EnsResolver {
238248 funcName = "resolve(bytes,bytes)" ;
239249 }
240250
241- params . push ( {
242- enableCcipRead : true
243- } ) ;
251+ params . push ( { enableCcipRead : true } ) ;
244252
245253 try {
246- const result = await this . #resolver[ funcName ] ( ...params ) ;
254+ let result ;
255+ if ( this . #universal) {
256+ result = ( await this . #universal. resolve ( ...params ) ) . result ;
257+ } else {
258+ result = await this . #resolver[ funcName ] ( ...params ) ;
259+ }
247260
248261 if ( fragment ) {
249262 return iface . decodeFunctionResult ( fragment , result ) [ 0 ] ;
250263 }
251264
252265 return result ;
266+
253267 } catch ( error : any ) {
254268 if ( ! isError ( error , "CALL_EXCEPTION" ) ) { throw error ; }
255269 }
@@ -572,11 +586,86 @@ export class EnsResolver {
572586 return null ;
573587 }
574588
589+ static async lookupAddress ( provider : AbstractProvider , address : string , coinType ?: number ) : Promise < null | string > {
590+ address = getAddress ( address ) ;
591+ if ( coinType == null ) { coinType = 60 ; }
592+
593+ // We have a Universal resolver, use it
594+ const universal = await getUniversal ( provider ) ;
595+ if ( universal ) {
596+ const result = await universal . reverse ( address , coinType , {
597+ enableCcipRead : true
598+ } ) ;
599+
600+ return result . primary || null ;
601+ }
602+
603+ // Use legacy reverse lookup
604+
605+ assert ( coinType === 60 , "lookupAddress coinType requires ENS Universal Resolver" , "UNSUPPORTED_OPERATION" , {
606+ operation : "lookupAddress"
607+ } ) ;
608+
609+ try {
610+ // Legacy resolver uses namehash of the lowercase name
611+ const node = namehash ( `${ address . toLowerCase ( ) . substring ( 2 ) } .addr.reverse` ) ;
612+
613+ const ensAddr = await EnsResolver . getEnsAddress ( provider ) ;
614+ const ensContract = new Contract ( ensAddr , [
615+ "function resolver(bytes32) view returns (address)"
616+ ] , provider ) ;
617+
618+ const resolver = await ensContract . resolver ( node ) ;
619+ if ( resolver == null || resolver === ZeroAddress ) { return null ; }
620+
621+ const resolverContract = new Contract ( resolver , [
622+ "function name(bytes32) view returns (string)"
623+ ] , provider ) ;
624+
625+ const name = await resolverContract . name ( node ) ;
626+
627+ // Failed forward resolution
628+ const check = await provider . resolveName ( name ) ;
629+ if ( check !== address ) { return null ; }
630+ return name ;
631+
632+ } catch ( error ) {
633+ // No data was returned from the resolver
634+ if ( isError ( error , "BAD_DATA" ) && error . value === "0x" ) {
635+ return null ;
636+ }
637+
638+ // Something reverted
639+ if ( isError ( error , "CALL_EXCEPTION" ) ) { return null ; }
640+
641+ throw error ;
642+ }
643+
644+ return null ;
645+ }
646+
575647 /**
576648 * Resolve to the ENS resolver for %%name%% using %%provider%% or
577649 * ``null`` if unconfigured.
650+ *
651+ * If %%preferUniversal%% and the network supports ENS Universal
652+ * Resolver, a resolver is always returned (even if the name doesn't
653+ * have one) and calls are resolved by it. The Universal Resolver is
654+ * more eth_call efficient, as it performs more on-chain, but is not
655+ * the actual resolver, if for example the setters are required.
578656 */
579- static async fromName ( provider : AbstractProvider , name : string ) : Promise < null | EnsResolver > {
657+ static async fromName ( provider : AbstractProvider , name : string , preferUniversal ?: boolean ) : Promise < null | EnsResolver > {
658+
659+ // We have a Universal Resolver, use it
660+ const universal = await getUniversal ( provider ) ;
661+ if ( universal ) {
662+ if ( preferUniversal ) {
663+ return new EnsResolver ( provider , < string > universal . target , name , true ) ;
664+ }
665+
666+ const result = await universal . findResolver ( dnsEncode ( name ) ) ;
667+ return new EnsResolver ( provider , result . resolver , name ) ;
668+ }
580669
581670 let currentName = name ;
582671 while ( true ) {
@@ -604,3 +693,23 @@ export class EnsResolver {
604693 }
605694 }
606695}
696+
697+ function createUniversal ( provider : AbstractProvider , address : string ) : Contract {
698+ return new Contract ( address , [
699+ "function findResolver(bytes) view returns (address resolver, bytes32 node, uint offset)" ,
700+ "function resolve(bytes name, bytes data) view returns (bytes result, address resolver)" ,
701+ "function reverse(bytes name, uint coinType) view returns (string primary, address resolver, address reverseResolver)" ,
702+ "error HttpError(uint16 statusCode, string statusMessage)"
703+ ] , provider ) ;
704+ }
705+
706+ async function getUniversal ( provider : AbstractProvider ) : Promise < null | Contract > {
707+ const network = await provider . getNetwork ( ) ;
708+
709+ const ensPlugin = network . getPlugin < EnsPlugin > ( "org.ethers.plugins.network.Ens" ) ;
710+ if ( ensPlugin && ensPlugin . universalResolver ) {
711+ return createUniversal ( provider , ensPlugin . universalResolver ) ;
712+ }
713+
714+ return null ;
715+ }
0 commit comments