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
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.
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".
Once setup, here are some useful commands:
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'
cat deployments/ens-chain-sepolia-final/L1StandardBridgeProxy.json | jq -r .address
curl -H "Content-Type: application/json" --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\",\"params\":[\"0xfc04d70bea992da2c67995bbddc3500767394513\", \"latest\"],\"id\":67}" https://chain.enstools.com
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
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
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
OPVerifier
: 0xdaa862764b64CF3D1f8AfCb8C9137De3f3a8A19E
L1Resolver
: 0x1eC31D576d602D685496605A81323D0678d955d0
L1UnruggableResolver
: 0xce03Dde5d3ab55b94D10202d0c0155f4A8a7f2a7
We can demonstrate resolving a name from our ‘ENS Chain’ using our L1Resolver (deployed on Sepolia) and our OwnedResolver
instance (deployed on ENS Chain).
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.
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});
L1Resolver
instance (I have chosen to do this using the ENS App)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:
cast call 0xC6348c2b785770E5ac1b3B6EE5cD8E9961ee7540 "addr(bytes32)" 0x7a46ad5d836d91ffc5b5682fcac3eccb728a10e2ced12dd9f29bda2be8694240 --rpc-url https://chain.enstools.com
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.
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.
blog-demo.eth
Note: If you are not connected to Sepolia you will be prompted to change networks.
L2SubnameRegistrar
on the L2NameWrapper
deployed on ENS Chain.L1UnruggableResolver
from the 'Subnames' tab.Hurrah ! Now users can register subnames of blog-demo.eth
!
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..
Once registered you can set the resolution address for your subname.
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).
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.