Skip to content

Add ENSIP: Contract Self-Naming#60

Open
nxt3d wants to merge 8 commits into
ensdomains:masterfrom
nxt3d:contract-self-naming
Open

Add ENSIP: Contract Self-Naming#60
nxt3d wants to merge 8 commits into
ensdomains:masterfrom
nxt3d:contract-self-naming

Conversation

@nxt3d
Copy link
Copy Markdown
Contributor

@nxt3d nxt3d commented Dec 17, 2025

Summary

This PR adds a new ENSIP that enables contracts to declare their own reverse names using ERC-8042 Diamond Storage. By storing a namehash in a predictable ERC-8042 storage slot keyed by "eth.ens.reverse-name", contracts can self-declare their ENS name during deployment, enabling trustless and permissionless reverse name registration.

Key Features

  • Self-declaration at deployment: contracts can declare their reverse ENS name during initialization using ERC-8042 diamond storage
  • Trustless verification: uses a single predictable storage slot
  • Permissionless registration: any account can register the declared name
  • Works for admin-free contracts: no owner required to perform a separate registration transaction

Specification Details

  • Diamond storage identifier: "eth.ens.reverse-name"
  • Storage location: keccak256("eth.ens.reverse-name") (ERC-8042)
  • Value format: bytes32 namehash of the ENS name (e.g., namehash("mycontract.eth"))

This ENSIP enables contracts to declare their own reverse names using ERC-8049 metadata with Optional Diamond Storage extension. Contracts can self-declare their ENS name during deployment, and any account can then trustlessly verify and permissionlessly register the contract's reverse name in the registrar.

Key features:
- Contracts declare reverse names via ERC-8049 metadata at key 'eth.ens.reverse-name'
- Predictable storage locations enable trustless verification
- Permissionless registration removes burden from contract deployers
- Compatible with ENSIP-19
- Includes verified storage slot calculations
- Note that contract owner may not always exist
- Mention alternative of making custom external call in constructor
- Change 'ABI-encoded' to 'stored as bytes' for value format (correct terminology)
- Clarify single-slot verification applies to names under 32 characters
- Update rationale to emphasize admin-free contracts use case
@gskril gskril requested a review from Arachnid December 17, 2025 20:08
nxt3d added 2 commits January 1, 2026 23:15
This updates the Contract Self-Naming ENSIP to remove the ERC-8049 dependency and instead use
an ERC-8042 diamond storage slot keyed by "eth.ens.reverse-name".

Key changes:
- Replace the ERC-8049 Optional Diamond Storage metadata mapping approach with direct ERC-8042
  storage location addressing.
- Define the storage location as keccak256("eth.ens.reverse-name"), matching ERC-8042’s formula
  (erc8042(id) = keccak256(id)).
- Specify that the value stored at this slot is a bytes32 namehash (rather than a UTF-8 bytes
  string), keeping the declaration single-slot and efficient to verify.
- Update the Solidity example to the canonical diamond-storage struct pattern:
  - STORAGE_POSITION constant
  - getStorage() that assigns s.slot via assembly
  - @Custom:storage-location erc8042:eth.ens.reverse-name annotation
  - set ReverseNameStorage.reverseNameHash during deployment
- Update abstract/motivation/rationale/backwards compatibility wording and references to reflect
  the simplified ERC-8042-only approach.
- Add raffy.eth as a contributor.
Update the example contract in the Contract Self-Naming ENSIP to explicitly use namehash("mycontract.eth").

## Abstract

This ENSIP extends ENSIP-19 to enable contracts to declare their own reverse names using ERC-8042 Diamond Storage. By storing a namehash of the reverse name in a known storage location, contracts can self-declare their ENS name. Any account can then register this name using the reverse registrar on L1, or using an L2 reverse registrar. This enables trustless, permissionless reverse name registration for contracts without requiring the contract deployer to perform additional registration steps.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the namehash of the primary name?

"Any account can then register this name using the reverse registrar on L1, or using an L2 reverse registrar."

This spec doesn't outline how..?

@Arachnid
Copy link
Copy Markdown
Member

Wouldn't a simpler implementation be to define an interface with a predicate function that allows the contract to explicitly accept or reject a primary name?

Also, presumably the idea here is to update the reverse registrar to support checking this and setting the name accordingly, but this isn't mentioned anywhere.

@nxt3d
Copy link
Copy Markdown
Contributor Author

nxt3d commented Jan 27, 2026

Wouldn't a simpler implementation be to define an interface with a predicate function that allows the contract to explicitly accept or reject a primary name?

By predicate function, do you mean something like isReverseName(bytes32 namehash) returns (bool)? What benefit would this have over saving the namehash as a named storage slot value?

With a named storage slot using ERC‑8042 Diamond Storage, the storage value itself acts as the verification. If the namehash exists at that slot and matches, it is valid. This avoids function specification overhead, is chain‑agnostic because there is no need to know the reverse registrar address, uses minimal gas via a single sstore, and is upgrade‑safe since storage persists across proxy upgrades.

Example:

constructor() {
    // Declare ENS reverse name using ERC-8042 Diamond Storage
    // Slot: keccak256("eth.ens.reverse-name")
    // Value: namehash("mycontract.eth")
    // This allows any account to permissionlessly register the reverse name
    assembly {
        sstore(
            0x09ded414ae6c0ce389342caf0619071d5be1687a6f7314e74bcc7cfa1a0df4bf,
            0x6ff093714e95f3f123db7d14a81db07d48e7e0f7ca7ef35ca446facd9d5e7c1c
        )
    }
}

Also, presumably the idea here is to update the reverse registrar to support checking this and setting the name accordingly, but this isn't mentioned anywhere.

I agree, I can work on that and document it once the self-naming mechanism is finalized.

@adraffy
Copy link
Copy Markdown
Member

adraffy commented Jan 27, 2026

How about:

On L1, a contract can name itself by implementing INameResolver. The detection method can be ERC-165. The addr.reverse resolver can implement this check. This dodges the ownership requirement.

On L2 (or any contract that interfaces with the crosschain reverse registrars), since we want names stored in one contract for read simplicity (so the name needs copied into the registrar), the only option currently is to implement owner().

However, a similar mechanism like above would work: there can be a function on the registrar that anyone can call, and if the address supports INameResolver, it copies that name accordingly.

In both situations, the requirement would be the name() response must be onchain.

Instead of ERC-165, it could just be a blind call with a non-empty string response.

It would be up to the contract to check if node matches. Alternatively, we could use a different function (maybe that's what was meant by predicate function above.)

@Arachnid
Copy link
Copy Markdown
Member

This avoids function specification overhead,

Except it requires referencing ERC-8042, which is large and overengineered.

is chain‑agnostic because there is no need to know the reverse registrar address

Nor does my suggested alternative require this

uses minimal gas via a single sstore

20k gas - while a simple constant-returning function uses much less

and is upgrade‑safe since storage persists across proxy upgrades.

Which is a cost, rather than a benefit, since you may want to change the primary name

@nxt3d
Copy link
Copy Markdown
Contributor Author

nxt3d commented Jan 27, 2026

Except it requires referencing ERC-8042, which is large and overengineered.

We can remove the dependency on ERC-8042 and simply have the storage location use the same scheme, which I think is simpler than ERC-7201?

Nor does my suggested alternative require this.

I agree.

20k gas, while a simple constant-returning function uses much less.

Right, if it is a constant, I agree.

Which is a cost, rather than a benefit, since you may want to change the primary name.

How does this affect whether it can be updated or not? If we use a function, then that friction is required for every upgrade, whether or not the developer wants that friction, because the developer needs to ensure that the function call persists across upgrades.

I also think this may be a key part of the design:

This avoids function specification overhead.

We might also decide in the future to allow L2 reverse names to be registered on L1, in which case we could use an inclusion proof to verify the contract name.


Another idea would be to just do both methods. If the goal is to make it as easy for contract developers as possible to set their reverse name, then we could offer both methods as options, allowing for developers to decide what works best for them.

@Arachnid
Copy link
Copy Markdown
Member

How does this affect whether it can be updated or not? If we use a function, then that friction is required for every upgrade, whether or not the developer wants that friction, because the developer needs to ensure that the function call persists across upgrades.

How is leaving a piece of code unmodified 'friction'?

@nxt3d
Copy link
Copy Markdown
Contributor Author

nxt3d commented Jan 28, 2026

How does this affect whether it can be updated or not? If we use a function, then that friction is required for every upgrade, whether or not the developer wants that friction, because the developer needs to ensure that the function call persists across upgrades.

How is leaving a piece of code unmodified 'friction'?

Because it's necessary to continue to manage the interface, such as isReverseName(bytes32 namehash) returns (bool), across multiple contract upgrades, whereas setting the named storage value in the constructor can be done only once and persists across upgrades.

My original intention for this proposal was to try to make it as easy as possible for a contract to name itself. I couldn't think of anything easier than setting a storage value.

@Arachnid
Copy link
Copy Markdown
Member

Arachnid commented Feb 2, 2026

Because it's necessary to continue to manage the interface, such as isReverseName(bytes32 namehash) returns (bool), across multiple contract upgrades, whereas setting the named storage value in the constructor can be done only once and persists across upgrades.

This is a real stretch. I think having invisible state that isn't encompassed by the current version of the source is much more of a maintenance and transparency issue than a 3-line function you simply leave alone.

@nxt3d
Copy link
Copy Markdown
Contributor Author

nxt3d commented Feb 2, 2026

This is a real stretch. I think having invisible state that isn't encompassed by the current version of the source is much more of a maintenance and transparency issue than a 3-line function you simply leave alone.

I think that most implementers would like the value to be mutable, and therefore would create a setter function.

setENSName(bytes32 namehash)

I am happy to move forward with either design, or both. If I change this PR to just support isReverseName(bytes32 namehash) returns (bool), is this something that we could move forward on? I could also add other methods such as ERC-1271 and DEFAULT_ADMIN_ROLE for access control.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants