'ENS Chain'

If the Ethereum Name Service (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, Arbitrum, zkSync 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.

Our gateway is running at https://ens-chain-sepolia.unruggablegateway.com/

ENS Chain RPC is at 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.

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.

Understanding Optimism

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

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

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

Infura have also produced an interesting blog post entitled "How to use Ethereum proofs".

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}' | 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:

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:

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

Demonstrating resolution from ENS Chain

We can demonstrate resolving a name from our ‘ENS Chain’ using our L1Resolver (deployed on Sepolia) and our OwnedResolver instance (deployed on ENS Chain).

Conceptual explainer

On L1 we are using the evmgateway code to build a request that will revert with the ERC-3668 revert error OffchainLookup appropriately populated with calldata for fetching the data contained within the appropriate storage slots 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 (byOPVerifier) against the storage proofs posted onto L1 by the ‘ENSChain’. An example response can be seen here.

Basic demo

This basic demo is adapted from the crosschain-resolver demo built by Makoto/Nick 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

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:

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();

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)
  • 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

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:

  • 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.

Note: Run this is a browser window that has a globally exposed instance of 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.

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.

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
  • 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.
  • Step 4. You can set the resolver for your second level name to use the L1UnruggableResolver from the 'Subnames' tab.
  • 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).

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).

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'.

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 / 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.

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:

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.

Subscribe to clowes.eth
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
This entry has been permanently stored onchain and signed by its creator.