diff --git a/bun.lock b/bun.lock index 67d5cba2..257dc76e 100644 --- a/bun.lock +++ b/bun.lock @@ -15,16 +15,19 @@ "@namestone/ezccip": "^0.1.0", "@nomicfoundation/edr": "0.12.0-next.4", "@nomicfoundation/hardhat-foundry": "^1.2.0", + "@nomicfoundation/hardhat-keystore": "3.0.0", "@nomicfoundation/hardhat-network-helpers": "3.0.0", "@nomicfoundation/hardhat-viem": "3.0.0", "@rocketh/deploy": "0.14.0", "@rocketh/proxy": "0.14.0", "@rocketh/read-execute": "0.14.0", + "@rocketh/signer": "0.14.0", "@rocketh/verifier": "0.14.4", "@rocketh/viem": "0.14.0", "@types/bun": "latest", "chai": "^5.1.1", "dns-packet": "^5.6.1", + "eip-1193": "^0.6.5", "hardhat": "3.0.1", "hardhat-deploy": "2.0.0-next.41", "prettier": "^3.5.3", @@ -128,7 +131,7 @@ "@namestone/ezccip": ["@namestone/ezccip@0.1.1", "", { "dependencies": { "ethers": "^6.13.4" } }, "sha512-MZ0pLyAT695OfbXGIKNqHRHylIsk9KHMhpRP3ZgHVJxv5TZi1ouptylfEYr05qDqX33wYnR1w9BIqvcYcqruvA=="], - "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + "@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], "@noble/curves": ["@noble/curves@1.9.2", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g=="], @@ -154,6 +157,8 @@ "@nomicfoundation/hardhat-foundry": ["@nomicfoundation/hardhat-foundry@1.2.0", "", { "dependencies": { "picocolors": "^1.1.0" }, "peerDependencies": { "hardhat": "^2.26.0" } }, "sha512-2AJQLcWnUk/iQqHDVnyOadASKFQKF1PhNtt1cONEQqzUPK+fqME1IbP+EKu+RkZTRcyc4xqUMaB0sutglKRITg=="], + "@nomicfoundation/hardhat-keystore": ["@nomicfoundation/hardhat-keystore@3.0.0", "", { "dependencies": { "@noble/ciphers": "1.2.1", "@noble/hashes": "1.7.1", "@nomicfoundation/hardhat-errors": "^3.0.0", "@nomicfoundation/hardhat-utils": "^3.0.0", "@nomicfoundation/hardhat-zod-utils": "^3.0.0", "chalk": "^5.3.0", "debug": "^4.3.2", "zod": "^3.23.8" }, "peerDependencies": { "hardhat": "^3.0.0" } }, "sha512-a6rw/72LBg6PVlk5lVeWCtQsw4LZnNXM7XroIMWv7aN00o/IBgXPRLePaX+zZXvJqWYZrw2XcZMay1eN9m+rRA=="], + "@nomicfoundation/hardhat-network-helpers": ["@nomicfoundation/hardhat-network-helpers@3.0.0", "", { "dependencies": { "@nomicfoundation/hardhat-errors": "^3.0.0", "@nomicfoundation/hardhat-utils": "^3.0.0" }, "peerDependencies": { "hardhat": "^3.0.0" } }, "sha512-Nemas5cEaHyb4QoK40+USMxHMhIr4csRhpJFzm1T9I0/Wd1szw9kG412ubjUJxgm82ofyJGH3i5NKu7QgprmVg=="], "@nomicfoundation/hardhat-utils": ["@nomicfoundation/hardhat-utils@3.0.0", "", { "dependencies": { "@streamparser/json-node": "^0.0.22", "debug": "^4.3.2", "env-paths": "^2.2.0", "ethereum-cryptography": "^2.2.1", "fast-equals": "^5.0.1", "json-stream-stringify": "^3.1.6", "rfdc": "^1.3.1", "undici": "^6.16.1" } }, "sha512-dpzumbxM69ny/BSVd/8jquZO3wjg61e+S81DJPJwQ7naeZNai1r9gYuxT65VgKKTYZG/xKwrP36tJvB+gRtBrg=="], @@ -194,6 +199,8 @@ "@rocketh/read-execute": ["@rocketh/read-execute@0.14.0", "", { "dependencies": { "named-logs": "^0.3.2", "viem": "^2.23.12" } }, "sha512-+QhG2I34kg36FklCVotZdKp5UuNlq+AfnKka75wVJigdAn3+RSheYf9yhofT8tIGa4dv6p4LE5eett2HOmfENQ=="], + "@rocketh/signer": ["@rocketh/signer@0.14.0", "", { "dependencies": { "eip-1193-signer": "^0.1.1" } }, "sha512-FQNpHoxXEkc2vDsns52cCNIZcG7CLY1iKPldCMsXWUfTF7iPK0c1/ICTRGz/vFcvsL5XUijyPPTslIdJnp1SnQ=="], + "@rocketh/verifier": ["@rocketh/verifier@0.14.4", "", { "dependencies": { "@types/fs-extra": "^11.0.4", "@types/qs": "^6.9.18", "chalk": "5.4.1", "commander": "^13.1.0", "fs-extra": "^11.3.0", "ldenv": "^0.3.12", "neoqs": "^6.13.0" }, "peerDependencies": { "rocketh": "0.14.4" }, "bin": { "rocketh-verify": "dist/cli.js" } }, "sha512-nfxigIPC3zXZ6ajJhxEYJ7zkm0nn3kNHRryY1HPOlhnAjwTwzELLzDP2YFutvhwRcaEGcQz5vIeQJEVAaFG4zw=="], "@rocketh/viem": ["@rocketh/viem@0.14.0", "", { "dependencies": { "named-logs": "^0.3.2" }, "peerDependencies": { "viem": "^2.23.12" } }, "sha512-1RRVpCgPJrPQEIs9OUyVBzCzXNbhIME+HFJBLdYI4y4ol94otqcaTnoXC91bRLmTPb60fvfssKV4p8gK9pyUQw=="], @@ -356,7 +363,7 @@ "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], - "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "chalk": ["chalk@5.6.0", "", {}, "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ=="], "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], @@ -400,8 +407,12 @@ "dotenv-expand": ["dotenv-expand@10.0.0", "", {}, "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A=="], + "eip-1193": ["eip-1193@0.6.5", "", {}, "sha512-KXCSdjFLIT5/06rMD2pMqoAZhZcTg4EofiCI70ovIOy8L/6twGJFE+RtW89S/hMFKDoNEGJ/WK8jQv7CpuGDgg=="], + "eip-1193-jsonrpc-provider": ["eip-1193-jsonrpc-provider@0.4.3", "", { "dependencies": { "named-logs": "^0.3.2", "promise-throttle": "^1.1.2" } }, "sha512-xcrz22ArOqvbXt4LHOeV5JooL8jTt/sv8WIH7MLQTn8z7fQwRDDzUECgIwZaX1Irpn/HIZGiu6YZwIoRVfPEow=="], + "eip-1193-signer": ["eip-1193-signer@0.1.1", "", { "dependencies": { "viem": "^2.23.12" } }, "sha512-dEMgcibZnOccQDDQ5zA6hVvDSNCce7tfzu/E08cCd3fBhcX7Sh8P83YJKfd7f0XNUXJBQ0aJSO31eZ5Cic/Qpg=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], @@ -756,8 +767,12 @@ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@nomicfoundation/hardhat-keystore/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], + "@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="], + "@rocketh/verifier/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "@rocketh/verifier/rocketh": ["rocketh@0.14.4", "", { "dependencies": { "@types/figlet": "^1.7.0", "@types/prompts": "^2.4.9", "commander": "^13.1.0", "eip-1193-jsonrpc-provider": "^0.4.3", "ethers": "^6.13.5", "figlet": "^1.8.0", "ldenv": "^0.3.12", "named-logs": "^0.3.2", "named-logs-console": "^0.3.1", "prompts": "^2.4.2", "tsx": "^4.19.3", "viem": "^2.23.12" }, "bin": { "rocketh": "dist/cli.js" } }, "sha512-nnLt74glSuEVqdiNL8DR0dPeHluWqK5GqmlKQ89TBsUyo3r8Iq04y+eEsZ6QAtsY4KZKdRoFLWQ8q4xFwj16oQ=="], "@vitest/snapshot/@vitest/pretty-format": ["@vitest/pretty-format@3.1.3", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA=="], @@ -786,8 +801,6 @@ "got/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], - "hardhat/chalk": ["chalk@5.6.0", "", {}, "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ=="], - "hardhat-deploy/zod": ["zod@4.1.5", "", {}, "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg=="], "http-proxy/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -802,6 +815,8 @@ "ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], + "ox/@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + "solhint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "solhint/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], diff --git a/contracts/deploy/l1/01_DNSAliasResolver.ts b/contracts/deploy/l1/01_DNSAliasResolver.ts index 7961ac9e..54ba54d5 100755 --- a/contracts/deploy/l1/01_DNSAliasResolver.ts +++ b/contracts/deploy/l1/01_DNSAliasResolver.ts @@ -1,13 +1,13 @@ import { artifacts, execute } from "@rocketh"; export default execute( - async ({ deploy, get, namedAccounts: { deployer } }) => { + async ({ deploy, get, getV1, namedAccounts: { deployer } }) => { const rootRegistry = get<(typeof artifacts.PermissionedRegistry)["abi"]>("RootRegistry"); - const batchGatewayProvider = get<(typeof artifacts.GatewayProvider)["abi"]>( - "BatchGatewayProvider", - ); + const batchGatewayProvider = getV1< + (typeof artifacts.GatewayProvider)["abi"] + >("BatchGatewayProvider"); const dnsAliasResolver = await deploy("DNSAliasResolver", { account: deployer, diff --git a/contracts/deploy/l1/01_DNSTLDResolver.ts b/contracts/deploy/l1/01_DNSTLDResolver.ts index 101234eb..367591d8 100755 --- a/contracts/deploy/l1/01_DNSTLDResolver.ts +++ b/contracts/deploy/l1/01_DNSTLDResolver.ts @@ -1,5 +1,10 @@ import { artifacts, execute } from "@rocketh"; -import { zeroAddress } from "viem"; +import { + encodeFunctionData, + TransactionReceiptNotFoundError, + UserRejectedRequestError, + zeroAddress, +} from "viem"; import { dnsEncodeName } from "../../test/utils/utils.js"; import { MAX_EXPIRY } from "../constants.js"; @@ -20,29 +25,32 @@ export default execute( deploy, execute: write, get, - read, + getV1, + tx, namedAccounts: { deployer }, + addressSigners, network, + viem, }) => { const ensRegistryV1 = - get<(typeof artifacts.ENSRegistry)["abi"]>("ENSRegistry"); + getV1<(typeof artifacts.ENSRegistry)["abi"]>("ENSRegistry"); - const dnsTLDResolverV1 = get<(typeof artifacts.OffchainDNSResolver)["abi"]>( - "OffchainDNSResolver", - ); + const dnsTLDResolverV1 = getV1< + (typeof artifacts.OffchainDNSResolver)["abi"] + >("OffchainDNSResolver"); - const publicSuffixList = get< + const publicSuffixList = getV1< (typeof artifacts.SimplePublicSuffixList)["abi"] >("SimplePublicSuffixList"); const rootRegistry = get<(typeof artifacts.PermissionedRegistry)["abi"]>("RootRegistry"); - const dnssecOracle = get<(typeof artifacts.DNSSEC)["abi"]>("DNSSECImpl"); + const dnssecOracle = getV1<(typeof artifacts.DNSSEC)["abi"]>("DNSSECImpl"); - const batchGatewayProvider = get<(typeof artifacts.GatewayProvider)["abi"]>( - "BatchGatewayProvider", - ); + const batchGatewayProvider = getV1< + (typeof artifacts.GatewayProvider)["abi"] + >("BatchGatewayProvider"); const dnssecGatewayProvider = get< (typeof artifacts.GatewayProvider)["abi"] @@ -61,37 +69,206 @@ export default execute( ], }); - let suffixes = network.tags.local - ? ["com", "org", "net", "xyz"] - : await fetchPublicSuffixes(); - suffixes = ( - await Promise.all( - suffixes.map((suffix) => - read(publicSuffixList, { - functionName: "isPublicSuffix", - args: [dnsEncodeName(suffix)], - }).then((pub) => (pub ? suffix : "")), - ), - ) - ).filter(Boolean); - - // TODO: this create 1000+ transactions - // batching is a mess in rocketh - // anvil batching appears broken (only mines 1-2 tx) - for (const suffix of suffixes) { - await write(rootRegistry, { - account: deployer, - functionName: "register", - args: [ - suffix, - deployer, // TODO: ownership - zeroAddress, - dnsTLDResolver.address, - 0n, // TODO: roles - MAX_EXPIRY, - ], + let suffixes = await fetchPublicSuffixes(); + suffixes = await viem.publicClient + .multicall({ + contracts: suffixes.map((suffix) => ({ + address: publicSuffixList.address, + abi: publicSuffixList.abi, + functionName: "isPublicSuffix", + args: [dnsEncodeName(suffix)], + })), + }) + .then((results) => + results + .map((result, i) => (result.result ? suffixes[i] : "")) + .filter(Boolean), + ); + + suffixes = await viem.publicClient + .multicall({ + contracts: suffixes.map((suffix) => ({ + address: rootRegistry.address, + abi: rootRegistry.abi, + functionName: "getNameData", + args: [suffix], + })), + }) + .then((results) => + results + .map((result, i) => + !result.result || result.result[1].expiry < Date.now() / 1000 + ? suffixes[i] + : "", + ) + .filter(Boolean), + ); + + console.log("suffixes", suffixes); + + const chunks = []; + for (let i = 0; i < suffixes.length; i += 25) { + chunks.push(suffixes.slice(i, i + 25)); + } + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + console.log( + `Sending chunk ${i + 1} of ${chunks.length} (${chunk.length} transactions)`, + ); + + const nonce = await viem.publicClient.getTransactionCount({ + address: deployer, + }); + const fees = await viem.publicClient.estimateFeesPerGas({ + chain: network.chain, }); + + console.log("signing chunk"); + const signedTransactions = await Promise.all( + chunk.map(async (suffix, i) => + viem.walletClient.signTransaction({ + account: addressSigners[deployer].signer.account, + to: rootRegistry.address, + nonce: nonce + i, + data: encodeFunctionData({ + abi: rootRegistry.abi, + functionName: "register", + args: [ + suffix, + deployer, // TODO: ownership + zeroAddress, + dnsTLDResolver.address, + 0n, // TODO: roles + MAX_EXPIRY, + ], + }), + chain: network.chain, + type: "eip1559", + gas: 300_000n, + maxFeePerGas: fees.maxFeePerGas, + maxPriorityFeePerGas: fees.maxPriorityFeePerGas, + }), + ), + ); + + console.log("sending chunk"); + const txs = await Promise.all( + signedTransactions.map((tx) => + viem.walletClient.sendRawTransaction({ + serializedTransaction: tx, + }), + ), + ); + + // ensure txs are in mempool + + console.log("waiting for last transaction"); + // wait for the transaction with the highest nonce + const run = async (): Promise => + viem.publicClient + .waitForTransactionReceipt({ + hash: txs[txs.length - 1], + confirmations: 1, + pollingInterval: network.tags.local ? 150 : 1_000, + retryCount: 12, + }) + .catch(async (e) => { + if (e instanceof UserRejectedRequestError) { + console.log("got user rejected request error, retrying"); + // ensure tx is in mempool + const tx = await viem.publicClient.getTransaction({ + hash: txs[txs.length - 1], + }); + console.log("tx in mempool", tx); + if (!tx) throw new Error("tx not in mempool"); + return run(); + } + throw e; + }) + .then(() => undefined); + await run(); + + const checkAllSuccessful = async (): Promise => { + console.log("checking all were successful"); + await Promise.allSettled( + txs.map((tx) => + viem.publicClient.getTransactionReceipt({ + hash: tx, + }), + ), + ).then((receipts) => { + for (let i = 0; i < receipts.length; i++) { + const receipt = receipts[i]; + if (receipt.status === "rejected") { + if (!(receipt.reason instanceof TransactionReceiptNotFoundError)) + throw receipt.reason; + console.log("transaction not found, waiting then retrying"); + return viem.publicClient + .waitForTransactionReceipt({ + hash: txs[i], + confirmations: 1, + pollingInterval: network.tags.local ? 150 : 1_000, + retryCount: 12, + }) + .then(() => checkAllSuccessful()); + } else { + if (receipt.value.status !== "success") + throw new Error( + `Transaction failed: ${receipt.value.status} / ${txs[i]}`, + ); + } + } + }); + }; + await checkAllSuccessful(); } + + // // Split suffixes into chunks of 100 + // const chunkSize = 100; + // const suffixChunks: string[][] = []; + // for (let i = 0; i < suffixes.length; i += chunkSize) { + // suffixChunks.push(suffixes.slice(i, i + chunkSize)); + // } + + // const + + // for (const chunk of suffixChunks) { + + // await Promise.all( + // chunk.map((suffix) => + // write(rootRegistry, { + // account: deployer, + // functionName: "register", + // args: [ + // suffix, + // deployer, // TODO: ownership + // zeroAddress, + // dnsTLDResolver.address, + // 0n, // TODO: roles + // MAX_EXPIRY, + // ], + // }), + // ), + // ); + // } + + // // TODO: this create 1000+ transactions + // // batching is a mess in rocketh + // // anvil batching appears broken (only mines 1-2 tx) + // for (const suffix of suffixes) { + // await write(rootRegistry, { + // account: deployer, + // functionName: "register", + // args: [ + // suffix, + // deployer, // TODO: ownership + // zeroAddress, + // dnsTLDResolver.address, + // 0n, // TODO: roles + // MAX_EXPIRY, + // ], + // }); + // } }, { tags: ["DNSTLDResolver", "l1"], diff --git a/contracts/deploy/l1/01_ETHTLDResolver.ts b/contracts/deploy/l1/01_ETHTLDResolver.ts index 47715323..6282554b 100755 --- a/contracts/deploy/l1/01_ETHTLDResolver.ts +++ b/contracts/deploy/l1/01_ETHTLDResolver.ts @@ -8,21 +8,28 @@ import { export default execute( async ( - { deploy, execute: write, get, save, namedAccounts: { deployer }, network }, + { + deploy, + execute: write, + get, + getV1, + save, + namedAccounts: { deployer }, + network, + }, args, ) => { if (!args?.l2Deploy) throw new Error("expected L2 deployment"); const ensRegistryV1 = - get<(typeof artifacts.ENSRegistry)["abi"]>("ENSRegistry"); + getV1<(typeof artifacts.ENSRegistry)["abi"]>("ENSRegistry"); - const batchGatewayProvider = get<(typeof artifacts.GatewayProvider)["abi"]>( - "BatchGatewayProvider", - ); + const batchGatewayProvider = getV1< + (typeof artifacts.GatewayProvider)["abi"] + >("BatchGatewayProvider"); - const verifiableFactory = get<(typeof artifacts.VerifiableFactory)["abi"]>( - "DedicatedResolverFactory", - ); + const verifiableFactory = + get<(typeof artifacts.VerifiableFactory)["abi"]>("VerifiableFactory"); const dedicatedResolverImpl = get< (typeof artifacts.DedicatedResolver)["abi"] diff --git a/contracts/deploy/l1/01_ReverseRegistry.ts b/contracts/deploy/l1/01_ReverseRegistry.ts index 9d301a9e..39252fbd 100644 --- a/contracts/deploy/l1/01_ReverseRegistry.ts +++ b/contracts/deploy/l1/01_ReverseRegistry.ts @@ -3,8 +3,14 @@ import { MAX_EXPIRY, ROLES } from "../constants.ts"; // TODO: ownership export default execute( - async ({ deploy, execute: write, get, namedAccounts: { deployer } }) => { - const defaultReverseResolverV1 = get< + async ({ + deploy, + execute: write, + get, + getV1, + namedAccounts: { deployer }, + }) => { + const defaultReverseResolverV1 = getV1< (typeof artifacts.DefaultReverseResolver)["abi"] >("DefaultReverseResolver"); diff --git a/contracts/deploy/l1/02_ETHReverseResolver.ts b/contracts/deploy/l1/02_ETHReverseResolver.ts index 4d5c0a0e..a7fd77de 100644 --- a/contracts/deploy/l1/02_ETHReverseResolver.ts +++ b/contracts/deploy/l1/02_ETHReverseResolver.ts @@ -1,14 +1,20 @@ import { artifacts, execute } from "@rocketh"; -import { MAX_EXPIRY } from "../constants.js"; import { zeroAddress } from "viem"; +import { MAX_EXPIRY } from "../constants.js"; // TODO: ownership export default execute( - async ({ deploy, execute: write, get, namedAccounts: { deployer } }) => { + async ({ + deploy, + execute: write, + get, + getV1, + namedAccounts: { deployer }, + }) => { const ensRegistryV1 = - get<(typeof artifacts.ENSRegistry)["abi"]>("ENSRegistry"); + getV1<(typeof artifacts.ENSRegistry)["abi"]>("ENSRegistry"); - const defaultReverseRegistrarV1 = get< + const defaultReverseRegistrarV1 = getV1< (typeof artifacts.DefaultReverseRegistrar)["abi"] >("DefaultReverseRegistrar"); diff --git a/contracts/deploy/l1/universalResolver/00_UniversalResolver.ts b/contracts/deploy/l1/universalResolver/00_UniversalResolver.ts index e87162df..d0558bc0 100644 --- a/contracts/deploy/l1/universalResolver/00_UniversalResolver.ts +++ b/contracts/deploy/l1/universalResolver/00_UniversalResolver.ts @@ -1,13 +1,13 @@ import { artifacts, execute } from "@rocketh"; export default execute( - async ({ deploy, get, namedAccounts: { deployer } }) => { + async ({ deploy, get, getV1, namedAccounts: { deployer } }) => { const rootRegistry = get<(typeof artifacts.PermissionedRegistry)["abi"]>("RootRegistry"); - const batchGatewayProvider = get<(typeof artifacts.GatewayProvider)["abi"]>( - "BatchGatewayProvider", - ); + const batchGatewayProvider = getV1< + (typeof artifacts.GatewayProvider)["abi"] + >("BatchGatewayProvider"); await deploy("UniversalResolverV2", { account: deployer, diff --git a/contracts/deploy/shared/00_VerifiableFactory.ts b/contracts/deploy/shared/00_VerifiableFactory.ts new file mode 100644 index 00000000..34daca19 --- /dev/null +++ b/contracts/deploy/shared/00_VerifiableFactory.ts @@ -0,0 +1,13 @@ +import { artifacts, execute } from "@rocketh"; + +export default execute( + async ({ deploy, namedAccounts: { deployer } }) => { + await deploy("VerifiableFactory", { + account: deployer, + artifact: artifacts.VerifiableFactory, + }); + }, + { + tags: ["VerifiableFactory", "shared"], + }, +); diff --git a/contracts/deploy/shared/00_DedicatedResolver.ts b/contracts/deploy/shared/01_DedicatedResolver.ts similarity index 71% rename from contracts/deploy/shared/00_DedicatedResolver.ts rename to contracts/deploy/shared/01_DedicatedResolver.ts index ecec7507..c3a55520 100755 --- a/contracts/deploy/shared/00_DedicatedResolver.ts +++ b/contracts/deploy/shared/01_DedicatedResolver.ts @@ -2,11 +2,6 @@ import { artifacts, execute } from "@rocketh"; export default execute( async ({ deploy, namedAccounts: { deployer } }) => { - await deploy("DedicatedResolverFactory", { - account: deployer, - artifact: artifacts.VerifiableFactory, - }); - await deploy("DedicatedResolverImpl", { account: deployer, artifact: artifacts.DedicatedResolver, @@ -14,5 +9,6 @@ export default execute( }, { tags: ["DedicatedResolver", "shared"], + dependencies: ["VerifiableFactory"], }, ); diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 67323a61..54d6d365 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -1,14 +1,23 @@ -import type { HardhatUserConfig } from "hardhat/config"; +import { configVariable, type HardhatUserConfig } from "hardhat/config"; import HardhatChaiMatchersViemPlugin from "@ensdomains/hardhat-chai-matchers-viem"; +import HardhatKeystore from "@nomicfoundation/hardhat-keystore"; import HardhatNetworkHelpersPlugin from "@nomicfoundation/hardhat-network-helpers"; import HardhatViem from "@nomicfoundation/hardhat-viem"; import HardhatDeploy from "hardhat-deploy"; -import HardhatStorageLayoutPlugin from "./plugins/storage-layout/index.ts"; import HardhatIgnoreWarningsPlugin from "./plugins/ignore-warnings/index.ts"; +import HardhatStorageLayoutPlugin from "./plugins/storage-layout/index.ts"; const config = { + networks: { + sepoliaFresh: { + type: "http", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("DEPLOYER_KEY")], + chainId: 11155111, + }, + }, solidity: { compilers: [ { @@ -51,6 +60,7 @@ const config = { HardhatStorageLayoutPlugin, HardhatIgnoreWarningsPlugin, HardhatDeploy, + HardhatKeystore, ], } satisfies HardhatUserConfig; diff --git a/contracts/package.json b/contracts/package.json index 6d2c24b1..ed78297d 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -8,10 +8,12 @@ "@nomicfoundation/hardhat-foundry": "^1.2.0", "@nomicfoundation/hardhat-network-helpers": "3.0.0", "@nomicfoundation/hardhat-viem": "3.0.0", + "@nomicfoundation/hardhat-keystore": "3.0.0", "@rocketh/deploy": "0.14.0", "@rocketh/proxy": "0.14.0", "@rocketh/read-execute": "0.14.0", "@rocketh/verifier": "0.14.4", + "@rocketh/signer": "0.14.0", "@rocketh/viem": "0.14.0", "@types/bun": "latest", "chai": "^5.1.1", @@ -28,7 +30,8 @@ "ts-node": "^10.9.2", "viem": "^2.31.6", "vite-tsconfig-paths": "^5.1.4", - "vitest": "3.1.3" + "vitest": "3.1.3", + "eip-1193": "^0.6.5" }, "peerDependencies": { "typescript": "^5.8.3" diff --git a/contracts/rocketh.ts b/contracts/rocketh.ts index a033cc43..23fe49e6 100644 --- a/contracts/rocketh.ts +++ b/contracts/rocketh.ts @@ -2,7 +2,9 @@ // ------------------------------------------------------------------------------------------------ // Typed Config // ------------------------------------------------------------------------------------------------ -import type { UserConfig } from "rocketh"; +import { resolve } from "path"; +import type { Deployment, UnknownDeployments, UserConfig } from "rocketh"; +import type { Abi } from "viem"; export const config = { accounts: { deployer: { @@ -37,6 +39,15 @@ export const config = { scripts: ["deploy/l1/universalResolver"], tags: [], }, + sepoliaFresh: { + scripts: [ + "lib/ens-contracts/deploy", + "deploy/l1", + "deploy/l2", + "deploy/shared", + ], + tags: ["l1", "l2", "use_root", "allow_unsafe", "legacy"], + }, }, } as const satisfies UserConfig; @@ -56,15 +67,36 @@ export { artifacts }; // ------------------------------------------------------------------------------------------------ import { + loadDeployments, setup, type CurriedFunctions, type Environment as Environment_, } from "rocketh"; +const deploymentsCache = new Map(); + const functions = { ...deployFunctions, ...readExecuteFunctions, ...viemFunctions, + getV1: (env: Environment_) => { + const path = resolve(env.config.deployments, "v1"); + const deployments = (() => { + if (deploymentsCache.has(path)) return deploymentsCache.get(path)!; + const { deployments: deployments_ } = loadDeployments( + path, + env.config.network.name, + false, + ); + deploymentsCache.set(path, deployments_); + return deployments_; + })(); + return (name: string): Deployment => { + const deployment = deployments[name]; + if (!deployment) throw new Error(`V1 Deployment ${name} not found`); + return deployment as Deployment; + }; + }, }; export type Environment = Environment_ & @@ -75,6 +107,7 @@ const enhanced = setup(functions); import type { RockethArguments } from "./script/types.ts"; export const execute = enhanced.deployScript; +export const deployScript = enhanced.deployScript; export const loadAndExecuteDeployments = enhanced.loadAndExecuteDeployments; diff --git a/contracts/script/live-deploy.ts b/contracts/script/live-deploy.ts new file mode 100644 index 00000000..fedcccad --- /dev/null +++ b/contracts/script/live-deploy.ts @@ -0,0 +1,205 @@ +import { executeDeployScripts, loadDeployments, resolveConfig } from "rocketh"; +import { + createClient, + createWalletClient, + hexToNumber, + http, + nonceManager, + type EIP1193RequestFn, + type Hex, + type WalletRpcSchema, +} from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { prepareTransactionRequest, sendRawTransaction } from "viem/actions"; +import { sepolia } from "viem/chains"; + +import { deployArtifact } from "../test/integration/fixtures/deployArtifact.js"; +import { urgArtifact } from "../test/integration/fixtures/externalArtifacts.js"; +import type { RockethArguments } from "./types.ts"; + +const chain = sepolia; +const rpcUrl = process.env.SEPOLIA_RPC_URL as string; + +const client = createClient({ + chain, + transport: http(rpcUrl), +}); + +process.env.BATCH_GATEWAY_URLS = '["x-batch-gateway:true"]'; + +type Signer = { + request: EIP1193RequestFn; +}; + +const getTransactionType = (params: unknown) => { + const serializedType = (params as { type: Hex }).type; + if (serializedType === "0x4") return "eip7702"; + if (serializedType === "0x3") return "eip4844"; + if (serializedType === "0x2") return "eip1559"; + if (serializedType === "0x1") return "eip2930"; + if (serializedType !== "0x" && hexToNumber(serializedType) >= 0xc0) + return "legacy"; + throw new Error(`Unknown transaction type: ${serializedType}`); +}; + +const signer = (privKey: Hex): Signer => { + const acc = privateKeyToAccount(privKey, { nonceManager: nonceManager }); + return { + account: acc, + request: async (request) => { + if (request.method === "eth_sendTransaction") { + for (const [key, value] of Object.entries(request.params[0])) { + if (value === undefined) { + delete request.params[0][key]; + } + } + const prepared = await prepareTransactionRequest(client, { + ...request.params[0], + type: getTransactionType(request.params[0]), + nonceManager: nonceManager, + account: acc, + }); + console.log(Object.keys(prepared)); + const signed = await acc.signTransaction(prepared); + const hash = await sendRawTransaction(client, { + serializedTransaction: signed, + }); + return hash; + } + if (request.method === "eth_accounts") { + return [acc.address]; + } + throw new Error(`Unknown method: ${request.method}`); + }, + }; +}; + +const privateKey = async (protocolString: string) => { + const [proto, privateKeyString] = protocolString.split(":"); + if (!privateKeyString.startsWith("0x")) { + throw new Error(`Private key must start with 0x, got: ${privateKeyString}`); + } + const privateKey = privateKeyString; + return { + type: "wallet", + signer: signer(privateKey as Hex), + } as const; +}; + +const runDeploy = async ( + scripts: string[], + tags: string[], + deployments: string, + args?: RockethArguments, +) => { + return executeDeployScripts( + resolveConfig({ + logLevel: 1, + network: { + nodeUrl: rpcUrl, + name: "sepoliaFresh", + fork: false, + scripts, + tags, + publicInfo: { + name: "sepolia", + nativeCurrency: chain.nativeCurrency, + rpcUrls: { default: { http: [...chain.rpcUrls.default.http] } }, + }, + provider: http(rpcUrl)({ chain }), + }, + askBeforeProceeding: false, + saveDeployments: true, + accounts: { + deployer: process.env.DEPLOYER_KEY as Hex, + owner: process.env.DEPLOYER_KEY as Hex, + }, + deployments, + signerProtocols: { + privateKey, + }, + }), + args, + ); +}; + +const deployUnrug = async () => { + const walletClient = createWalletClient({ + chain, + transport: http(rpcUrl), + account: privateKeyToAccount(process.env.DEPLOYER_KEY as Hex, { + nonceManager: nonceManager, + }), + }); + + console.log("Deploying GatewayVM..."); + const GatewayVM = await deployArtifact(walletClient, { + file: urgArtifact("GatewayVM"), + }); + console.log("GatewayVM deployed to", GatewayVM); + + console.log("Deploying EthHookVerifier..."); + const ethHookVerifier = await deployArtifact(walletClient, { + file: urgArtifact("EthVerifierHooks"), + }); + console.log("EthHookVerifier deployed to", ethHookVerifier); + + console.log("Deploying SelfVerifier..."); + const selfVerifier = await deployArtifact(walletClient, { + file: urgArtifact("SelfVerifier"), + args: [ + ["https://gateways-worker-sepolia.ens-cf.workers.dev"], + 60, + ethHookVerifier, + ], + libs: { GatewayVM }, + }); + console.log("SelfVerifier deployed to", selfVerifier); + return selfVerifier; +}; + +const GatewayVM = "0x31FF4C757ea3C0517bF5148058C611e884B14Ca2"; +const EthHookVerifier = "0x088Bd87C93C06EEB184C82ff987Faf7Aa28aE6f7"; +const selfVerifier = "0x8eA3957bF696bB81523E6b9Cdf3fac8A57C14f0e"; // await deployUnrug(); + +const runV1L1Deploy = async () => { + console.log("Running V1 L1 deploy"); + + const v1L1Deploy = await runDeploy( + ["lib/ens-contracts/deploy"], + ["use_root", "allow_unsafe", "legacy"], + "deployments/l1/v1", + ); +}; + +// // copy BatchGatewayProvider since it's reused by v2 deploy scripts +// await $`mkdir -p deployments/l1/sepoliaFresh`; +// await $`cp deployments/l1/v1/sepoliaFresh/.chain deployments/l1/v1/sepoliaFresh/BatchGatewayProvider.json deployments/l1/sepoliaFresh`; + +const runL2Deploy = async () => { + console.log("Running L2 deploy"); + + const l2Deploy = await runDeploy( + ["deploy/l2", "deploy/shared"], + ["l2"], + "deployments/l2", + ); +}; + +const runL1Deploy = async () => { + console.log("Running L1 deploy"); + const l2Deploy = loadDeployments("deployments/l2", "sepoliaFresh", true); + + // local tag for URv2 on UURP + const l1Deploy = await runDeploy( + ["deploy/l1", "deploy/shared"], + ["l1", "local"], + "deployments/l1", + { + l2Deploy, + verifierAddress: selfVerifier, + }, + ); +}; + +await runL1Deploy(); diff --git a/contracts/script/print-contracts.ts b/contracts/script/print-contracts.ts new file mode 100644 index 00000000..4b104fd1 --- /dev/null +++ b/contracts/script/print-contracts.ts @@ -0,0 +1,22 @@ +import { loadDeployments } from "rocketh"; + +const chainName = "sepoliaFresh"; +const deployments = { + v1: loadDeployments("deployments/l1/v1", chainName, false).deployments, + l1: loadDeployments("deployments/l1", chainName, false).deployments, + l2: loadDeployments("deployments/l2", chainName, false).deployments, +} as const; + +function Contract(name: string, address: string) { + this.name = name; + this.address = address; +} + +for (const chain of Object.keys(deployments)) { + console.log(chain); + const ctrcts = []; + for (const contract of Object.keys(deployments[chain])) { + ctrcts.push(new Contract(contract, deployments[chain][contract].address)); + } + console.table(ctrcts, ["name", "address"]); +} diff --git a/contracts/script/set-addr.ts b/contracts/script/set-addr.ts new file mode 100644 index 00000000..bda7a3fc --- /dev/null +++ b/contracts/script/set-addr.ts @@ -0,0 +1,56 @@ +import { loadDeployments, type Deployment } from "rocketh"; +import { + createClient, + getContract, + http, + zeroHash, + type Abi, + type Hex, +} from "viem"; +import { sepolia } from "viem/chains"; + +import type { artifacts } from "@rocketh"; +import { privateKeyToAccount } from "viem/accounts"; +import { waitForTransactionReceipt } from "viem/actions"; + +const chainName = "sepoliaFresh"; +const deployments = { + v1: loadDeployments("deployments/l1/v1", chainName, false).deployments, + l1: loadDeployments("deployments/l1", chainName, false).deployments, + l2: loadDeployments("deployments/l2", chainName, false).deployments, +} as const; +const getDeployment = ( + from: keyof typeof deployments, + path: string, +): Deployment => { + const deployment = + deployments[from][path as keyof (typeof deployments)[typeof from]]; + console.log(Object.keys(deployments[from])); + if (!deployment) throw new Error(`Deployment ${path} not found`); + return deployment as Deployment; +}; + +const client = createClient({ + chain: sepolia, + transport: http(process.env.SEPOLIA_RPC_URL as string), + account: privateKeyToAccount(process.env.NAME_OWNER_KEY as Hex), +}); + +const dedicatedResolverImplDeployment = getDeployment< + typeof artifacts.DedicatedResolver.abi +>("l2", "DedicatedResolverImpl"); +const dedicatedResolver = getContract({ + abi: dedicatedResolverImplDeployment.abi, + address: "0xd7695e331224103d0dce7f443610c816f0e91ad9", + client, +}); + +const tx = await dedicatedResolver.write.setAddr([60n, client.account.address]); + +const receipt = await waitForTransactionReceipt(client, { hash: tx }); +if (receipt.status !== "success") throw new Error("Failed to set addr"); + +console.log("Address set"); + +const addr = await dedicatedResolver.read.addr([zeroHash]); +console.log("addr", addr); diff --git a/contracts/script/try-resolve.ts b/contracts/script/try-resolve.ts new file mode 100644 index 00000000..faa3a03c --- /dev/null +++ b/contracts/script/try-resolve.ts @@ -0,0 +1,71 @@ +import { loadDeployments, type Deployment } from "rocketh"; +import { + bytesToHex, + createClient, + decodeFunctionResult, + encodeFunctionData, + getContract, + http, + parseAbi, + zeroHash, + type Abi, + type Hex, +} from "viem"; +import { sepolia } from "viem/chains"; + +import type { artifacts } from "@rocketh"; +import { privateKeyToAccount } from "viem/accounts"; +import { packetToBytes } from "../test/utils/utils.ts"; + +const chainName = "sepoliaFresh"; +const deployments = { + v1: loadDeployments("deployments/l1/v1", chainName, false).deployments, + l1: loadDeployments("deployments/l1", chainName, false).deployments, + l2: loadDeployments("deployments/l2", chainName, false).deployments, +} as const; +const getDeployment = ( + from: keyof typeof deployments, + path: string, +): Deployment => { + const deployment = + deployments[from][path as keyof (typeof deployments)[typeof from]]; + console.log(Object.keys(deployments[from])); + if (!deployment) throw new Error(`Deployment ${path} not found`); + return deployment as Deployment; +}; + +const client = createClient({ + chain: sepolia, + transport: http(process.env.SEPOLIA_RPC_URL as string), + account: privateKeyToAccount(process.env.DEPLOYER_KEY as Hex), +}); + +const universalResolverDeployment = getDeployment< + typeof artifacts.UniversalResolver.abi +>("l1", "UniversalResolverV2"); +const universalResolver = getContract({ + abi: universalResolverDeployment.abi, + address: universalResolverDeployment.address, + client, +}); + +const result = await universalResolver.read.resolve([ + bytesToHex(packetToBytes("first-test.eth")), + encodeFunctionData({ + abi: parseAbi([ + "function addr(bytes32, uint256 coinType) external view returns (bytes)", + ]), + functionName: "addr", + args: [zeroHash, 60n], + }), +]); +console.log("result", result); +const decoded = decodeFunctionResult({ + abi: parseAbi([ + "function addr(bytes32, uint256 coinType) external view returns (bytes)", + ]), + functionName: "addr", + data: result[0], +}); + +console.log("result", decoded); diff --git a/contracts/script/try.ts b/contracts/script/try.ts new file mode 100644 index 00000000..daa4da9f --- /dev/null +++ b/contracts/script/try.ts @@ -0,0 +1,106 @@ +import { loadDeployments, type Deployment } from "rocketh"; +import { + createClient, + encodeFunctionData, + getContract, + http, + parseEventLogs, + zeroAddress, + type Abi, + type Hex, +} from "viem"; +import { sepolia } from "viem/chains"; + +import type { artifacts } from "@rocketh"; +import { privateKeyToAccount } from "viem/accounts"; +import { waitForTransactionReceipt } from "viem/actions"; + +const chainName = "sepoliaFresh"; +const deployments = { + v1: loadDeployments("deployments/l1/v1", chainName, false).deployments, + l1: loadDeployments("deployments/l1", chainName, false).deployments, + l2: loadDeployments("deployments/l2", chainName, false).deployments, +} as const; +const getDeployment = ( + from: keyof typeof deployments, + path: string, +): Deployment => { + const deployment = + deployments[from][path as keyof (typeof deployments)[typeof from]]; + console.log(Object.keys(deployments[from])); + if (!deployment) throw new Error(`Deployment ${path} not found`); + return deployment as Deployment; +}; + +const client = createClient({ + chain: sepolia, + transport: http(process.env.SEPOLIA_RPC_URL as string), + account: privateKeyToAccount(process.env.DEPLOYER_KEY as Hex), +}); + +const ethRegistryDeployment = getDeployment< + typeof artifacts.PermissionedRegistry.abi +>("l2", "ETHRegistry"); +const ethRegistry = getContract({ + abi: ethRegistryDeployment.abi, + address: ethRegistryDeployment.address, + client, +}); + +const dedicatedResolverImplDeployment = getDeployment< + typeof artifacts.DedicatedResolver.abi +>("l2", "DedicatedResolverImpl"); + +const verifiableFactoryDeployment = getDeployment< + typeof artifacts.VerifiableFactory.abi +>("l2", "VerifiableFactory"); +const verifiableFactory = getContract({ + abi: verifiableFactoryDeployment.abi, + address: verifiableFactoryDeployment.address, + client, +}); + +const label = "first-test"; +const nameOwner = "0x69420f05A11f617B4B74fFe2E04B2D300dFA556F"; + +const deployProxyHash = await verifiableFactory.write.deployProxy([ + dedicatedResolverImplDeployment.address, + 1n, + encodeFunctionData({ + abi: dedicatedResolverImplDeployment.abi, + functionName: "initialize", + args: [nameOwner], + }), +]); + +const deployProxyReceipt = await waitForTransactionReceipt(client, { + hash: deployProxyHash, +}); +if (deployProxyReceipt.status !== "success") + throw new Error("Proxy deployment failed"); + +const [log] = parseEventLogs({ + abi: verifiableFactoryDeployment.abi, + eventName: "ProxyDeployed", + logs: deployProxyReceipt.logs, +}); +const dedicatedResolverAddress = log.args.proxyAddress; + +const tx = await ethRegistry.write.register([ + "first-test", + nameOwner, + zeroAddress, + dedicatedResolverAddress, + 0n, + BigInt(Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365), +]); + +const registerReceipt = await waitForTransactionReceipt(client, { hash: tx }); +if (registerReceipt.status !== "success") + throw new Error("Name registration failed"); + +const [tokenId] = await ethRegistry.read.getNameData([label]); +console.log("tokenId", tokenId); + +const owner = await ethRegistry.read.ownerOf([tokenId]); +console.log("owner", owner);