after seconds, no longer resolves and someone else can claim it
Rental implements LinkConfig.sol mixin which implements 2 functions:
_setNamespaceProgram(bytes)
_setBasenameNamespace(ns)
The Namespace Program defines how a contract translates a label into a namespace. The program takes (label: string, now: uint256) as input and expects (ns: uint256) as output.
The Basename Namespace is the namespace used when the basename is resolved, eg. chonk.eth in the above example. The small green circle (ns = 2) corresponds to the basename namespace.
Rental Program Explained
Rental uses the following program:
This program is:
Example: resolve("123.abc.chonk.eth", addr(60))
Basename = chonk.eth → LinkedResolver
Link = Rental (contract address)
Read program from Rental
uses slot: keccak("namespace.program")
Execute program with inputs: (block.timestamp, "abc")
The key idea is that the contract and the namespace program can be anything.
It can be a ERC-721, ERC-1155, or something custom.
It could link from label to labelhash to namespace (Rental example)
It could link from label to integer to namespace (eg. 10K collection)
It could link from label to “claimed name” using a separate claiming mechanism that associates “claimed name” with namespace, where only the owner of a 1
I have Good Morning Cafe #331
As owner of 331, I can claim "raffy" (eg. 331 → raffy)
As owner of 331, I can set namespace to X
raffy.[gmcafe.eth] → ns = X
It could link to something unrelated to a token, like another contract, maybe a directory of ERC-20s?
It could associate ENS names to an existing NFT
The namespace program can also execute logic. In the Rental example, the rental mechanism is solely defined on L2 yet enforced on L1.
any rental logic you want
any pricing logic you want
any minting logic you want
you could make a name resolve randomly
you could make a name resolve different based on time
anything!
Advanced Features
Any feature added to the LinkedResolver is shared by all users of this protocol.
The LinkedResolver implements the following new features:
EVM Fallback Address — coinType = 0x80000000 is considered the universal EVM address. If this address is set and you query a nonexistant EVM coinType like addr(60) or addr(8453), it will automatically fallback to the universal address.
LastModifiedResolver — given any record like addr(60), text(avatar), contenthash() you can query lastMod(record) and get the timestamp when that record was last changed.
Consolidated Storage Keys — all records are translated into universal key format. This massively simplifies the complexity of LinkedResolver and Namespace contracts as all lookups are just bytes → bytes.
Hashed Storage — regardless of how large of data you store in the Namespace, the value can be efficiently proven crosschain because it first proves the hash of the value, and then is supplied the value unproven (instead of storage proofs), and then the hash is verified.
Gasless Expiration — if the namespace program returns an invalid namespace, the name doesn’t resolve
Simplest example: Link an ENS name to a Namespace
Get an ENS name on Sepolia, unwrap it (no NameWrapper support yet), and set the resolver to LinkedResolver (0x084462610A20eFbDB686b30fe587ecA0E234D2EF)
Note: The LinkedResolver demo on Sepolia is using a TrustedVerifier that talks to my server, which is connected to Base. My server is signing the stateRoots such that modifications on Base are visible within a minute. The LinkedResolver can be deployed with any verifier w/o modification. A production deployment for Base would rely on Base’s Rollup (via OPFaultVerifier) and have a finalization delay (minimum of 1.75 days). Other rollups have different finalization periods, some significantly shorter than optimistic fault proofs.
This looks interesting, but it seems to be very similar to ENS v2, yet built on v1. Are you proposing it as an alternative to the current v2 design? Or for something else?
Separating names and namespaces (possibly this is confusing terminology) is interesting as it makes data portable and reusable. Linking multiple identities to the same namespace seems useful.
I also think it matches an user’s journey in ENS: start with an offchain name → get a subdomain → get an .eth. This shouldn’t require 3 data migrations.
By putting all user data in Namespace storage, a single editor app can manage all that data, even though the names linking to them can be tokenized (or not) using various methods across unrelated projects.
Fully-owned namespaces make management easy: since the resolver and owner are known at every level of the tree, the owner can just write anywhere (eg. create “a.b.c.d” w/o first creating the ancestors) and edit multiple nodes at the same time. If they want to delete their entire tree, they just create a new namespace and link it.
“Link to Namespace” and “Link to Contract that Links to Namespace” I think covers 99%+ of use-cases, which are: I have a name, but I don’t want to pay mainnet gas to set records, or I have a community, and I want to give them ENS. I’m not sure how many names need tokenized at multiple levels. This solution currently only supports 1 level (although more is possible.)
Being able to add features on the L1 LinkedResolver and having all users of the protocol benefit seems very interesting as long as they don’t impact existing user records. Last Modified Resolver is an example of a feature I always wanted.
The Namespace Program idea was exploring what Unruggable Gateways can do beyond the basics. Although somewhat weird, I think the underlying idea is pretty cool: trustlessly lifting code from L2 to L1 and executing it.
My main objective was: I wanted to make something that was maximally easy for a developer to integrate ENS. With this approach, you’d simply deploy an L2 contract for your collection/community, acquire an L1 name, and link to it. No server. No resolver. No ENS setters/getters.
The TeamNick NFT is a good case study. Currently, that is a custom crosschain ERC-721 that only supports 1 level of subdomains with avatar and address records and requires a custom manager. With this design, any vanilla NFT contract with a small program (like _setNamespaceProgram(hex"5a010646483c...") would be a direct replacement, while also supporting any deep subdomains, any record type, and all of the new Linked Resolver features.
Sounds good! I’d love to see this approach applied to v2. Many of the ideas are already embedded in the design, but others could be used to make the whole thing simpler for users.
I’m assuming that additional colour will be added to the current state of the v2 architecture/design at frensday (which I can then feedback to @raffy / he can watch on the livestream)?
Having played with some ideas relating to this, it is extremely flexible and this:
is particularly cool.
Noting the last commit to GitHub - ensdomains/enschain was a few months ago is this general architecture still the same. What would be the best way to apply/contribute real world code to v2 for this, and more generally?
Although we’re still iterating on the design, the interfaces should be fairly stable now - though we can’t guarantee no changes at all - so you can base any work off them fairly safely.