If you’d like to deploy LNS on your own network, or deploy your own copy of LNS on a public network, this guide shows you how. If you want to use an existing LNS deployment, see Resolving Names, Managing Names, and Registering & Renewing Names instead.
On this page we will use Javascript, Web3, and Hardhat with npm for simplicity. You will find a complete migration file example at the bottom of this page.
Please be aware that existing frameworks such as waffle and embark have support for local LNS deployment as well.
Importing contracts
The essential smart contracts are published as npm modules. You can install them in your npm project with npm install @ensdomains/ens-contracts. Now, you can require them in a migration script as follows (see the Truffle Documentation on working with contract artifacts and npm for details)
Once deployed, you will have a fresh LNS registry, whose root node is owned by the account that submitted the transaction. This account has total control over the LNS registry - it can create and replace any node in the entire tree.
From here, it's possible to create and manage names by directly interacting with the registry, as described in Managing Names. However, you will probably want to deploy a resolver, and you may want to deploy a registrar so other users can register names.
Deploy a Resolver
Records in the registry can point to resolver contracts which store additional domain information. The most common use-case is to store an address for a domain, but storing a contract ABI or text is also possible. For most purposes on private networks it's convenient to have an unrestricted general-purpose resolver available. Deploying one is straightforward:
Above, we first create a new top-level domain, "resolver", then set its resolver address to our newly deployed public resolver. Finally, we set up an address record for "resolver", pointing back to the resolver address. In effect, the resolver is answering queries about its own address. After this, anyone can find the public resolver at the special LNS name "resolver". We call this function after deploying the public resolver in a .then() block as we did with the resolver.
Deploy a Registrar
So far, domains can only be registered manually by the owner of the registry's root node. Fortunately, contracts can also own nodes. This means we can set up a registrar contract as the owner of a node, e.g. "test", in the registry which enables it to distribute subdomains such as "mycontract.test". It allows us to have custom, on-chain logic which governs domain allocation. Once we own a (sub-)node we are free to repeat this process and set up another registrar. If you are part of the "myorg" organisation you could register "myorg.test" and let it point to your custom registrar which only allows certified members of your organisation to claim subdomains such as "bob.myorg.test". For our private network, we'll use the simple 'first come, first served' FIFSRegistrar, and set it as the owner of the top-level domain "test" in our migration script:
We can combine the steps above in a single hardhat migration file. This allows us to deploy LNS in one go:
contracts/deps.sol
//SPDX-License-Identifier: MIT
// These imports are here to force Hardhat to compile contracts we depend on in our tests but don't need anywhere else.
import "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol";
import "@ensdomains/ens-contracts/contracts/registry/FIFSRegistrar.sol";
import "@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol";
script/deploy.js
consthre=require("hardhat");constnamehash=require('eth-ens-namehash');consttld="test";constethers=hre.ethers;constutils=ethers.utils;constlabelhash= (label) =>utils.keccak256(utils.toUtf8Bytes(label))constZERO_ADDRESS="0x0000000000000000000000000000000000000000";constZERO_HASH="0x0000000000000000000000000000000000000000000000000000000000000000";asyncfunctionmain() {constENSRegistry=awaitethers.getContractFactory("ENSRegistry")constFIFSRegistrar=awaitethers.getContractFactory("FIFSRegistrar")constReverseRegistrar=awaitethers.getContractFactory("ReverseRegistrar")constPublicResolver=awaitethers.getContractFactory("PublicResolver")constsigners=awaitethers.getSigners();constaccounts=signers.map(s =>s.address)constens=awaitENSRegistry.deploy()awaitens.deployed()constresolver=awaitPublicResolver.deploy(ens.address,ZERO_ADDRESS);awaitresolver.deployed()awaitsetupResolver(ens, resolver, accounts)constregistrar=awaitFIFSRegistrar.deploy(ens.address,namehash.hash(tld));awaitregistrar.deployed()awaitsetupRegistrar(ens, registrar);constreverseRegistrar=awaitReverseRegistrar.deploy(ens.address,resolver.address);awaitreverseRegistrar.deployed()awaitsetupReverseRegistrar(ens, registrar, reverseRegistrar, accounts);};asyncfunctionsetupResolver(ens, resolver, accounts) {constresolverNode=namehash.hash("resolver");constresolverLabel=labelhash("resolver");awaitens.setSubnodeOwner(ZERO_HASH, resolverLabel, accounts[0]);awaitens.setResolver(resolverNode,resolver.address);await resolver['setAddr(bytes32,address)'](resolverNode,resolver.address);}asyncfunctionsetupRegistrar(ens, registrar) {awaitens.setSubnodeOwner(ZERO_HASH,labelhash(tld),registrar.address);}asyncfunctionsetupReverseRegistrar(ens, registrar, reverseRegistrar, accounts) {awaitens.setSubnodeOwner(ZERO_HASH,labelhash("reverse"), accounts[0]);awaitens.setSubnodeOwner(namehash.hash("reverse"),labelhash("addr"),reverseRegistrar.address);}// We recommend this pattern to be able to use async/await everywhere// and properly handle errors.main().then(() =>process.exit(0)).catch((error) => {console.error(error);process.exit(1); });
To execute the migration file on hardhat, run the following command line.
npx hardhat run scripts/deploy.js
Deploying ENS in a single transaction
Alternately you may wish to deploy a test registrar and its dependencies with a single transaction. This is useful for example in unit tests where you wish to start from a clean slate in each test. In many cases it will also be faster than sending a series of separate transactions.
This can be done by deploying a new contract that creates and sets up all the other contracts in its constructor. The below code creates all the LNS contracts and assigns the eth TLD to the FIFS Registrar so that any eth domain may be registered in the unit tests.
pragma solidity >=0.8.4;
import {INameWrapper, PublicResolver} from '@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol';
import '@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol';
import '@ensdomains/ens-contracts/contracts/registry/FIFSRegistrar.sol';
import {NameResolver, ReverseRegistrar} from '@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol';
// Construct a set of test ENS contracts.
contract ENSDeployer {
bytes32 public constant TLD_LABEL = keccak256('eth');
bytes32 public constant RESOLVER_LABEL = keccak256('resolver');
bytes32 public constant REVERSE_REGISTRAR_LABEL = keccak256('reverse');
bytes32 public constant ADDR_LABEL = keccak256('addr');
ENSRegistry public ens;
FIFSRegistrar public fifsRegistrar;
ReverseRegistrar public reverseRegistrar;
PublicResolver public publicResolver;
function namehash(bytes32 node, bytes32 label) public pure returns (bytes32) {
return keccak256(abi.encodePacked(node, label));
}
constructor() public {
ens = new ENSRegistry();
publicResolver = new PublicResolver(ens, INameWrapper(address(0)));
// Set up the resolver
bytes32 resolverNode = namehash(bytes32(0), RESOLVER_LABEL);
ens.setSubnodeOwner(bytes32(0), RESOLVER_LABEL, address(this));
ens.setResolver(resolverNode, address(publicResolver));
publicResolver.setAddr(resolverNode, address(publicResolver));
// Create a FIFS registrar for the TLD
fifsRegistrar = new FIFSRegistrar(ens, namehash(bytes32(0), TLD_LABEL));
ens.setSubnodeOwner(bytes32(0), TLD_LABEL, address(fifsRegistrar));
// Construct a new reverse registrar and point it at the public resolver
reverseRegistrar = new ReverseRegistrar(
ens,
NameResolver(address(publicResolver))
);
// Set up the reverse registrar
ens.setSubnodeOwner(bytes32(0), REVERSE_REGISTRAR_LABEL, address(this));
ens.setSubnodeOwner(
namehash(bytes32(0), REVERSE_REGISTRAR_LABEL),
ADDR_LABEL,
address(reverseRegistrar)
);
}
}