diff --git a/README.md b/README.md index 963dcd7..e032d4c 100644 --- a/README.md +++ b/README.md @@ -161,3 +161,9 @@ These solutions were combined with ideas from [Hats Protocol](https://www.hatspr ```bash yarn contract getContractAddress --contract Lock --network sepolia ``` + + - #### **resiger new subdomain to `toban.eth`** + + ```bash + yarn contract registerSubdomain --label --network sepolia + ``` diff --git a/pkgs/contract/hardhat.config.ts b/pkgs/contract/hardhat.config.ts index c8c46b7..57784b2 100644 --- a/pkgs/contract/hardhat.config.ts +++ b/pkgs/contract/hardhat.config.ts @@ -19,7 +19,7 @@ const { // タスクファイルを読み込むための設定 const SKIP_LOAD = process.env.SKIP_LOAD === "true"; if (!SKIP_LOAD) { - const taskPaths = ["", "utils"]; + const taskPaths = ["", "utils", "ens"]; taskPaths.forEach((folder) => { const tasksPath = path.join(__dirname, "tasks", folder); fs.readdirSync(tasksPath) diff --git a/pkgs/contract/helpers/ens/abi.ts b/pkgs/contract/helpers/ens/abi.ts new file mode 100644 index 0000000..c6d73f8 --- /dev/null +++ b/pkgs/contract/helpers/ens/abi.ts @@ -0,0 +1,731 @@ +export const NAME_WRAPPER_ABI = [ + { + inputs: [ + { internalType: "contract ENS", name: "_ens", type: "address" }, + { + internalType: "contract IBaseRegistrar", + name: "_registrar", + type: "address", + }, + { + internalType: "contract IMetadataService", + name: "_metadataService", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "CannotUpgrade", type: "error" }, + { inputs: [], name: "IncompatibleParent", type: "error" }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "IncorrectTargetOwner", + type: "error", + }, + { inputs: [], name: "IncorrectTokenType", type: "error" }, + { + inputs: [ + { internalType: "bytes32", name: "labelHash", type: "bytes32" }, + { internalType: "bytes32", name: "expectedLabelhash", type: "bytes32" }, + ], + name: "LabelMismatch", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "label", type: "string" }], + name: "LabelTooLong", + type: "error", + }, + { inputs: [], name: "LabelTooShort", type: "error" }, + { inputs: [], name: "NameIsNotWrapped", type: "error" }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "OperationProhibited", + type: "error", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "addr", type: "address" }, + ], + name: "Unauthorised", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "approved", + type: "address", + }, + { + indexed: true, + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { indexed: false, internalType: "bool", name: "approved", type: "bool" }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "controller", + type: "address", + }, + { indexed: false, internalType: "bool", name: "active", type: "bool" }, + ], + name: "ControllerChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: false, + internalType: "uint64", + name: "expiry", + type: "uint64", + }, + ], + name: "ExpiryExtended", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "uint32", name: "fuses", type: "uint32" }, + ], + name: "FusesSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: false, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "NameUnwrapped", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "bytes", name: "name", type: "bytes" }, + { + indexed: false, + internalType: "address", + name: "owner", + type: "address", + }, + { indexed: false, internalType: "uint32", name: "fuses", type: "uint32" }, + { + indexed: false, + internalType: "uint64", + name: "expiry", + type: "uint64", + }, + ], + name: "NameWrapped", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { indexed: true, internalType: "address", name: "from", type: "address" }, + { indexed: true, internalType: "address", name: "to", type: "address" }, + { + indexed: false, + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + indexed: false, + internalType: "uint256[]", + name: "values", + type: "uint256[]", + }, + ], + name: "TransferBatch", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { indexed: true, internalType: "address", name: "from", type: "address" }, + { indexed: true, internalType: "address", name: "to", type: "address" }, + { indexed: false, internalType: "uint256", name: "id", type: "uint256" }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "TransferSingle", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "string", name: "value", type: "string" }, + { indexed: true, internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "URI", + type: "event", + }, + { + inputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "_tokens", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint32", name: "fuseMask", type: "uint32" }, + ], + name: "allFusesBurned", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address[]", name: "accounts", type: "address[]" }, + { internalType: "uint256[]", name: "ids", type: "uint256[]" }, + ], + name: "balanceOfBatch", + outputs: [{ internalType: "uint256[]", name: "", type: "uint256[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "addr", type: "address" }, + ], + name: "canExtendSubnames", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "addr", type: "address" }, + ], + name: "canModifyName", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "controllers", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "ens", + outputs: [{ internalType: "contract ENS", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "parentNode", type: "bytes32" }, + { internalType: "bytes32", name: "labelhash", type: "bytes32" }, + { internalType: "uint64", name: "expiry", type: "uint64" }, + ], + name: "extendExpiry", + outputs: [{ internalType: "uint64", name: "", type: "uint64" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "operator", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "getData", + outputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint32", name: "fuses", type: "uint32" }, + { internalType: "uint64", name: "expiry", type: "uint64" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "address", name: "operator", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "parentNode", type: "bytes32" }, + { internalType: "bytes32", name: "labelhash", type: "bytes32" }, + ], + name: "isWrapped", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "isWrapped", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "metadataService", + outputs: [ + { internalType: "contract IMetadataService", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + name: "names", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "tokenId", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "onERC721Received", + outputs: [{ internalType: "bytes4", name: "", type: "bytes4" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "owner", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_token", type: "address" }, + { internalType: "address", name: "_to", type: "address" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "recoverFunds", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "label", type: "string" }, + { internalType: "address", name: "wrappedOwner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "uint16", name: "ownerControlledFuses", type: "uint16" }, + ], + name: "registerAndWrapETH2LD", + outputs: [ + { internalType: "uint256", name: "registrarExpiry", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "registrar", + outputs: [ + { internalType: "contract IBaseRegistrar", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "tokenId", type: "uint256" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "renew", + outputs: [{ internalType: "uint256", name: "expires", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256[]", name: "ids", type: "uint256[]" }, + { internalType: "uint256[]", name: "amounts", type: "uint256[]" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "safeBatchTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "parentNode", type: "bytes32" }, + { internalType: "bytes32", name: "labelhash", type: "bytes32" }, + { internalType: "uint32", name: "fuses", type: "uint32" }, + { internalType: "uint64", name: "expiry", type: "uint64" }, + ], + name: "setChildFuses", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "controller", type: "address" }, + { internalType: "bool", name: "active", type: "bool" }, + ], + name: "setController", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint16", name: "ownerControlledFuses", type: "uint16" }, + ], + name: "setFuses", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IMetadataService", + name: "_metadataService", + type: "address", + }, + ], + name: "setMetadataService", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "uint64", name: "ttl", type: "uint64" }, + ], + name: "setRecord", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "resolver", type: "address" }, + ], + name: "setResolver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "parentNode", type: "bytes32" }, + { internalType: "string", name: "label", type: "string" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint32", name: "fuses", type: "uint32" }, + { internalType: "uint64", name: "expiry", type: "uint64" }, + ], + name: "setSubnodeOwner", + outputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "parentNode", type: "bytes32" }, + { internalType: "string", name: "label", type: "string" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "uint64", name: "ttl", type: "uint64" }, + { internalType: "uint32", name: "fuses", type: "uint32" }, + { internalType: "uint64", name: "expiry", type: "uint64" }, + ], + name: "setSubnodeRecord", + outputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint64", name: "ttl", type: "uint64" }, + ], + name: "setTTL", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract INameWrapperUpgrade", + name: "_upgradeAddress", + type: "address", + }, + ], + name: "setUpgradeContract", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "parentNode", type: "bytes32" }, + { internalType: "bytes32", name: "labelhash", type: "bytes32" }, + { internalType: "address", name: "controller", type: "address" }, + ], + name: "unwrap", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "labelhash", type: "bytes32" }, + { internalType: "address", name: "registrant", type: "address" }, + { internalType: "address", name: "controller", type: "address" }, + ], + name: "unwrapETH2LD", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "name", type: "bytes" }, + { internalType: "bytes", name: "extraData", type: "bytes" }, + ], + name: "upgrade", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "upgradeContract", + outputs: [ + { + internalType: "contract INameWrapperUpgrade", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "uri", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "name", type: "bytes" }, + { internalType: "address", name: "wrappedOwner", type: "address" }, + { internalType: "address", name: "resolver", type: "address" }, + ], + name: "wrap", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "label", type: "string" }, + { internalType: "address", name: "wrappedOwner", type: "address" }, + { internalType: "uint16", name: "ownerControlledFuses", type: "uint16" }, + { internalType: "address", name: "resolver", type: "address" }, + ], + name: "wrapETH2LD", + outputs: [{ internalType: "uint64", name: "expiry", type: "uint64" }], + stateMutability: "nonpayable", + type: "function", + }, +]; diff --git a/pkgs/contract/helpers/ens/constants.ts b/pkgs/contract/helpers/ens/constants.ts new file mode 100644 index 0000000..4fbe924 --- /dev/null +++ b/pkgs/contract/helpers/ens/constants.ts @@ -0,0 +1,9 @@ +// NAME Wrapper Contract Address(sepolia) +export const NAME_WRAPPER_CONTRACT_ADDRESS = + "0x0635513f179D50A207757E05759CbD106d7dFcE8"; +// contract.toban.ethのparent node(sepolia) +export const CONTRACT_TOBAN_PARENT_NODE = + "0x8f16dcf0ba3c4c5b2bb9786c84c45925294ff9e18b65e97dda3521708b071a33"; +// public resolever contract address(sepolia) +export const RESOLEVER_CONTRACT_ADDRESS = + "0x8FADE66B79cC9f707aB26799354482EB93a5B7dD"; diff --git a/pkgs/contract/helpers/ens/function.ts b/pkgs/contract/helpers/ens/function.ts new file mode 100644 index 0000000..273a783 --- /dev/null +++ b/pkgs/contract/helpers/ens/function.ts @@ -0,0 +1,46 @@ +import * as dotenv from "dotenv"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { privateKeyToAccount } from "viem/accounts"; +import { NAME_WRAPPER_ABI } from "./abi"; +import { + CONTRACT_TOBAN_PARENT_NODE, + NAME_WRAPPER_CONTRACT_ADDRESS, + RESOLEVER_CONTRACT_ADDRESS, +} from "./constants"; + +dotenv.config(); + +const { PRIVATE_KEY } = process.env; + +// create Signer from Private Key +const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`); + +/** + * register subdomain method + */ +export const registerSubdomain = async ( + hre: HardhatRuntimeEnvironment, + label: string +) => { + const [bobWalletClient] = await hre.viem.getWalletClients(); + // register subdomain methodを呼び出す。 + const txHash = await bobWalletClient.writeContract({ + address: NAME_WRAPPER_CONTRACT_ADDRESS, + abi: NAME_WRAPPER_ABI, + functionName: "setSubnodeRecord", + args: [ + CONTRACT_TOBAN_PARENT_NODE, + label, + account.address, + RESOLEVER_CONTRACT_ADDRESS, + 0, + 0, + 0, + ], + account, + }); + + console.log(`registerSubdomain tx: ${txHash}`); + + return txHash; +}; diff --git a/pkgs/contract/package.json b/pkgs/contract/package.json index 8824750..5ebe701 100644 --- a/pkgs/contract/package.json +++ b/pkgs/contract/package.json @@ -12,7 +12,8 @@ "local": "npx hardhat node", "getBalance": "npx hardhat getBalance", "getChainInfo": "npx hardhat getChainInfo", - "getContractAddress": "npx hardhat getContractAddress" + "getContractAddress": "npx hardhat getContractAddress", + "registerSubdomain": "npx hardhat registerSubdomain" }, "devDependencies": { "@nomicfoundation/hardhat-ignition": "^0.15.5", diff --git a/pkgs/contract/tasks/ens/registerSubdomain.ts b/pkgs/contract/tasks/ens/registerSubdomain.ts new file mode 100644 index 0000000..0018456 --- /dev/null +++ b/pkgs/contract/tasks/ens/registerSubdomain.ts @@ -0,0 +1,21 @@ +import { task } from "hardhat/config"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { registerSubdomain } from "../../helpers/ens/function"; + +/** + * 【Task】register subdomain + */ +task("registerSubdomain", "register subdomain") + .addParam("label", "label for subdomain") + .setAction(async (taskArgs: any, hre: HardhatRuntimeEnvironment) => { + console.log( + "################################### [START] ###################################" + ); + + // call registerSubdomain method + await registerSubdomain(hre, taskArgs.label); + + console.log( + "################################### [END] ###################################" + ); + });