Skip to content

Instantly share code, notes, and snippets.

@brock-haugen
Created September 23, 2022 02:07
Embed
What would you like to do?
// ens-utils.js
// import a couple data formatting libraries
const { default: BigNumber } = require('bignumber.js');
const uts46 = require('idna-uts46-hx');
// make sure to also impmort web3.js
const Web3 = require('web3');
// configure BigNumber to not round large floats
BigNumber.config({ EXPONENTIAL_AT: 100 });
// define our current ENS contract addresses
const ENS_NFT_ADDRESS = '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85';
const ENS_RESOLVER_ADDRESS = '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41';
const ENS_REVERSE_RESOLVER_ADDRESS = '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C';
// if we're working from a server, make sure to point to an ETH node endpoint
// (e.g. a locally running node, Infura, etc)
const WEB3_HOST = 'http://127.0.0.1:8545';
// and instantiate a new Web3 instance using it
const web3 = new Web3(WEB3_HOST);
// ENSIP-1 / EIP-137 - aka "namehash"
function namehash(name) {
let node = '0000000000000000000000000000000000000000000000000000000000000000';
// do a basic null check
if (!name) {
return `0x${node}`;
}
// go through the namehash algorithm
let parts = [];
try {
// parse out the name and split on the periods
parts = uts46.toUnicode(name, { useStd3ASCII: true }).split('.');
} catch (e) {
// this is a common error - e.g. "---.eth" isn't valid based on the namehash algorithm
if (!`${e}`.includes('Illegal char')) {
console.log('FAILED NAMEHASH FOR', name);
}
parts = [];
}
// filter out any empty parts
parts = parts.filter((v) => v?.length > 0);
// go through each part of the name
while (parts.length) {
const label = parts.pop();
// hash it
const labelSha3 = Web3.utils.sha3(label).slice(2);
node = `0x${node}${labelSha3}`;
// and then hash it again
node = Web3.utils.sha3(node).slice(2);
}
// return our recursively hashed value
return `0x${node}`;
}
async function getOwnerAddressForENSName(name) {
// do a basic null check
if (!name) {
return null;
}
// define a new contract instance for ENS NFTs
const nftContract = new web3.eth.Contract(
[
{
inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }],
name: 'ownerOf',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
],
ENS_NFT_ADDRESS
);
// convert the name to a tokenId
const tokenId = new BigNumber(Web3.utils.sha3(name.replace(/\.eth$/, ''))).toString();
// ask the contract for the current owner and return the result
return nftContract.methods
.ownerOf(tokenId)
.call()
.catch(() => null);
}
async function getAddressFromENSName(name) {
// some basic validity checks
// @NOTE: non .eth names are valid, but may require a different resolver contract address
if (!name || !name.endsWith('.eth')) {
return null;
}
// define a new contract instance for our ENS resolver
const resolverContract = new web3.eth.Contract(
[
{
constant: true,
inputs: [{ internalType: 'bytes32', name: 'node', type: 'bytes32' }],
name: 'addr',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
payable: false,
stateMutability: 'view',
type: 'function',
},
],
ENS_RESOLVER_ADDRESS
);
// convert our name to a node hash
const node = namehash(name);
// ask the contract for the current address and return the result
return resolverContract.methods
.addr(node)
.call()
.catch(() => null);
}
async function getENSNameFromAddress(address) {
// some basic validity checks
if (!address || address.length !== 42) {
return null;
}
// make sure our address is the checksum version
address = Web3.utils.toChecksumAddress(address);
// define a new contract instance for our ENS reverse resolver
const reverseResolverContract = new web3.eth.Contract(
[
{
inputs: [{ internalType: 'address[]', name: 'addresses', type: 'address[]' }],
name: 'getNames',
outputs: [{ internalType: 'string[]', name: 'r', type: 'string[]' }],
stateMutability: 'view',
type: 'function',
},
],
ENS_REVERSE_RESOLVER_ADDRESS
);
// ask the contract for the name that maps from the address
// @NOTE: you can pass multiple addresses in a single call with this method
const [name] = await reverseResolverContract.methods
.getNames([address])
.call()
.catch(() => []);
// @NOTE: ideally we double check that the reverse resolver is correct
// this can be done by comparing against the name => address mapping
if (address !== (await getAddressFromENSName(name))) {
return null;
}
return name;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment