Approaches to Support Setting Primary Names for All Contracts

Problem Statement

Currently, setting primary ENS names for smart contracts is limited to a subset of contracts. To be able to set a primary name, a contract must:

  • Extend Ownable or ERC-173

  • Be capable of delegating reverse address claim permissions to another externally owned account (EOA) via methods like reverseClaimer or reverseSetter (which must be set at deployment and cannot be changed afterward)

  • Able to make arbitrary calls, can set a primary name.

This significantly restricts the number of contracts, especially those already deployed or not meeting the above criteria, that can obtain a primary name.

The Enscribe team has been thinking about how we could expand this capability so that, in principle, every contract can have a primary name set.

High-Level Solution

We propose extending the current permission logic in the Reverse Registrar contract.

Instead of limiting this capability exclusively to contracts that implement Ownable/ERC-173, we suggest also allowing the original deployer of the contract to set the primary name.

This approach achieves two key objectives:

  • Universal Accessibility: Enables every contract address to set a primary name.

  • Security: Ensures only authorized entities (contract owner or verified deployer) can set primary names.

To achieve this, we must reliably verify the contract deployer on-chain or allow any address to securely prove that it deployed the contract.

Potential Implementation Approaches

We have identified a number of potential methods for verifying deployer addresses:

  • Oracle / CCIP-Read / EigenLayer AVS:
    Utilize off-chain services to fetch and verify deployer addresses, with on-chain verification.

  • ZK Proofs:
    Generate cryptographic proofs off-chain that verify deployer information, then verify these proofs on-chain.

  • Deployer Decoding from Contract Address:
    Derive the deployer’s address directly from the contract address by analyzing the nonce (for CREATE) or salt (for CREATE2) used during deployment.

Note: We still need to explore the possible implementation strategies in more depth and would love to initiate a discussion around the proposed high-level solution before we commence work on a PoC.

Changes Requested

With the L2 primary name support discussions in ENSIP-19, which enable setting primary names for L2 chains, we want to propose some changes to the L2ReverseRegistrar contract.

Since the L2ReverseRegistrar implementation has not yet been finalized and the mainnet deployment is still pending with only testing ongoing in L2 testnets, there are a few changes we can make now to enable setting primary names not just for Ownable/ERC-173 contracts but for all existing and new contracts.

Refer to the L2ReverseRegistrar.sol Implementation

Authorized modifier
Source Link

/// @notice Checks if the caller is authorised
/// @param addr The address to check.
modifier authorised(address addr) {
    if (addr != msg.sender && !_ownsContract(addr, msg.sender)) {
        revert Unauthorised();
    }
    _;
}

We want to highlight the _ownsContract() function
Source Link

/// @notice Checks if the provided contractAddr is a contract and is owned by the provided addr.
/// @param contractAddr The address of the contract to check.
/// @param addr The address to check ownership against.
function _ownsContract(
    address contractAddr,
    address addr
) internal view returns (bool) {
    if (contractAddr.code.length == 0) return false;
    try Ownable(contractAddr).owner() returns (address owner) {
        return owner == addr;
    } catch {
        return false;
    }
}

In this we want to add a new check for contract deployer, which would look something like -

function _ownsContract(
    address contractAddr,
    address addr
) internal view returns (bool) {
    if (contractAddr.code.length == 0) return false;

    // Check Ownable ownership first
    try Ownable(contractAddr).owner() returns (address owner) {
        if (owner == addr) {
            return true;
        }
    } catch {
        // Continue to check deployer if Ownable check fails
    }

    // Check if addr is the deployer (mock implementation)
    if (_isAddrContractDeployer(addr, contractAddr)) {
        return true;
    }

    return false;
}

// Dummy/mock function to simulate deployer address verification
function _isAddrContractDeployer(address addr, address contractAddress) internal pure returns (bool) {
    // TODO: Implement actual deployer verification logic
    return false;
}

Summary

To enable every contract - past or future - to set a primary ENS name, we propose expanding the current authorization logic beyond Ownable/ERC-173 checks. By recognizing verified deployers as authorized entities, we unlock support for a broader range of contracts, including those already deployed without ownership functions.

We’re suggesting a change to the L2ReverseRegistrar contract to include deployer checks within _ownsContract, using a pluggable _isAddrContractDeployer method. This prepares the ENS ecosystem for more inclusive and flexible primary name assignments for contracts across L2s, including Namechain.

3 Likes

I believe setNameForAddrWithSignature() covers the safe CREATE case, where the deployment originates from an account that can generate signatures (EoA, SCA).

I don’t think it is safe in general for contracts to be claimed by their deployer.


Wait, no, I agree we need a setNameForDeploymentWithSignature().

Technically doing this is straightforward via the last mechanism you mention: you can prove the deployer of a contract by generating its address from the deployer address and nonce (or other parameters, in the case of CREATE2).

However, I don’t believe it’s safe in general to allow the deployer to set the primary name on a contract. Often, dedicated or shared deployment accounts are used, and ownership of the contracts is then transferred elsewhere post-deployment - if there’s any ownership at all. Not infrequently, these deployment accounts get compromised, in which case anyone who obtains the leaked credentials would now also be in a position to set an arbitrary primary name for all the contracts the account deployed.

2 Likes

What about an extendable claim system for reverse registrars so we could add methods post-deployment for claiming?

  • programmatic like Ownable
    • AccessControl
    • ERC1967 Admin
  • ad-hoc
    • Merkle proof of inclusion for a set of DAO-Approved addresses

Basically add Controllable to L2ReverseRegistrar and include controllers in authorised()

1 Like

Exploring New Ideas to Address the Concerns Raised by the Community

By @nick.eth

However, I don’t believe it’s safe in general to allow the deployer to set the primary name on a contract. Often, dedicated or shared deployment accounts are used, and ownership of the contracts is then transferred elsewhere post-deployment - if there’s any ownership at all. Not infrequently, these deployment accounts get compromised, in which case anyone who obtains the leaked credentials would now also be in a position to set an arbitrary primary name for all the contracts the account deployed.


Proposed Solution

To address the concerns raised by the Nick, we’re introducing a tiered permission system for setting a contract’s primary ENS name:

Level 1 - Contract is Ownable/ERC-173

  • If the contract implements Ownable or ERC-173, only the owner() should be allowed to claim the reverseNode.
  • In this case, do not proceed to check the deployer.

Level 2 - Contract Deployed via ReverseClaimable Mechanism

  • If the reverseNode has already been claimed by another account, and the sender is not that account, then reject the attempt to claim the reverse record by the deployer or set a primary name.
  • Do not check further conditions.

Level 3 - Fallback for Non-Ownable Contracts

  • If the contract is neither Ownable/ERC-173 nor has its reverseNode claimed during deployment,
  • Then allow the sender to set the primary name only if they are the original deployer of the contract.

By @raffy

I don’t think it is safe in general for contracts to be claimed by their deployer.

If we can eliminate the risk where the reverse resolution of a contract address doesn’t match its forward resolution, we effectively remove the primary incentive for a scammer to manipulate the contract’s primary name.

I’ll discuss how to mitigate this risk further at the end of the proposed solution.

Currently, only a small fraction of contracts are eligible to set primary names.
If we can increase that eligibility to 80–90% of contracts (for now eliminating contracts deployed thorugh factory contracts), it would be a significant win for both the ENS and broader Ethereum communities.


By @gregskril

To explore further:

  • AA deployers
  • Burner/compromised key deployments

Burner/Compromised Key Deployments

While this is a broader Ethereum issue, if a private key is compromised, there’s no truly safe recovery path, such contracts shouldn’t be considered safe to interact with in the first place.
However, if we can eliminate the risk where reverse resolution does not match forward resolution, then the motivation for a scammer to set or manipulate a primary name disappears.

Account Abstraction (AA) Deployers

Since these contract deployments occur through fixed EntryPoint contracts, we can reliably filter them.
We can enforce a rule that contracts deployed via EntryPoint are not allowed to have their primary name set via the EntryPoint address, thereby preventing misuse.


Revised Solution After Considering the Concerns

We need to understand the motivation of a hacker or scammer for changing the primary name of a contract, whether by gaining access to the deployer’s private key or exploiting permissions.

Legitimate Scenario (Genuine Contract Deployer)

  • Contract: 0x123abc

  • ENS Name: genuine.original.eth
  • Reverse Resolution: 0x123abc
.addr.reverse → genuine.original.eth
  • Forward Resolution: genuine.original.eth → 0x123abc


Attack Scenario (Scammer/Hacker)

  • Contract: 0x123abc

  • ENS Name: fake.scammer.eth
  • Reverse Resolution: 0x123abc
.addr.reverse → fake.scammer.eth
  • Forward Resolution: fake.scammer.eth → 0x456def


This creates a misleading link where reverse and forward resolutions don’t align, enabling scam potential.


If we can eliminate this mismatch, ensuring that reverse and forward resolution always align then we remove the scammer’s motivation to change the primary name in the first place.

Rephrasing in more technical terms - If we disallow setting any name as a contract’s primary name unless it forward-resolves to that contract address, and also prevent any name from changing its forward resolution unless it is set as the reverse resolution of a contract, we can effectively eliminate the scammer’s motivation to manipulate a contract’s primary name.

Take the previous example again:

Genuine Contract Deployer

The legitimate deployer claims the reverse node and sets the primary name for the contract:

  • Contract: 0x123abc

  • ENS Name: genuine.original.eth
  • Reverse Resolution: 0x123abc
.addr.reverse → genuine.original.eth
  • Forward Resolution: genuine.original.eth → 0x123abc


Scammer/Hacker Scenario

A scammer gains the ability to change the primary name of the contract and sets it to their own ENS name:

  • Contract: 0x123abc

  • ENS Name: fake.scammer.eth

They then:

  • Set the forward resolution of fake.scammer.eth to 0x123abc

  • Set the reverse resolution of 0x123abc
.addr.reverse to fake.scammer.eth

Resulting in:

  • Reverse Resolution: 0x123abc
.addr.reverse → fake.scammer.eth
  • Forward Resolution: fake.scammer.eth → 0x123abc


Now, under the proposed constraints, the scammer cannot change the forward resolution of fake.scammer.eth to any other address.

This effectively neutralizes the scam vector, as the ENS name becomes locked to the contract, unless a new name first forward-resolves to the contract and then replaces the old primary name through reverse resolution. This removes the incentive to exploit primary name assignments.


Implementation Plan

To achieve this, we need to create a new ContractResolver and make some changes to the ReverseRegistrar.

Changes in ReverseRegistrar

  • Only allow a custom ContractResolver to be used as the resolver address when setting names via claimForAddr and claimWithResolver.
  • Update the authorised modifier to include the deployer of the contract can also claim reverseNode.

ContractResolver — Updates to NameResolver Logic

  • Check that the name forward-resolves to the contract address using the addr() function from AddrResolver.

ContractResolver — Updates to AddrResolver Logic

  • Ensure the name/node is also set as a reverse record by checking the name() function from NameResolver.

The Enscribe team is keen and already working to address issues with smart contract naming. We believe that with the Namechain release and support for L2 primary names, if we can implement changes to allow all contracts to have a primary name, it would be a win for both us and the ENS community.

2 Likes

@nischal.eth This is super cool. A lot of effort has clearly gone into this. :clap:
Appreciate you and @conor taking the lead on this.

Would love to discuss further on Ecosystem this week and see what opinions other have.

One thing that jumps out to me is this bit:

What if the forward resolution goes off chain?

Am I misreading this, or is this not a circular constraint? Can’t set A before B but also can’t set B before A?

Since we are proposing custom ContractResolver - this is going to be an onchain resolver with addr() function which returns actual address (or resolve() which returns the result) not the OffchainLookup error as it is in OffchainResolver.

There is a typo my bad, it should read like this -

If we disallow setting any name as a contract’s primary name unless it forward-resolves to that contract address, and also prevent any name from changing its forward resolution once it is already set as the reverse resolution to that contract address, we can effectively eliminate the scammer’s motivation to manipulate a contract’s primary name.

This might initially seem like a circular dependency, but it’s not.

Consider this scenario -

At the beginning, no forward resolution or reverse resolution has been set.

  • Contract: 0x123abc

  • ENS Name: genuine.original.eth
  1. Forward resolution is set first : genuine.original.eth → 0x123abc

  2. Reverse resolution is then set, but only if the name already forward resolves to this contract, which it does: 0x123abc
.addr.reverse → genuine.original.eth

Now user can’t change forward resolution of genuine.original.eth to any other address as it is being used in reverse resolution.

genuine.original.eth forward and reverse resolve to the same contract address.

If a user wants to update the primary name of the contract:

  • New ENS Name created: new.original.eth
  1. Sets Forward Resolution ( 1. allowed because this name isn’t yet used in any reverse resolution): new.original.eth → 0x123abc

  2. Sets Reverse Resolution 2. since the name now forward-resolves correctly: 0x123abc
.addr.reverse → new.original.eth

Now, the new primary name has been updated to new.original.eth, and once again, both forward and reverse resolutions align correctly.

I hope this clears it up. Thanks for the thoughtful question!