ENSIP Supersession, resolver implementations, and client integrations

On a recent ecosystem call I asked a query about the supersession of previous standards in the context of ENSIPs. I had written resolvers that I believed to be to spec but in certain Wallet clients and libraries they were not working as expected. Some of the issues were on my end, others were in the client implementations.

Having read through all of the ENSIPs again I think it would be extremely useful if ENSIPs (or sections of them) that have been superseded by another ENSIP could have notices added which forward users to the latest related specification. Thoughts?

As regards incorrect client implementations I have found some of the specs to be a little confusing. I do appreciate that many were written many many years ago. I was hoping that other ecosystem participants could provide clarity where my understanding is incorrect…

Some context

As it relates to forward name resolution we currently have the following ENSIPs that I have considered here.

ENSIP-1

Resolvers MUST specify a fallback function that throws.

I see a lot of resolvers that do not do this.

Resolvers wishing to support contract address resources must provide the following function.

This is clear but there is no similar clarity on what clients should do to correctly integrate the protocol. I appreciate that in the early days a client could reasonably assume that something labelled as a resolver by the Registry would implement addr(node) but as the protocol has developed and extended there is a solid (in my opinion) argument for now requiring clients to call the ERC165 supportsInterface method to discern the appropriate resolution path. I feel as though clarity on precedence should also be codified (discussed further down).

I use ethers.js as an example as it is the client library with which I have the most experience. Within the ethers.js ENS resolver functionality resolution checks if a resolver supports Wildcard Resolution and if it does not calls addr(node).

ENSIP 9

ENSIP-9 does not specify any requirement to implement supportsInterface for its respective Interface ID. This is inconsistent with other ENSIPs.

With ethers.js if someone does not implement ENSIP-10 but does implement ENSIP-9 they necessarily have to support ENSIP-1 for support when resolving chain ID 60, Mainnet Ethereum.

What is the expectation? Is the idea that the entry point for mainnet resolution is always addr(node)? That seems to be what is being assumed here. I have no issue with that assumption, I just think it needs clarifying.

The backwards compatibility section notes:

If the resolver supports the addr(bytes32) interface

The implication being that ENSIP-9 can be implemented without needing ENSIP-1 addr(node) to be implemented and as such my resolver that does so should really work with common client libraries.

ENSIP 10

I have outlined my understanding of ENSIP-10 below. I have spent numerous hours re-reading this, and looking at/testing client implementations.

ENSIP-10 is named Wildcard Resolution yet it holds the specification for the IExtendedResolver interface and notes explicitly:

“Resolvers wishing to implement the new resolve function for non-wildcard use-cases (eg, where the resolver is set directly on the name being resolved) should consider what to return to legacy clients that call the individual resolution functions for maximum compatibility.”

The implication is that its contents don’t specifically pertain to Wildcard Resolution and that resolve can be called with a current name that is equal to the queried name.

In principle a to specification resolver could be set on a 2LD and implement resolve standalone without any connection to subnames or wildcard resolution which makes the naming of the ENSIP a little confusing.

The latter part of that statement implies that you can implement both resolve and the ENSIP-1 addr method (for example), and that you probably should for backwards compatibility with clients that are not up to date with the latest specs.

My read is that the IExtendedResolver interface is the generic resolution entry point and the wildcard aspect is simply ‘current name != queried name’.

The spec continues on to state:

  1. If supportsENSIP10 is false and name == currentname, set result to the result of calling resolver with calldata.

So in the non-wildcard case we call the resolver directly with the calldata completely bypassing the (not implemented) resolve method.

This then comes full circle to that outlined in the section on ENSIP-9. In this scenario ethers.js will call through to addr(node) assuming its existence.

Thoughts

There are a few inline questions in the body above.

My only other question is as to what clients should do in terms of discoverability and precedence moving forward. Is there potential for improvements in this regard?

In my opinion an ideal client implementation should call supportsInterface for the available resolution interfaces - IExtendedResolver, the ENSIP-9 addr(node,cointype) interface, and the ENSIP-1 addr(node) interface. This should be done up front using a multicall. The results should be considered in that order of precedence.

If IExtendedResolver returns true, find the appropriate resolver and call resolve. If it returns false call the next implemented addr. If none are implemented => 0x

I.E. Determine the available resolution paths, use the most ‘modern’.

What do others thing about this?

4 Likes

I think a better way to do this would probably be to have a “living document” that describes the current ENS system in detail equivalent to the ENSIPs, with citations. This is similar to how legislation is handled; you can see both the bill that made an amendment, and the law with all the amendments applied.

When this was written, Solidity did not automatically generate a fallback function that throws. Now it generates one automatically.

If a client is trying to resolve one specific type of record, it can simply call the relevant function, and fail if it gets a revert. If it has multiple options for how to resolve (as is the case with addr-with-cointype vs legacy addr, or with support for resolve), it can use supportsInterface to determine which ones are supported. I believe this is spelled out in the relevant standards.

That’s definitely an accidental omission, good catch. The sample code includes it but it’s nowhere in the text.

If a resolver supports ENSIP-9, it must return the same result for addr(node, 60) as for addr(node). This is described in the “backwards compatibility” section.

That’s correct.

That’s correct; ENSIP-10 exists primarily to enable wildcard resolution, but it has non-wildcard use-cases as well. As you observe, resolve can be called for an exact match.

Also correct.

:100:

Yes, that’s a reasonable approach to take.

2 Likes