# 'ENS Chain'

By [clowes.eth](https://paragraph.com/@clowes) · 2023-12-06

---

If the [Ethereum Name Service](https://ens.domains/) (ENS) intends to be the universal naming system it needs to scale to be able to onboard new users quickly and at minimal (no?) cost. The majority of the ENS codebase is currently on Layer 1 Ethereum. Layer 2 (L2) solutions have recently become an option for scaling, and there are many new L2 chains in various states of development. These include [Optimism](https://www.optimism.io/), [Arbitrum](https://arbitrum.io/), [zkSync](https://zksync.io/) and many others. Using an L2 for scaling should work well for ENS, however, it is difficult at this time to determine which L2 to choose, or even which tech stack is best.

We are currently working on solving the “which L2” problem which also includes the possibility of an ‘ENS Chain’. This post seeks to document the work that we have completed thus far on an OP Stack based ‘ENS Chain’, and also provides a detailed step-by-step guide for anyone else who wants to do the same.

Quick Links
-----------

*   Setting up the chain
    
*   Understanding Optimism
    
*   Useful Commands
    
*   Deploying the L2 ENS contracts
    
    *   L2 Deployment addresses
        
*   Deploying the L1 contracts
    
    *   L1 Deployment addresses
        
*   Demonstrating resolution from ‘ENS Chain’
    
*   Conceptual explainer
    
    *   Basic demo
        
    *   Basic resolution demo
        
    *   Full ‘ENS Chain’ subname resolution demo
        

* * *

*   [L2 ENS source code (Github)](https://github.com/unruggable-labs/L2-ens)
    
*   ‘[ENS Chain’ demo integration on Sepolia](https://ens-chain-sepolia.unruggablenames.com/)
    

* * *

Our gateway is running at [**https://ens-chain-sepolia.unruggablegateway.com/**](https://ens-chain-sepolia.unruggablegateway.com/)

ENS Chain RPC is at [**https://chain.enstools.com**](https://chain.enstools.com)

Setting up the chain
--------------------

The Optimism team have detailed documentation about setting up your own L2 chain. For the most part setting up the chain is a formality yet it is worth noting that given the number of contracts/complexity of the Optimism codebase it is worth setting up your chain on a machine with at least 16GB of ram.

[https://stack.optimism.io/docs/build/getting-started/](https://stack.optimism.io/docs/build/getting-started/)

It is also worth noting that the OP testnets are being migrated to Sepolia. We set up our ‘ENS Chain’ to interface with Sepolia too.

[https://blog.oplabs.co/op-sepolia/](https://blog.oplabs.co/op-sepolia/)

[https://github.com/eth-clients/sepolia](https://github.com/eth-clients/sepolia)

Understanding Optimism
----------------------

Your first point of call should be the Optimism specification documentation:

[https://github.com/ethereum-optimism/optimism/tree/develop/specs](https://github.com/ethereum-optimism/optimism/tree/develop/specs)

There is a comprehensive repository of documentation that details the inner workings of the OP Stack. I'd highly recommend giving it a read.

[https://github.com/joohhnnn/Understanding-Optimism-Codebase/tree/main](https://github.com/joohhnnn/Understanding-Optimism-Codebase/tree/main)

`nanoeth` is "_a simple implementation of Ethereum's execution layer_". I found it a really interesting code base to look through.

[https://github.com/norswap/nanoeth/](https://github.com/norswap/nanoeth/)

[Infura](https://www.infura.io/) have also produced an interesting blog post entitled "[How to use Ethereum proofs](https://www.infura.io/blog/post/how-to-use-ethereum-proofs-2)".

Useful commands
---------------

Once setup, here are some useful commands:

*   To see what the finalized ENS Chain block is, use the following CURL command:
    

    curl -X POST -H "Content-Type: application/json" --data     '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}'     http://0.0.0.0:8547 | jq '.result.finalized_l2'
    

*   To find out the address of the L1 bridge (to get Sepolia ETH onto the chain if you havn't prefunded in your genesis configuration)
    

    cat deployments/ens-chain-sepolia-final/L1StandardBridgeProxy.json | jq -r .address
    

*   To verify receipt of funds on your chain
    

    curl -H "Content-Type: application/json" --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\",\"params\":[\"0xfc04d70bea992da2c67995bbddc3500767394513\", \"latest\"],\"id\":67}" https://chain.enstools.com
    

Deploying the L2 ENS contracts
------------------------------

Our contracts can be found here:

[https://github.com/unruggable-labs/L2-ens](https://github.com/unruggable-labs/L2-ens)

Firstly we deployed the core contracts onto our chain.

    npx hardhat deploy --network ensChain --tags root
    npx hardhat deploy --network ensChain --tags registrars
    npx hardhat deploy --network ensChain --tags renewal-controllers
    npx hardhat deploy --network ensChain --tags resolvers
    

### L2 Deployment addresses

*   `ENSRegistry`: _0xbad813ca1CF82a10f805B0Ae6167AffBD8b8368D_
    
*   `Root`: _0x0faB0E18c9A378D407073e399928fF8d73E494Ad_
    
*   `USDOracleMock`: _0x3F8456ea1c2e68B5c996da6800d90E4c52a44df6_
    
*   `L2MetadataService` : _0xeeF46BB63D5C119Cf38F9f731149E2595f1d15DA_
    
*   `L2NameWrapper`: _0xF24A89e588F36063AD4Ed16cDE61a13d6272C1Cd_
    
*   `L2EthRegistrar`: _0x3624029970EF97ae44588FC5D23d88f46F4e645e_
    
*   `L2SubnameRegistrar`: _0x249BAdd9d5fa9a4AAcc7b40d4690687fD81BfBd0_
    
*   `L2PricePerCharRenewalController`: _0x8E64668e78471f1FcC17AaBe078359e9B7Ea61Da_
    
*   `L2FixedPriceRenewalController`: _0x956BC0A5c45179D9Fb1D07B10e0c3e3879769F2b_
    
*   `OwnedResolver`: _0xC6348c2b785770E5ac1b3B6EE5cD8E9961ee7540_
    
*   `L2PublicResolver`: _0x58224219aF8d0Cab731C6247c2C779a38681d810_
    

You can check that the contracts are actually deployed using the `cast` command from [foundry](https://getfoundry.sh/):

    cast code 0x0faB0E18c9A378D407073e399928fF8d73E494Ad --rpc-url https://chain.enstools.com
    

Deploying the L1 contracts
--------------------------

*   Get the Oracle Proxy address:
    

    cat ./packages/contracts-bedrock/deployments/ens-chain-sepolia-final/L2OutputOracleProxy.json | jq -r .address
    

*   Update it in `00_deploy_l1_resolver.ts`
    
*   Deploy `OPVerifier`, `L1Resolver`, and `L1UnruggableResolver`
    

    npx hardhat deploy --network sepolia --tags resolver
    

### L1 Deployment addresses

*   `OPVerifier`: [_0xdaa862764b64CF3D1f8AfCb8C9137De3f3a8A19E_](https://sepolia.etherscan.io/address/0xdaa862764b64CF3D1f8AfCb8C9137De3f3a8A19E)
    
*   `L1Resolver`: [_0x1eC31D576d602D685496605A81323D0678d955d0_](https://sepolia.etherscan.io/address/0x1eC31D576d602D685496605A81323D0678d955d0)
    
*   `L1UnruggableResolver`: [_0xce03Dde5d3ab55b94D10202d0c0155f4A8a7f2a7_](https://sepolia.etherscan.io/address/0xce03Dde5d3ab55b94D10202d0c0155f4A8a7f2a7)
    

Demonstrating resolution from ENS Chain
---------------------------------------

We can demonstrate resolving a name from our ‘ENS Chain’ using our [L1Resolver](https://sepolia.etherscan.io/address/0x1eC31D576d602D685496605A81323D0678d955d0) (deployed on Sepolia) and our `OwnedResolver` instance (deployed on ENS Chain).

### Conceptual explainer

On L1 we are using the [evmgateway](https://github.com/ensdomains/evmgateway/) code to build a request that will revert with the [ERC-3668](https://eips.ethereum.org/EIPS/eip-3668) revert error `OffchainLookup` appropriately populated with calldata for fetching the data contained within the appropriate [storage slots](https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html) in the L2 `OwnedResolver` contract for the name passed in.

The first value of the `OffchainLookup` revert is our gateway URL at which our instance of the `op-gateway` is operating. We are running our gateway at `https://ens-chain-sepolia.unruggablegateway.com/`.

The gateway returns the requested value alongside a proof which is verified (by`OPVerifier`) against the storage proofs posted onto L1 by the ‘ENSChain’. An example response can be seen [here](https://ens-chain-sepolia.unruggablegateway.com/0x88da9fb9abba909bae3aebc2176928e4fefdd23f/0xea9cd3bf00000000000000000000000058224219af8d0cab731c6247c2c779a38681d810000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000002000001ff000000000000000000000000000000000000000000000000000000000102200304ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020ea115ef3aa41bcdba799dc71e79d614446551b68881d293f394b43398dabbe97000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000020ea115ef3aa41bcdba799dc71e79d614446551b68881d293f394b43398dabbe970000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003c.json).

### Basic demo

This basic demo is adapted from the [crosschain-resolver](https://github.com/ensdomains/evmgateway/tree/main/crosschain-resolver) demo built by [Makoto](https://github.com/makoto)/[Nick](https://github.com/Arachnid) from ENS Labs.

It demonstrates usage of the basic VM built by Nick for discerning storage slot addresses and fetching the values contained at those addresses for simple (`uint256`) and more complex (`string`, nested mappings etc) data types.

Deploy the L2 contract onto ‘ENS Chain’

    npx hardhat deploy --network ensChain --tags demo
    

`BasicDemoL2`: _0x96B37888947965275F9d79baB40B8b66Fc01F701_

Deploy the L1 contract to Sepolia.

    npx hardhat deploy --network sepolia --tags demo
    

`BasicDemo`: [_0xe04D9d2f9BD261F4b9146c99d9f9caB79dc3Ef9a_](https://sepolia.etherscan.io/address/0xe04D9d2f9BD261F4b9146c99d9f9caB79dc3Ef9a)

Once deployed you can create a ethers.js `Contract` instance in the Chrome developer console and call the L1 methods to fetch the data from L2.

    const sepoliaProvider =  new ethers.JsonRpcProvider("https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY");`
    
    const instance = new ethers.Contract(BASIC-L1-DEMO-DEPLOYMENT-ADDRESS, BASIC-DEMO-ABI, sepoliaProvider);
    

Note: You can source the ABI for the `BasicDemo` contract here:

[https://ethtools.com/interface-id-generator/basic-demo](https://ethtools.com/interface-id-generator/basic-demo)

If you run the following command in a browser window with ethers.js version 5.7 you will see the below error output. If you've spent enough time working with ERC-3668 this will look immediately familiar. The method signature `0x556f183` is the selector for the `OffchainLookup` error definition.

    await instance.getLatestHighscore();
    

![](https://storage.googleapis.com/papyrus_images/668a446480482a5c00446215856965a0f76d67b16032fd6f2cd5baf344c882a4.png)

This response is returned because ethers.js 5.7 does not automatically enable its CCIP read implementation.

If you explicitly enable it the resolution will occur behind the scenes and you will receive the result that you expect.

    await instance.getLatestHighscore({ccipReadEnabled: true});
    

### Basic resolution demo

*   Set the resolver for the name to that of our deployed `L1Resolver` instance (I have chosen to do this using the [ENS App](https://app.ens.domains/))
    

![](https://storage.googleapis.com/papyrus_images/f5fb99afd1f9aa5c5ce4e1071eb81220efc85591ed2c6a64e2698def90c5a22c.png)

*   Set our resolution address on the L2 `OwnedResolver` instance.
    

    cast send 0xC6348c2b785770E5ac1b3B6EE5cD8E9961ee7540 "setAddr(bytes32,address)" 0x7a46ad5d836d91ffc5b5682fcac3eccb728a10e2ced12dd9f29bda2be8694240 0xfc04d70bea992da2c67995bbddc3500767394513 --rpc-url https://chain.enstools.com --private-key YOUR-PRIVATE-KEY
    

Where _0x7a46ad5d836d91ffc5b5682fcac3eccb728a10e2ced12dd9f29bda2be8694240_ is the namehash of demoname.eth

![](https://storage.googleapis.com/papyrus_images/4ecb20393beb0cf997c4f2a7d0b23ad0efbbb7bfc023675c6bc423501b6d9956.png)

**Note:** The various encodings for ENS names (namehash, labelhash, token ID, DNS encoded format) can be generated using the ENS Hash generator from EthTools.com:

[https://ethtools.com/ens-namehash-labelhash-node-generator](https://ethtools.com/ens-namehash-labelhash-node-generator)

*   Check that the resolution address is set correctly:
    

    cast call 0xC6348c2b785770E5ac1b3B6EE5cD8E9961ee7540 "addr(bytes32)" 0x7a46ad5d836d91ffc5b5682fcac3eccb728a10e2ced12dd9f29bda2be8694240  --rpc-url https://chain.enstools.com
    

*   We can then verify that our resolution is working as expected by running the following commands in the [Chrome developer console](https://developer.chrome.com/docs/devtools/open/).
    

**Note:** Run this is a browser window that has a globally exposed instance of [ethers.js](https://github.com/ethers-io/ethers.js) **v6**.

    const sepoliaProvider =  new ethers.JsonRpcProvider("https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY");
    
    await sepoliaProvider.resolveName("demoname.eth")
    

**Note:** One of the downsides of the current state of Optimistic rollups is the delay between submitting a transaction to the layer 2 and the time at which the block is finalised/storage proofs have been posted to layer 1. There will be a delay between when your resolution address is updated on Layer 2 and when it can be proven on Layer 1.

![](https://storage.googleapis.com/papyrus_images/15c36b99b86628dc4fb5318726482c521e7b5da06a206bd84c8afe317e7801ae.png)

Full ENS Chain subname resolution demo
--------------------------------------

The demo app for setting and resolving ENS names of our ‘ENS Chain’ can be seen here: [ENS Chain Sepolia demo](https://ens-chain-sepolia.unruggablenames.com/).

**Note:** As in our previous demos we have set up subname registrations on `fivedollars.eth` and `pricepercharacter.eth`. These names demonstrate our renewal controllers concept whereby second level name owners can provide Unruggable subname rentals to others by associating a renewal controller with their name which contractually defines the terms of renewal in perpetuity.

Renewals of subnames of `fivesdollars.eth` cost $5.

Renewals of subnames of `pricepercharacter.eth` cost $1,000 for 1 character names, $100 for 2 character names, $10 for 3 character names, and $5 for all other lengths.

This demo will outline how you can do the same and offer rentals on your second level names.

### Setting up subname registrations

*   **Step 1.** Search for a name. I have searched for `blog-demo.eth`
    

![](https://storage.googleapis.com/papyrus_images/f933bf60fb90ef77a151a58fab0c321bfe9f7d9f6e709f4880336078aba6882e.png)

*   **Step 2.** Click 'Register' and follow the normal registration flow (two transactions need to be submitted - a commitment, and then after 60 seconds a registration) to register a second level name on Layer 1 Sepolia.
    

**Note:** If you are not connected to Sepolia you will be prompted to change networks.

*   **Step 3.** Upon successful registration the profile dialogue will be displayed for the registered name. You must provide the appropriate approval for our `L2SubnameRegistrar` on the `L2NameWrapper` deployed on ENS Chain.
    

![](https://storage.googleapis.com/papyrus_images/80db43ed7c9b9cc260564a96423ac34344689fd36953f20799a15b371fdbe995.png)

*   **Step 4.** You can set the resolver for your second level name to use the `L1UnruggableResolver` from the 'Subnames' tab.
    

![](https://storage.googleapis.com/papyrus_images/c640a0419ca0905cddb7a570056f5b7a7a890bec858956b32346b8ac431db9c7.png)

*   **Step 5.** Once set you can configure the 'Renewal Controller' that you want to use for your name. At the moment we offer the $5 renewal controller and the price per character renewal controller (as mentioned above).
    

![](https://storage.googleapis.com/papyrus_images/a2e308593d303b1034076b9656ba066e0862245392a574485580f76526e8050d.png)

Hurrah ! Now users can register subnames of `blog-demo.eth` !

### Registering a subname

*   **Step 1.** Enter a subname of `blog-demo.eth`. For example `thomas.blog-demo.eth`
    
*   **Step 2.** Register the name following the 2 step registration process (commitment and registration).
    

![](https://storage.googleapis.com/papyrus_images/235213f2f9909df8c28bec7061acad9f762320b5a2847cab4e0b97c009136263.png)

**Note:** These transactions are submitted to ‘ENS Chain’.Thats it..

### Resolving from ENS Chain

Once registered you can set the resolution address for your subname.

*   **Step 1.** Enter the address on the 'Configure' tab. Click 'Configure'.
    

![](https://storage.googleapis.com/papyrus_images/e92bae4df3f76d96fc7478a1f7e969ed423095c95923d22710a80a7c988419ca.png)

Thats it ! You've now set your resolution address for your subname on ENS Chain, and it will resolve in all clients that **correctly** implement the [ERC-3668 CCIP Read](https://eips.ethereum.org/EIPS/eip-3668) / [ENSIP-10 Wildcard Resolution](https://docs.ens.domains/ens-improvement-proposals/ensip-10-wildcard-resolution) specification.

**Note:** There is a 20 minute or so delay between when you set the resolver on ENS Chain and when it will provably resolve against Layer 1. This is due to the constraints of the current Optimism Fraud proof mechanism and is an active area of research.

![](https://storage.googleapis.com/papyrus_images/1c7438bb3262f0d8dd83a7d12e28eb444b9ab017733e2fed34a05a78e47c8e5d.png)

So.. lets try it out.

We once again open up our Chrome developers console in a browser window that has a globally accessible ethers.js instance. I am using a fork of version 6.9 (see below for why).

    const sepoliaProvider =  new ethers.JsonRpcProvider("https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY");
    
    await sepoliaProvider.resolveName("thomas.blog-demo.eth")
    

You will see an output similar to the below:

![](https://storage.googleapis.com/papyrus_images/c544567200de1e3ff399c1e69b2f3591315cf7a626c0cbec425277672d1688ca.png)

Notice how you connected to a Sepolia (L1) provider yet the data that was returned was set (at minimal cost might I add) on ‘ENS Chain’ (L2).

### Too good to be true

Unfortunately there are a few intricacies to this that are somewhat annoying.

**Metamask does not support ENS on Sepolia**

Unfortunately Metamask does not currently support ENS on Sepolia. Hopefully they will support it soon.

[https://twitter.com/GUA/status/1728438161490235448](https://twitter.com/GUA/status/1728438161490235448)

---

*Originally published on [clowes.eth](https://paragraph.com/@clowes/ens-chain)*
