🌳
pt0/serverF/ethersF/ensRegF.mts
1import { namehash, Contract, keccak256, concat, toUtf8Bytes, randomBytes, ZeroAddress } from 'ethers'
7const ethRegistrarControllerAddr = '0x59E16fcCd424Cc24e280Be16E11Bcd56fb0CE547'
8const baseRegistrarAddr = '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85'
9const nameWrapperAddr = '0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401'
10const ensRegistryAddr = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'
11const publicResolverAddr = '0xF29100983E058B709F3D539b0c765937B804AC15'
13const controllerAbi = [
14 'function available(string memory name) public view returns (bool)',
15 'function rentPrice(string memory name, uint256 duration) public view returns (uint256)',
16 'function makeCommitment(string memory name, address owner, uint256 duration, bytes32 secret, address resolver, bytes[] calldata data, bool reverseRecord, uint16 ownerControlledFuses) public pure returns (bytes32)',
17 'function commit(bytes32 commitment) public',
18 'function register(string calldata name, address owner, uint256 duration, bytes32 secret, address resolver, bytes[] calldata data, bool reverseRecord, uint16 ownerControlledFuses) external payable',
19 'function renew(string calldata name, uint256 duration) external payable',
22const baseRegistrarAbi = [
23 'function ownerOf(uint256 tokenId) public view returns (address)',
24 'function controllers(address) public view returns (bool)',
27const nameWrapperAbi = [
28 'function ownerOf(uint256 id) public view returns (address)',
29 'function setResolver(bytes32 node, address resolver) external',
32const ensRegistryAbi = [
33 'function resolver(bytes32 node) external view returns (address)',
34 'function setResolver(bytes32 node, address resolver) external',
37const publicResolverAbi = [
38 'function addr(bytes32 node) external view returns (address)',
39 'function setAddr(bytes32 node, address a) external',
42const stripEthSuffix = (ensName: string) => ensName.replace(/\.eth$/, '')
44const labelToTokenId = (label: string) => BigInt(keccak256(toUtf8Bytes(label)))
46const assertControllerApproved = async () => {
47 const provider = getAlchemyProv('homestead')
48 const baseRegistrar = new Contract(baseRegistrarAddr, baseRegistrarAbi, provider)
49 const isApproved = await baseRegistrar.controllers(ethRegistrarControllerAddr)
50 assertTruthy(isApproved, {ethRegistrarControllerAddr})
53const isNameWrapped = async ({ensName}: {ensName: string}) => {
54 const provider = getAlchemyProv('homestead')
55 const baseRegistrar = new Contract(baseRegistrarAddr, baseRegistrarAbi, provider)
56 const label = stripEthSuffix(ensName)
57 const tokenId = labelToTokenId(label)
58 try {
59 const baseOwner = await baseRegistrar.ownerOf(tokenId)
60 return baseOwner.toLowerCase() === nameWrapperAddr.toLowerCase()
61 } catch {
62 return false
63 }
66export const ensEnsureResolver = async ({ensName, wallet}: {ensName: string, wallet: any}) => {
67 const provider = getAlchemyProv('homestead')
68 const connectedWallet = wallet.connect(provider)
69 const node = namehash(ensName)
70 const registry = new Contract(ensRegistryAddr, ensRegistryAbi, provider)
71 const currentResolver = await registry.resolver(node)
73 if (currentResolver.toLowerCase() === publicResolverAddr.toLowerCase()) {
74 return {updated: false, resolver: currentResolver}
75 }
77 console.log(`Updating resolver for ${ensName} from ${currentResolver} to ${publicResolverAddr}...`)
78 const isWrapped = await isNameWrapped({ensName})
80 if (isWrapped) {
81 const nameWrapper = new Contract(nameWrapperAddr, nameWrapperAbi, connectedWallet)
82 const tx = await nameWrapper.setResolver(node, publicResolverAddr)
83 console.log(`setResolver tx: ${tx.hash}`)
84 await tx.wait()
85 } else {
86 const registryWritable = new Contract(ensRegistryAddr, ensRegistryAbi, connectedWallet)
87 const tx = await registryWritable.setResolver(node, publicResolverAddr)
88 console.log(`setResolver tx: ${tx.hash}`)
89 await tx.wait()
90 }
92 console.log(`Resolver updated for ${ensName}`)
93 return {updated: true, oldResolver: currentResolver, newResolver: publicResolverAddr}
96export const ensEnsureAddrRecord = async ({ensName, wallet}: {ensName: string, wallet: any}) => {
97 const provider = getAlchemyProv('homestead')
98 const connectedWallet = wallet.connect(provider)
99 const node = namehash(ensName)
100 const resolver = new Contract(publicResolverAddr, publicResolverAbi, provider)
102 const currentAddr = await resolver.addr(node)
103 if (currentAddr.toLowerCase() === wallet.address.toLowerCase()) {
104 return {updated: false, addr: currentAddr}
105 }
107 console.log(`Setting addr record for ${ensName} from ${currentAddr} to ${wallet.address}...`)
108 const resolverWritable = new Contract(publicResolverAddr, publicResolverAbi, connectedWallet)
109 const tx = await resolverWritable.setAddr(node, wallet.address)
110 console.log(`setAddr tx: ${tx.hash}`)
111 await tx.wait()
113 console.log(`Addr record updated for ${ensName}`)
114 return {updated: true, oldAddr: currentAddr, newAddr: wallet.address}
117export const ensGetOwner = async ({ensName}: {ensName: string}) => {
118 const provider = getAlchemyProv('homestead')
119 const baseRegistrar = new Contract(baseRegistrarAddr, baseRegistrarAbi, provider)
120 const nameWrapper = new Contract(nameWrapperAddr, nameWrapperAbi, provider)
121 const label = stripEthSuffix(ensName)
122 const tokenId = labelToTokenId(label)
123 try {
124 const baseOwner = await baseRegistrar.ownerOf(tokenId)
125 if (baseOwner.toLowerCase() === nameWrapperAddr.toLowerCase()) {
126 const wrappedOwner = await nameWrapper.ownerOf(namehash(ensName))
127 return wrappedOwner as string
128 }
129 return baseOwner as string
130 } catch {
131 return null
132 }
135export const ensCommitAndRegister = async ({ensName, wallet, durationYears = 1}: {ensName: string, wallet: any, durationYears?: number}) => {
136 await assertControllerApproved()
137 const provider = getAlchemyProv('homestead')
138 const connectedWallet = wallet.connect(provider)
139 const controller = new Contract(ethRegistrarControllerAddr, controllerAbi, connectedWallet)
140 const label = stripEthSuffix(ensName)
141 const durationSec = durationYears * 365 * 24 * 60 * 60
143 const currentOwner = await ensGetOwner({ensName})
144 if (currentOwner?.toLowerCase() === wallet.address.toLowerCase()) {
145 console.log(`Already own ${ensName}, ensuring resolver and records...`)
146 const resolverResult = await ensEnsureResolver({ensName, wallet})
147 const addrResult = await ensEnsureAddrRecord({ensName, wallet})
148 return {skipped: true, owner: currentOwner, resolver: resolverResult, addr: addrResult}
149 }
151 const isAvailable = await controller.available(label)
152 if (!isAvailable) {
153 const owner = currentOwner || 'unknown'
154 throw new Error(`${ensName} is not available (owned by ${owner})`)
155 }
157 const secret = randomBytes(32)
158 const secretHex = '0x' + Buffer.from(secret).toString('hex')
160 betLog({label, durationYears, durationSec, walletAddress: wallet.address})
162 const commitment = await controller.makeCommitment(label, wallet.address, durationSec, secretHex, publicResolverAddr, [], false, 0)
163 betLog({commitment})
165 console.log(`Committing ${ensName}...`)
166 const commitTx = await controller.commit(commitment)
167 console.log(`Commit tx: ${commitTx.hash}`)
168 await commitTx.wait()
169 console.log('Commit confirmed, waiting 60s for minCommitmentAge...')
171 await sleep(61000)
173 const rentPrice = await controller.rentPrice(label, durationSec)
174 const rentPriceWithBuffer = (rentPrice * 110n) / 100n
175 betLog({rentPriceWei: rentPrice.toString(), rentPriceWithBuffer: rentPriceWithBuffer.toString()})
177 console.log(`Registering ${ensName}...`)
178 const registerTx = await controller.register(label, wallet.address, durationSec, secretHex, publicResolverAddr, [], false, 0, {value: rentPriceWithBuffer})
179 console.log(`Register tx: ${registerTx.hash}`)
180 const receipt = await registerTx.wait()
181 console.log(`Registered ${ensName} in block ${receipt.blockNumber}`)
183 return {txHash: registerTx.hash, blockNumber: receipt.blockNumber}