]> git.ardis.lu - ens2hex/commitdiff
Add Old ENS Public Resolver 2 and add warning
authorArdis Lu <ardisglu@gmail.com>
Thu, 23 Nov 2023 07:02:12 +0000 (23:02 -0800)
committerArdis Lu <ardisglu@gmail.com>
Thu, 23 Nov 2023 07:02:12 +0000 (23:02 -0800)
This is a necessary tradeoff to perform only one network request

README.md
index.html

index 1069729d290ac35cd6d60370301da83aae4513d0..248896f97095e9f8f6230a17edc565f6bc21ef03 100644 (file)
--- 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]<br>
+> 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**.
index 527e34eef71ffb197af190788632dc3c1319cf53..28c720fa73e991d4997d33260b52bde9c7d8fc9d 100644 (file)
         <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;
     }
This page took 0.047761 seconds and 4 git commands to generate.