From: Ardis Lu Date: Thu, 23 Nov 2023 07:02:12 +0000 (-0800) Subject: Add Old ENS Public Resolver 2 and add warning X-Git-Url: https://git.ardis.lu/?a=commitdiff_plain;h=f85457ffa03a0fa99c423aa37d563c5384e91592;p=ens2hex Add Old ENS Public Resolver 2 and add warning This is a necessary tradeoff to perform only one network request --- diff --git a/README.md b/README.md index 1069729..248896f 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,12 @@ Minimal website to look up the Ethereum hexadecimal (`0x`) address for every [ENS](https://ens.domains/) (`.eth`) name in a list. The keccak256 implementation is from Paul Miller's [noble-hashes](https://github.com/paulmillr/noble-hashes) library. + +## Hardcoded ENS resolvers + +> ![WARNING]
+> This tool will report an address of `0x0000000000000000000000000000000000000000` for ENS names that do not have an address set, or that use a custom ENS resolver. + +The [official method of resolving an ENS name](https://docs.ens.domains/dapp-developer-guide/resolving-names) requires (1) getting the ENS resolver for the name, then (2) asking the resolver for the name's address. + +To complete the lookup in one network request, this tool skips step (1) above and instead directly queries all the names against the ENS resolvers ["ENS: Public Resolver 2" (`0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41`)](https://etherscan.io/address/0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41) and ["ENS: Old Public Resolver 2" (`0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8`)](https://etherscan.io/address/0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8). These two resolvers will handle the majority of ENS names, but **this tool will miss ENS names where the owner has set a custom resolver**. diff --git a/index.html b/index.html index 527e34e..28c720f 100644 --- a/index.html +++ b/index.html @@ -275,7 +275,7 @@ How does it work?

For each ENS name in the list, this tool will calculate the name's namehash according to ERC-137.

-

Then it will prepare a JSON-RPC request to query the multicall3 smart contract, which in turn contains a batched call to the ENS PublicResolver smart contract for the hexadecimal addresses.

+

Then it will prepare a JSON-RPC request to query the multicall3 smart contract, which in turn contains batched calls to the ENS Public Resolver 2 and the Old ENS Public Resolver 2 smart contracts for the hexadecimal addresses.

Finally the tool sends the request to an Ethereum node, which executes the payload and provides a response.

@@ -283,7 +283,7 @@ Credits

This tool uses Cloudflare's Ethereum gateway to query the ENS smart contract on the Ethereum blockchain. Click here or here to see a list of some other public nodes you could use.

-

In the JavaScript, the only third-party code is the keccak256 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.

+

In the JavaScript, the only third-party code is the keccak256 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.

The keccak256 implementation was bundled from Paul Miller's noble-hashes project.

Click here to view this site's source code on GitHub.

@@ -383,7 +383,7 @@ 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 = @@ -392,12 +392,19 @@ 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 @@ -407,18 +414,30 @@ 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 = {}; @@ -426,7 +445,9 @@ 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; }