This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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