Introducing a pseudo-namespace for ENS contract discovery

There are a few well-known contracts throughout ENS that tools such as the manager app need to look up - a primary example is the .eth registrar controller contract. Currently this is handled via the interface discovery method described in ENSIP 8, which works fine because there is a natural name associated with the contract already (in this case, .eth).

However, we’re likely to see the ENS ecosystem develop more special-purpose utility contracts. One example is the name wrapper, which will permit applying restrictions to names and a lot of other functionality; this is intended to have only a single implementation, and has no obvious name of its own.

Another example is a ‘universal resolver’, a contract that can perform all the resolution steps for a name other than normalisation. Up until now, resolving an ENS name has only required two contract calls, so such a construct was mostly useful for bulk lookups, but with ENS on L2 (ENSIP 10 and EIP 3668) this is going to change; a single lookup will require at least 3 calls, and will require more if the name uses wildcards or CCIP read to fetch offchain data. A universal resolver can perform most of these steps in a single call, substantially reducing latency.

We could simply assign these contracts names under ‘ens.eth’ or ‘ensdao.eth’, but this comes with a number of shortcomings:

  • The names could expire (though this seems unlikely)
  • Unless the owner of the parent name uses the name wrapper to revoke permissions, they could change the contract for that name, resulting in behavioural changes.
  • Looking up these names requires following the resolution process, which adds significant overhead to discovery; ironic if you’re looking up the universal resolver for example.

I’d like to propose creating a new pseudo-TLD specifically for discovery of system-internal contracts such as the name wrapper and universal resolver. A natural name for this would be ‘_’, but we could even make this pseudo-TLD a hash with no preimage - for example the zero hash or some other similar example, in which case it would have no textual representation.

In either case, the pseudo-TLD would be owned by a contract that exposes a function to insert or update a contract address. When called by an authorised caller with an address and name, it would:

  1. Create or update the subdomain name._ and set its resolver address to the address supplied.
  2. Create, where x is a sequentially increasing version number, and set its resolver address to the address supplied.

Note that this pseudo-TLD would use the resolver record to store the addresses of these internal contracts, despite them not being resolvers; this saves a lookup step and the need to provision a resolver for each, which seems a worthwhile tradeoff for this discovery mechanism.

The intention then is that libraries such as ethers could fetch, for example, 1.universalresolver._ and know for certain that this will remain the same; they can opt in to upgrades by using the new version manually. Or they can fetch universalresolver._ and always get the latest version.



I think the moment anyone hears, that another TLD is created next to .eth, they will start finger pointing not even fiercely but ferociously. They wouldn’t care if its very technical in nature. Next they will start demanding more TLD and game over. Some projects are already using .eth names for technical purposes - Tornado cash or Uniswap, so why break the approach which has already taken some root. I think in case of ENS to become truly global it has to be very consistent.


Can you not just whitelist special purpose domain (eg: _.eth ) and embed the name into the protocol so that you don’t need the additional resolution if domain.match(/\._\.eth$/)? Hardcoding the exception rule into the resolution process is less messy than violating the tld rules which we set up on our own under our constitution.

Maybe this is unrelated, but an idea I have is to crowdfund a domain that can be used by anyone that is registered basically forever. I think the community would easily get it registered for 1000 or more years, if the domain was owned by a smart contract, and it allowed for anyone to register subdomains for free on it.

I see .eth becoming a super TLD, so basically all second level domains become like regular TLDs. This is possible because of the composable design. Also many countries use a double TLD system, e.g. I think scheduling an auction for two letter and even one letter would be a good idea at some point. domain.x.eth looks cool and adds a lot more domains into existence.

1 Like

Yea this makes sense. I would go with ._ because it’s easier for developers to just plug that into a namehash if they are referencing it manually. It’s not a TLD meant to be sold, it’s just an “internal” reference, and it doesn’t conflict with the DNS namespace. :+1:

1 Like

I think modifying the contracts and the clients like this is much messier than simply reserving a pseudo-TLD for it. If people are really concerned about people treating this like a “real” TLD, we could make the label hash not have a preimage at all - so it would be unresolvable in text form. The downside of this is that it makes it more complex to use, as you can’t use standard resolution methods to look it up.

1 Like

can’t caching mitigate this?
(assuming this contracts addresses doesn’t change often)

1 Like

Caching reduces the average latency, but not the maximum latency, and it doesn’t remove the complexity of having to use the normal lookup process in order to look up the contract that lets you avoid having to do the normal lookup process.

The constitution says this:

Not permissible : ENS governance must not create new top-level domains unless those domains have been granted to ENS by a DNS authority.

Is it fair to say that there is 0 chance ._ would ever be considered a valid TLD in the future?

It seems like this change would introduce a couple of extra idiosyncrasy “gotchas” to the project. First is the ._, and second is the idea that as a special case we’d use the resolver record to store the addresses of the contracts despite them not necessarily being resolvers.

It would make more sense to me to store these contract addresses as the ETH address record for names under ens.eth, like 1.universalresolver.contracts.ens.eth or something.

Who owns ens.eth right now, is it TNL? If so, is there any reason why that ownership couldn’t be transferred to the DAO? Then I don’t think there would be any worries about the name expiring or having the owner of the name making unexpected updates.

I suppose it’s true that looking up the contract addresses in the normal way would have overhead, but how often are you going to need to do that anyway? It seems like we’d be swapping one complexity (of using the normal lookup process) with a process that might be even more complex with its own special idiosyncratic rules, all just to reduce maximum latency.

But you’re the lead developer and would certainly know best just how onerous the extra calls and extra maximum latency would be. Is there like a worst-case scenario you can walk us through that shows just how beneficial this change would be for the end-user?

I think it is. Underscore is not a permitted character in DNS names as a rule, and is used exclusively for ‘well known’ names like SPF and DMARC records (eg,

This is how things are currently handled. But aside from the drawbacks I outlined above, it requires the caller to implement the full ENS resolution process in order to fetch the address. When what you’re trying to fetch is the address of a contract that does ENS resolution for you (so it’s faster, and so you don’t have to implement that code yourself), it’s a bit of a pain if you have to implement it yourself anyway, just to fetch that contract!

It also doesn’t presently provide an ironclad guarantee that the record won’t change, which is an important factor if we’re going to use this layer of indirection for looking up important contracts.

It’s TNL right now. I’d like to transfer ownership to the DAO in time, but wanted it to demonstrate that it’s capable of handling these sort of updates on a routine basis first. There’s also the issue of some of the infrastructure that TNL manages on behalf of the DAO; for example, we have app.ens.eth(.link|.limo) hosting the app, and we naturally wouldn’t want to have to create a DAO-wide vote every time the app needs redeploying!

I also think there’s value in being able to make the address entirely immutable for a given contract version - and even something that only can be updated by the DAO doesn’t meet that requirement.

In practical terms it’s extremely straightforward; to look up, say, the universal resolver contract, the client would make a single call to registry.resolver(UNIVERSAL_RESOLVER_HASH) where UNIVERSAL_RESOLVER_HASH can be a hardcoded value.

yes, of course, caching helps the average case (the most used case)
in the worst case, you have a cache-miss and do the normal thing…

is there any bigdata on this?
(I even didn’t find the default TTL on google)

I’m not against your solution, all I’m saying is that perhaps, maybe, a
universalresolver.contracts.ens.eth with good caching resolves 90% of the problem…

(premature optimization is the root of all evil)

We don’t want a situation where 10% of the time name resolution takes more than twice as long as normal. And it would still require client libraries to implement the full resolution process themselves, rather than relying on a universal resolver contract to do it for them.

1 Like

Universal resolver using _ works for me. Not a fan on cache&TTL latency, I think it’s very equitable for everyone and people will overall understand it’s not a TLD.

I will be working on a contract naming system for ETHOnline this year, using _contract.eth, e.g., namewrapper1._contract.eth. If anyone else is interested in working on this project with me, let me know. I think today is the last day to apply to EthOnline as well.


Sounds great! Can I suggest using subdomains? Allow anyone to register foo.contract.eth, and configure it so that it always resolves to the latest version, while, etc resolve to immutable past versions.


I am also planning to use _record.eth to be able to create a default uri for records, e.g., email.premm._record.eth.