<summary>How does it work?</summary>
<div class="details-body">
<p>For each ENS name in the list, this tool will calculate the name's <code>namehash</code> according to <a href="https://ercs.ethereum.org/ERCS/erc-137">ERC-137</a>.</p>
- <p>Then it will prepare a <a href="https://ethereum.org/en/developers/docs/apis/json-rpc/">JSON-RPC</a> request to query the <a href="https://www.multicall3.com/"><code>multicall3</code></a> smart contract, which in turn contains a batched call to the <a href="https://etherscan.io/address/0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41">ENS PublicResolver</a> smart contract for the hexadecimal addresses.</p>
+ <p>Then it will prepare a <a href="https://ethereum.org/en/developers/docs/apis/json-rpc/">JSON-RPC</a> request to query the <a href="https://www.multicall3.com/"><code>multicall3</code></a> smart contract, which in turn contains batched calls to the <a href="https://etherscan.io/address/0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41">ENS Public Resolver 2</a> and the <a href="https://etherscan.io/address/0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8">Old ENS Public Resolver 2</a> smart contracts for the hexadecimal addresses.</p>
<p>Finally the tool sends the request to an <a href="https://ethereum.org/en/developers/docs/nodes-and-clients/">Ethereum node</a>, which executes the payload and provides a response.</p>
</div>
</details>
<summary>Credits</summary>
<div class="details-body">
<p>This tool uses <a href="https://developers.cloudflare.com/web3/">Cloudflare's Ethereum gateway</a> to query the ENS smart contract on the Ethereum blockchain. <a href="https://ethereumnodes.com/">Click here</a> or <a href="https://chainlist.org/chain/1">here</a> to see a list of some other public nodes you could use.</p>
- <p>In the JavaScript, the only third-party code is the <code>keccak256</code> implementation used to hash the ENS names. The rest of the tool consists of about 80 lines of vanilla JavaScript to manipulate the names and prepare the JSON-RPC payload.</p>
+ <p>In the JavaScript, the only third-party code is the <code>keccak256</code> implementation used to hash the ENS names. The rest of the tool consists of about 200 lines of vanilla JavaScript to manipulate the names, prepare the JSON-RPC payload, and parse the response.</p>
<p>The <code>keccak256</code> implementation was bundled from Paul Miller's <a href="https://github.com/paulmillr/noble-hashes">noble-hashes</a> project.</p>
<p><a href="https://github.com/ardislu/ens2hex">Click here</a> to view this site's source code on GitHub.</p>
</div>
return [...hash].map(v => v.toString(16).padStart(2, '0')).join(''); // Convert byte array into 0-padded string. Must spread typed array into non-typed array to allow .map() to output strings
}
- // Batch multiple "addr(bytes32)" calls to ENS Public Resolver using the multicall3 smart contract
+ // Batch multiple "addr(bytes32)" calls to both "ENS Public Resolver 2" AND "Old ENS Public Resolver 2" using the multicall3 smart contract
async function multicall(rpc, names) {
// Construct payload
const multiCallPrefix =
names.length.toString(16).padStart(64, '0'); // Length of tuple
let multiCallTupleOffsets = '';
- let multiCallTuples = '';
+ let multiCallTuples1 = '';
+ let multiCallTuples2 = '';
for (const [index, name] of names.entries()) {
// Each tuple takes exactly 6 32-byte slots, starting after these offsets themselves end
multiCallTupleOffsets += ((6 * 32 * index) + (names.length * 32)).toString(16).padStart(64, '0');
- multiCallTuples +=
- '4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41'.padStart(64, '0') + // ENS PublicResolver
+ multiCallTuples1 +=
+ '4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41'.padStart(64, '0') + // ENS Public Resolver 2
+ '0'.repeat(64) + // Hardcode allowFailure = false
+ '60'.padStart(64, '0') + // Pointer to next calldata slot
+ '24'.padStart(64, '0') + // 0x24 == 36 bytes immediately follow
+ `3b3b57de${namehash(name)}`.padEnd(64 * 2, '0'); // Calldata for "addr(bytes32)", filling 2 slots
+ multiCallTuples2 +=
+ '226159d592E2b063810a10Ebf6dcbADA94Ed68b8'.padStart(64, '0') + // Old ENS Public Resolver 2
'0'.repeat(64) + // Hardcode allowFailure = false
'60'.padStart(64, '0') + // Pointer to next calldata slot
'24'.padStart(64, '0') + // 0x24 == 36 bytes immediately follow
const response = await fetch(rpc, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
+ body: JSON.stringify([{
+ jsonrpc: '2.0',
+ method: 'eth_call',
+ params: [
+ {
+ to: '0xcA11bde05977b3631167028862bE2a173976CA11', // multicall3
+ data: multiCallPrefix + multiCallTupleOffsets + multiCallTuples1
+ },
+ 'safe'
+ ],
+ id: 'new'
+ },
+ {
jsonrpc: '2.0',
method: 'eth_call',
params: [
{
to: '0xcA11bde05977b3631167028862bE2a173976CA11', // multicall3
- data: multiCallPrefix + multiCallTupleOffsets + multiCallTuples
+ data: multiCallPrefix + multiCallTupleOffsets + multiCallTuples2
},
'safe'
],
- id: 'multicall'
- }),
+ id: 'old'
+ }]),
}).then(r => r.json());
const addresses = {};
const prefixOffset = 64 * (2 + names.length); // First 2 slots encode struct offset and length, then 1 slot for the offset of each result
const payloadOffset = 64 * (3 + index * 4); // First 3 slots of each result is multicall overhead, actual result starts in slot 4
const totalOffset = prefixOffset + payloadOffset;
- addresses[name] = `0x${response.result.slice(totalOffset + 26, totalOffset + 66)}`; // Parse address from slot 4
+ const addr1 = `0x${response[0].result.slice(totalOffset + 26, totalOffset + 66)}`; // Parse address from slot 4
+ const addr2 = `0x${response[1].result.slice(totalOffset + 26, totalOffset + 66)}`;
+ addresses[name] = addr1 === `0x${'0'.repeat(40)}` ? addr2 : addr1;
}
return addresses;
}