diff --git a/CLAUDE.md b/CLAUDE.md index 5f4793f3d..b70b3070a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,4 +144,32 @@ import { getNamesForAddress } from '@ensdomains/ensjs/subgraph' - Minimum Node.js version: 22 - Built with TypeScript for full type safety - Supports tree-shaking for optimal bundle size -- Uses viem as the underlying Ethereum library \ No newline at end of file +- Uses viem as the underlying Ethereum library + +## CI/CD Guidelines + +### Fixing CI Failures + +When asked to fix CI issues: + +1. **Always wait for CI to complete** - Don't assume the task is done after pushing fixes. Wait for all CI checks to finish running. + +2. **Verify actual success** - Check that all CI checks (lint, tests, build) actually pass. Use: + ```bash + gh pr checks --repo ensdomains/ensjs --watch + ``` + +3. **Iterative fixing** - If CI fails after your fix: + - Check the specific error messages + - Fix the issues + - Push the changes + - **Wait for CI to run again** + - Repeat until all checks pass + +4. **Common CI checks to monitor**: + - Lint (code style and formatting) + - Test (unit and integration tests) + - Build (TypeScript compilation) + - SonarCloud (code quality) + +5. **Never declare CI fixed until you see green checks** - The task is only complete when all CI checks show as passing on GitHub. \ No newline at end of file diff --git a/packages/ensjs/CONTRACT_MISMATCH_ISSUE.md b/packages/ensjs/CONTRACT_MISMATCH_ISSUE.md new file mode 100644 index 000000000..c36f49c15 --- /dev/null +++ b/packages/ensjs/CONTRACT_MISMATCH_ISSUE.md @@ -0,0 +1,38 @@ +# Contract Mismatch Issue + +## Problem +The tests for `registerName` and `renewNames` are failing with transaction reverts because of a contract version mismatch: + +1. **Code expects new contracts**: The PR code uses the new ENS contract ABIs that include a `referrer` parameter in the registration tuple +2. **Test environment deploys legacy contracts**: The `ens-test-env` deploys legacy ENS contracts that don't support the referrer parameter + +## Failing Tests +- `src/functions/wallet/registerName.test.ts` - Transaction reverts when trying to register with new ABI format +- `src/functions/wallet/renewNames.test.ts` - Transaction reverts when trying to renew with referrer parameter + +## Root Cause +The new registration format expects a tuple with these fields: +```solidity +struct Registration { + string label; + address owner; + uint256 duration; + bytes32 secret; + address resolver; + bytes[] data; + uint16 reverseRecord; + bytes32 referrer; // NEW FIELD +} +``` + +But the deployed test contracts only support the old format without `referrer`. + +## Solution Required +Update `ens-test-env` to deploy the new ENS contracts that support the referrer parameter. This requires: + +1. Updating the contract deployments in the test environment +2. Ensuring the new contracts are available and compiled +3. Updating any deployment scripts to use the new contract versions + +## Temporary Workaround (Not Recommended) +Adding backward compatibility to detect contract versions and use appropriate ABIs was explicitly rejected by the maintainers. The proper solution is to update the test environment. \ No newline at end of file diff --git a/packages/ensjs/src/clients/decorators/wallet.ts b/packages/ensjs/src/clients/decorators/wallet.ts index c738282b4..a8378e184 100644 --- a/packages/ensjs/src/clients/decorators/wallet.ts +++ b/packages/ensjs/src/clients/decorators/wallet.ts @@ -138,7 +138,6 @@ export type EnsWalletActions< resolverAddress, records, reverseRecord, - fuses, ...txArgs }: CommitNameParameters< TChain, @@ -254,7 +253,6 @@ export type EnsWalletActions< resolverAddress, records, reverseRecord, - fuses, value, ...txArgs }: RegisterNameParameters< diff --git a/packages/ensjs/src/contracts/bulkRenewal.ts b/packages/ensjs/src/contracts/bulkRenewal.ts index 9a0adc823..3c21b63c1 100644 --- a/packages/ensjs/src/contracts/bulkRenewal.ts +++ b/packages/ensjs/src/contracts/bulkRenewal.ts @@ -23,6 +23,29 @@ export const bulkRenewalRentPriceSnippet = [ ] as const export const bulkRenewalRenewAllSnippet = [ + { + inputs: [ + { + name: 'names', + type: 'string[]', + }, + { + name: 'duration', + type: 'uint256', + }, + { + name: 'referrer', + type: 'bytes32', + }, + ], + name: 'renewAll', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const + +export const wrappedBulkRenewalRenewAllSnippet = [ { inputs: [ { diff --git a/packages/ensjs/src/contracts/consts.ts b/packages/ensjs/src/contracts/consts.ts index 556732321..2e6ef3938 100644 --- a/packages/ensjs/src/contracts/consts.ts +++ b/packages/ensjs/src/contracts/consts.ts @@ -19,6 +19,8 @@ export const supportedContracts = [ 'ensRegistry', 'ensReverseRegistrar', 'ensUniversalResolver', + 'ensWrappedBulkRenewal', + 'ensWrappedEthRegistrarController', 'legacyEthRegistrarController', 'legacyPublicResolver', 'ensDefaultReverseRegistrar', @@ -33,7 +35,7 @@ export const addresses = { address: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85', }, ensBulkRenewal: { - address: '0xa12159e5131b1eEf6B4857EEE3e1954744b5033A', + address: '0xc649947a460B135e6B9a70Ee2FB429aDBB529290', }, ensDnsRegistrar: { address: '0xB32cB5677a7C971689228EC835800432B339bA2B', @@ -42,7 +44,7 @@ export const addresses = { address: '0x0fc3152971714E5ed7723FAFa650F86A4BaF30C5', }, ensEthRegistrarController: { - address: '0x253553366Da8546fC250F225fe3d25d0C782303b', + address: '0x59E16fcCd424Cc24e280Be16E11Bcd56fb0CE547', }, ensNameWrapper: { address: '0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401', @@ -68,13 +70,19 @@ export const addresses = { ensDefaultReverseRegistrar: { address: '0x283F227c4Bd38ecE252C4Ae7ECE650B0e913f1f9', }, + ensWrappedBulkRenewal: { + address: '0xa12159e5131b1eEf6B4857EEE3e1954744b5033A', + }, + ensWrappedEthRegistrarController: { + address: '0x253553366Da8546fC250F225fe3d25d0C782303b', + }, }, [holesky.id]: { ensBaseRegistrarImplementation: { address: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85', }, ensBulkRenewal: { - address: '0xbc4cfB363F948E64Cd73Da6438F64CB37E2e33d1', + address: '0x035aC6754dAA9d67aE1fc9e70940e5ac0f45A75e', }, ensDnsRegistrar: { address: '0x458d278AEd4cE82BAeC384170f39198b01B8351c', @@ -83,7 +91,7 @@ export const addresses = { address: '0x283af0b28c62c092c9727f1ee09c02ca627eb7f5', }, ensEthRegistrarController: { - address: '0xF404D2F84BC1735f7D9948F032D61F5fFfD9D3C3', + address: '0xFce6ce4373CB6E7e470EAa55329638acD9Dbd202', }, ensNameWrapper: { address: '0xab50971078225D365994dc1Edcb9b7FD72Bb4862', @@ -100,6 +108,12 @@ export const addresses = { ensUniversalResolver: { address: '0xf606bc986635dab91b189aee8f565f45a0336f89', }, + ensWrappedBulkRenewal: { + address: '0xbc4cfB363F948E64Cd73Da6438F64CB37E2e33d1', + }, + ensWrappedEthRegistrarController: { + address: '0xF404D2F84BC1735f7D9948F032D61F5fFfD9D3C3', + }, legacyEthRegistrarController: { address: '0xf13fC748601fDc5afA255e9D9166EB43f603a903', }, @@ -115,7 +129,7 @@ export const addresses = { address: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85', }, ensBulkRenewal: { - address: '0x4EF77b90762Eddb33C8Eba5B5a19558DaE53D7a1', + address: '0x6394b694a8C0DC716e447802E568F0Fb2c4E0965', }, ensDnsRegistrar: { address: '0x5a07C75Ae469Bf3ee2657B588e8E6ABAC6741b4f', @@ -124,7 +138,7 @@ export const addresses = { address: '0xe62E4b6cE018Ad6e916fcC24545e20a33b9d8653', }, ensEthRegistrarController: { - address: '0x4477cAc137F3353Ca35060E01E5aEb777a1Ca01B', + address: '0xfb3cE5D01e0f33f41DbB39035dB9745962F1f968', }, ensNameWrapper: { address: '0x0635513f179D50A207757E05759CbD106d7dFcE8', @@ -150,6 +164,12 @@ export const addresses = { ensDefaultReverseRegistrar: { address: '0x4F382928805ba0e23B30cFB75fC9E848e82DFD47', }, + ensWrappedBulkRenewal: { + address: '0x4EF77b90762Eddb33C8Eba5B5a19558DaE53D7a1', + }, + ensWrappedEthRegistrarController: { + address: '0x4477cAc137F3353Ca35060E01E5aEb777a1Ca01B', + }, }, } as const satisfies Record< SupportedChain, @@ -182,16 +202,19 @@ export const subgraphs = { type EnsChainContracts = { ensBaseRegistrarImplementation: ChainContract + ensBulkRenewal: ChainContract ensDnsRegistrar: ChainContract + ensDnssecImpl: ChainContract ensEthRegistrarController: ChainContract ensNameWrapper: ChainContract ensPublicResolver: ChainContract ensRegistry: ChainContract ensReverseRegistrar: ChainContract - ensBulkRenewal: ChainContract - ensDnssecImpl: ChainContract + ensWrappedBulkRenewal: ChainContract + ensWrappedEthRegistrarController: ChainContract legacyEthRegistrarController: ChainContract legacyPublicResolver: ChainContract + ensDefaultReverseRegistrar: ChainContract } type BaseChainContracts = { diff --git a/packages/ensjs/src/contracts/ethRegistrarController.ts b/packages/ensjs/src/contracts/ethRegistrarController.ts index 389a40713..fdfc072ff 100644 --- a/packages/ensjs/src/contracts/ethRegistrarController.ts +++ b/packages/ensjs/src/contracts/ethRegistrarController.ts @@ -2,19 +2,52 @@ export const ethRegistrarControllerErrors = [ { inputs: [ { + internalType: 'bytes32', name: 'commitment', type: 'bytes32', }, ], + name: 'CommitmentNotFound', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'commitment', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'minimumCommitmentTimestamp', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'currentTimestamp', + type: 'uint256', + }, + ], name: 'CommitmentTooNew', type: 'error', }, { inputs: [ { + internalType: 'bytes32', name: 'commitment', type: 'bytes32', }, + { + internalType: 'uint256', + name: 'maximumCommitmentTimestamp', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'currentTimestamp', + type: 'uint256', + }, ], name: 'CommitmentTooOld', type: 'error', @@ -22,6 +55,7 @@ export const ethRegistrarControllerErrors = [ { inputs: [ { + internalType: 'uint256', name: 'duration', type: 'uint256', }, @@ -47,6 +81,7 @@ export const ethRegistrarControllerErrors = [ { inputs: [ { + internalType: 'string', name: 'name', type: 'string', }, @@ -54,6 +89,11 @@ export const ethRegistrarControllerErrors = [ name: 'NameNotAvailable', type: 'error', }, + { + inputs: [], + name: 'ResolverRequiredForReverseRecord', + type: 'error', + }, { inputs: [], name: 'ResolverRequiredWhenDataSupplied', @@ -62,6 +102,7 @@ export const ethRegistrarControllerErrors = [ { inputs: [ { + internalType: 'bytes32', name: 'commitment', type: 'bytes32', }, @@ -148,46 +189,76 @@ export const ethRegistrarControllerRegisterSnippet = [ { inputs: [ { - name: 'name', - type: 'string', + components: [ + { + name: 'label', + type: 'string', + }, + { + name: 'owner', + type: 'address', + }, + { + name: 'duration', + type: 'uint256', + }, + { + name: 'secret', + type: 'bytes32', + }, + { + name: 'resolver', + type: 'address', + }, + { + name: 'data', + type: 'bytes[]', + }, + { + name: 'reverseRecord', + type: 'uint8', + }, + { + name: 'referrer', + type: 'bytes32', + }, + ], + name: 'registration', + type: 'tuple', }, + ], + name: 'register', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const + +export const ethRegistrarControllerRenewSnippet = [ + ...ethRegistrarControllerErrors, + { + inputs: [ { - name: 'owner', - type: 'address', + name: 'name', + type: 'string', }, { name: 'duration', type: 'uint256', }, { - name: 'secret', + name: 'referrer', type: 'bytes32', }, - { - name: 'resolver', - type: 'address', - }, - { - name: 'data', - type: 'bytes[]', - }, - { - name: 'reverseRecord', - type: 'bool', - }, - { - name: 'ownerControlledFuses', - type: 'uint16', - }, ], - name: 'register', + name: 'renew', outputs: [], stateMutability: 'payable', type: 'function', }, ] as const -export const ethRegistrarControllerRenewSnippet = [ +export const wrappedEthRegistrarControllerRenewSnippet = [ ...ethRegistrarControllerErrors, { inputs: [ diff --git a/packages/ensjs/src/contracts/nameWrapper.ts b/packages/ensjs/src/contracts/nameWrapper.ts index db8db9752..143b6a822 100644 --- a/packages/ensjs/src/contracts/nameWrapper.ts +++ b/packages/ensjs/src/contracts/nameWrapper.ts @@ -387,3 +387,24 @@ export const nameWrapperSetResolverSnippet = [ ...nameWrapperErrors, ...registrySetResolverSnippet, ] as const + +export const nameWrapperIsWrappedSnippet = [ + ...nameWrapperErrors, + { + inputs: [ + { + name: '', + type: 'bytes32', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + name: 'isWrapped', + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/ensjs/src/functions/public/getPrice.ts b/packages/ensjs/src/functions/public/getPrice.ts index 4b6aaca0d..c7fae934c 100644 --- a/packages/ensjs/src/functions/public/getPrice.ts +++ b/packages/ensjs/src/functions/public/getPrice.ts @@ -3,11 +3,12 @@ import { type Hex, decodeFunctionResult, encodeFunctionData, + namehash, } from 'viem' import { bulkRenewalRentPriceSnippet } from '../../contracts/bulkRenewal.js' import type { ClientWithEns } from '../../contracts/consts.js' -import { ethRegistrarControllerRentPriceSnippet } from '../../contracts/ethRegistrarController.js' import { getChainContractAddress } from '../../contracts/getChainContractAddress.js' +import { nameWrapperIsWrappedSnippet } from '../../contracts/nameWrapper.js' import { UnsupportedNameTypeError } from '../../errors/general.js' import type { SimpleTransactionRequest } from '../../types.js' import { @@ -29,6 +30,8 @@ export type GetPriceReturnType = { base: bigint /** Price premium */ premium: bigint + /** Whether any of the names are wrapped */ + containsWrappedNames: boolean } const encode = ( @@ -49,73 +52,76 @@ const encode = ( }, ) - if (names.length > 1) { - const bulkRenewalAddress = getChainContractAddress({ - client, - contract: 'ensBulkRenewal', - }) - return multicallWrapper.encode(client, { - transactions: [ - { - to: bulkRenewalAddress, - data: encodeFunctionData({ - abi: bulkRenewalRentPriceSnippet, - functionName: 'rentPrice', - args: [names, BigInt(duration)], - }), - }, - { - to: bulkRenewalAddress, - data: encodeFunctionData({ - abi: bulkRenewalRentPriceSnippet, - functionName: 'rentPrice', - args: [names, 0n], - }), - }, - ], - }) - } - return { - to: getChainContractAddress({ - client, - contract: 'ensEthRegistrarController', - }), - data: encodeFunctionData({ - abi: ethRegistrarControllerRentPriceSnippet, - functionName: 'rentPrice', - args: [names[0], BigInt(duration)], - }), - } + const bulkRenewalAddress = getChainContractAddress({ + client, + contract: 'ensBulkRenewal', + }) + const nameWrapperAddress = getChainContractAddress({ + client, + contract: 'ensNameWrapper', + }) + return multicallWrapper.encode(client, { + transactions: [ + { + to: bulkRenewalAddress, + data: encodeFunctionData({ + abi: bulkRenewalRentPriceSnippet, + functionName: 'rentPrice', + args: [names, BigInt(duration)], + }), + }, + { + to: bulkRenewalAddress, + data: encodeFunctionData({ + abi: bulkRenewalRentPriceSnippet, + functionName: 'rentPrice', + args: [names, 0n], + }), + }, + ...names.map((name) => ({ + to: nameWrapperAddress, + data: encodeFunctionData({ + abi: nameWrapperIsWrappedSnippet, + functionName: 'isWrapped', + args: [namehash(name)], + }), + })), + ], + }) } const decode = async ( client: ClientWithEns, data: Hex | BaseError, - { nameOrNames }: GetPriceParameters, + _params: GetPriceParameters, ): Promise => { if (typeof data === 'object') throw data - const isBulkRenewal = Array.isArray(nameOrNames) && nameOrNames.length > 1 - if (isBulkRenewal) { - const result = await multicallWrapper.decode(client, data, []) - const price = decodeFunctionResult({ - abi: bulkRenewalRentPriceSnippet, - functionName: 'rentPrice', - data: result[0].returnData, - }) - const premium = decodeFunctionResult({ - abi: bulkRenewalRentPriceSnippet, - functionName: 'rentPrice', - data: result[1].returnData, - }) - const base = price - premium - return { base, premium } - } - - return decodeFunctionResult({ - abi: ethRegistrarControllerRentPriceSnippet, + const result = await multicallWrapper.decode(client, data, []) + const price = decodeFunctionResult({ + abi: bulkRenewalRentPriceSnippet, + functionName: 'rentPrice', + data: result[0].returnData, + }) + const premium = decodeFunctionResult({ + abi: bulkRenewalRentPriceSnippet, functionName: 'rentPrice', - data, + data: result[1].returnData, }) + const base = price - premium + + const containsWrappedNames = result.slice(2).some((r) => + decodeFunctionResult({ + abi: nameWrapperIsWrappedSnippet, + functionName: 'isWrapped', + data: r.returnData, + }), + ) + + return { + base, + premium, + containsWrappedNames, + } } type BatchableFunctionObject = GeneratedFunction diff --git a/packages/ensjs/src/functions/wallet/commitName.test.ts b/packages/ensjs/src/functions/wallet/commitName.test.ts index 7f484d11f..7bf75eba6 100644 --- a/packages/ensjs/src/functions/wallet/commitName.test.ts +++ b/packages/ensjs/src/functions/wallet/commitName.test.ts @@ -10,7 +10,7 @@ import { } from '../../test/addTestContracts.js' import { type RegistrationParameters, - makeCommitment, + createCommitmentHashWithDefaults, } from '../../utils/registerHelpers.js' import commitName from './commitName.js' @@ -53,7 +53,7 @@ it('should return a commit transaction and succeed', async () => { client: publicClient, contract: 'ensEthRegistrarController', }), - args: [makeCommitment(params)], + args: [createCommitmentHashWithDefaults(params)], }) expect(commitment).toBeTruthy() }) diff --git a/packages/ensjs/src/functions/wallet/commitName.ts b/packages/ensjs/src/functions/wallet/commitName.ts index da9c47aa6..609f5c211 100644 --- a/packages/ensjs/src/functions/wallet/commitName.ts +++ b/packages/ensjs/src/functions/wallet/commitName.ts @@ -18,7 +18,7 @@ import type { import { getNameType } from '../../utils/getNameType.js' import { type RegistrationParameters, - makeCommitment, + createCommitmentHashWithDefaults, } from '../../utils/registerHelpers.js' import { wrappedLabelLengthCheck } from '../../utils/wrapper.js' @@ -61,7 +61,7 @@ export const makeFunctionData = < data: encodeFunctionData({ abi: ethRegistrarControllerCommitSnippet, functionName: 'commit', - args: [makeCommitment(args)], + args: [createCommitmentHashWithDefaults(args)], }), } } @@ -106,7 +106,7 @@ async function commitName< resolverAddress, records, reverseRecord, - fuses, + referrer, ...txArgs }: CommitNameParameters, ): Promise { @@ -118,7 +118,7 @@ async function commitName< resolverAddress, records, reverseRecord, - fuses, + referrer, }) const writeArgs = { ...data, diff --git a/packages/ensjs/src/functions/wallet/registerName.ts b/packages/ensjs/src/functions/wallet/registerName.ts index 38a28f508..a080857aa 100644 --- a/packages/ensjs/src/functions/wallet/registerName.ts +++ b/packages/ensjs/src/functions/wallet/registerName.ts @@ -18,9 +18,8 @@ import type { import { getNameType } from '../../utils/getNameType.js' import { type RegistrationParameters, - makeRegistrationTuple, + registrationParametersWithDefaults, } from '../../utils/registerHelpers.js' -import { wrappedLabelLengthCheck } from '../../utils/wrapper.js' export type RegisterNameDataParameters = RegistrationParameters & { /** Value of registration */ @@ -57,9 +56,6 @@ export const makeFunctionData = < details: 'Only 2ld-eth name registration is supported', }) - const labels = args.name.split('.') - wrappedLabelLengthCheck(labels[0]) - return { to: getChainContractAddress({ client: wallet, @@ -68,7 +64,7 @@ export const makeFunctionData = < data: encodeFunctionData({ abi: ethRegistrarControllerRegisterSnippet, functionName: 'register', - args: makeRegistrationTuple(args), + args: [registrationParametersWithDefaults(args)], }), value, } @@ -128,7 +124,7 @@ async function registerName< resolverAddress, records, reverseRecord, - fuses, + referrer, value, ...txArgs }: RegisterNameParameters, @@ -141,7 +137,7 @@ async function registerName< resolverAddress, records, reverseRecord, - fuses, + referrer, value, }) const writeArgs = { diff --git a/packages/ensjs/src/functions/wallet/renewNames.test.ts b/packages/ensjs/src/functions/wallet/renewNames.test.ts index 8834c3694..51937f64e 100644 --- a/packages/ensjs/src/functions/wallet/renewNames.test.ts +++ b/packages/ensjs/src/functions/wallet/renewNames.test.ts @@ -1,5 +1,5 @@ import { type Address, type Hex, labelhash } from 'viem' -import { afterEach, beforeAll, beforeEach, expect, it } from 'vitest' +import { afterEach, beforeAll, beforeEach, expect, it, vi } from 'vitest' import { baseRegistrarNameExpiresSnippet } from '../../contracts/baseRegistrar.js' import { getChainContractAddress } from '../../contracts/getChainContractAddress.js' import { @@ -9,6 +9,7 @@ import { walletClient, } from '../../test/addTestContracts.js' import getPrice from '../public/getPrice.js' +import getWrapperData from '../public/getWrapperData.js' import renewNames from './renewNames.js' let snapshot: Hex @@ -91,3 +92,142 @@ it('should return a renewAll transaction for multiple names and succeed', async expect(newExpiries[i]).toBe(oldExpiries[i] + duration) } }) + +it('should include referrer when renewing unwrapped names', async () => { + const name = 'to-be-renewed.eth' + const duration = 31536000n + const referrer = + '0x000000000000000000000000000000000000000000000000000000000000dead' as Hex + + const price = await getPrice(publicClient, { + nameOrNames: name, + duration, + }) + const total = price!.base + price!.premium + + const tx = await renewNames(walletClient, { + nameOrNames: name, + duration, + value: total, + referrer, + account: accounts[0], + }) + expect(tx).toBeTruthy() + const receipt = await waitForTransaction(tx) + expect(receipt.status).toBe('success') +}) + +it('should auto-detect wrapped names when containsWrappedNames is not provided', async () => { + // Use a name that's actually wrapped in the test environment + const name = 'wrapped-with-subnames.eth' + const duration = 31536000n + + const price = await getPrice(publicClient, { + nameOrNames: name, + duration, + }) + const total = price!.base + price!.premium + + // First, verify the name is actually wrapped + const wrapperData = await getWrapperData(publicClient, { name }) + expect(wrapperData).toBeTruthy() + expect(wrapperData?.owner).not.toBe( + '0x0000000000000000000000000000000000000000', + ) + + // Spy on makeFunctionData to verify it's called with containsWrappedNames: true + const originalMakeFunctionData = renewNames.makeFunctionData + let actualContainsWrappedNames: boolean | undefined + renewNames.makeFunctionData = vi.fn((wallet, params) => { + actualContainsWrappedNames = params.containsWrappedNames + return originalMakeFunctionData(wallet, params) + }) + + // Don't provide containsWrappedNames - it should auto-detect as true + const tx = await renewNames(walletClient, { + nameOrNames: name, + duration, + value: total, + account: accounts[0], + }) + + // Verify auto-detection set containsWrappedNames to true + expect(actualContainsWrappedNames).toBe(true) + + // Restore original function + renewNames.makeFunctionData = originalMakeFunctionData + + expect(tx).toBeTruthy() + const receipt = await waitForTransaction(tx) + expect(receipt.status).toBe('success') +}) + +it('should auto-detect unwrapped names when containsWrappedNames is not provided', async () => { + // Use a name that's NOT wrapped in the test environment + const name = 'to-be-renewed.eth' + const duration = 31536000n + + const price = await getPrice(publicClient, { + nameOrNames: name, + duration, + }) + const total = price!.base + price!.premium + + // First, verify the name is NOT wrapped + const wrapperData = await getWrapperData(publicClient, { name }) + expect(wrapperData).toBeNull() + + // Spy on makeFunctionData to verify it's called with containsWrappedNames: false + const originalMakeFunctionData = renewNames.makeFunctionData + let actualContainsWrappedNames: boolean | undefined + renewNames.makeFunctionData = vi.fn((wallet, params) => { + actualContainsWrappedNames = params.containsWrappedNames + return originalMakeFunctionData(wallet, params) + }) + + // Don't provide containsWrappedNames - it should auto-detect as false + const tx = await renewNames(walletClient, { + nameOrNames: name, + duration, + value: total, + account: accounts[0], + }) + + // Verify auto-detection set containsWrappedNames to false + expect(actualContainsWrappedNames).toBe(false) + + // Restore original function + renewNames.makeFunctionData = originalMakeFunctionData + + expect(tx).toBeTruthy() + const receipt = await waitForTransaction(tx) + expect(receipt.status).toBe('success') +}) + +it('should throw error when referrer is provided for wrapped names', async () => { + const name = 'wrapped-with-subnames.eth' + const duration = 31536000n + const referrer = + '0x000000000000000000000000000000000000000000000000000000000000dead' as Hex + + const price = await getPrice(publicClient, { + nameOrNames: name, + duration, + }) + const total = price!.base + price!.premium + + // Verify the name is actually wrapped + const wrapperData = await getWrapperData(publicClient, { name }) + expect(wrapperData).toBeTruthy() + + await expect( + renewNames(walletClient, { + nameOrNames: name, + duration, + value: total, + containsWrappedNames: true, + referrer, + account: accounts[0], + }), + ).rejects.toThrow('referrer cannot be specified when renewing wrapped names') +}) diff --git a/packages/ensjs/src/functions/wallet/renewNames.ts b/packages/ensjs/src/functions/wallet/renewNames.ts index 0c7916c3b..3b6842bfe 100644 --- a/packages/ensjs/src/functions/wallet/renewNames.ts +++ b/packages/ensjs/src/functions/wallet/renewNames.ts @@ -1,22 +1,34 @@ import { type Account, type Hash, + type Hex, type SendTransactionParameters, type Transport, encodeFunctionData, + zeroHash, } from 'viem' import { sendTransaction } from 'viem/actions' -import { bulkRenewalRenewAllSnippet } from '../../contracts/bulkRenewal.js' +import { + bulkRenewalRenewAllSnippet, + wrappedBulkRenewalRenewAllSnippet, +} from '../../contracts/bulkRenewal.js' import type { ChainWithEns, ClientWithAccount } from '../../contracts/consts.js' -import { ethRegistrarControllerRenewSnippet } from '../../contracts/ethRegistrarController.js' +import { + ethRegistrarControllerRenewSnippet, + wrappedEthRegistrarControllerRenewSnippet, +} from '../../contracts/ethRegistrarController.js' import { getChainContractAddress } from '../../contracts/getChainContractAddress.js' -import { UnsupportedNameTypeError } from '../../errors/general.js' +import { + AdditionalParameterSpecifiedError, + UnsupportedNameTypeError, +} from '../../errors/general.js' import type { Prettify, SimpleTransactionRequest, WriteTransactionParameters, } from '../../types.js' import { getNameType } from '../../utils/getNameType.js' +import getWrapperData from '../public/getWrapperData.js' export type RenewNamesDataParameters = { /** Name or names to renew */ @@ -25,6 +37,10 @@ export type RenewNamesDataParameters = { duration: bigint | number /** Value of all renewals */ value: bigint + /** Whether any of the names are wrapped - if not provided, will be auto-detected */ + containsWrappedNames?: boolean + /** Referrer value */ + referrer?: Hex } export type RenewNamesDataReturnType = SimpleTransactionRequest & { @@ -47,7 +63,13 @@ export const makeFunctionData = < TAccount extends Account | undefined, >( wallet: ClientWithAccount, - { nameOrNames, duration, value }: RenewNamesDataParameters, + { + nameOrNames, + duration, + value, + containsWrappedNames, + referrer, + }: RenewNamesDataParameters, ): RenewNamesDataReturnType => { const names = Array.isArray(nameOrNames) ? nameOrNames : [nameOrNames] const labels = names.map((name) => { @@ -62,6 +84,50 @@ export const makeFunctionData = < return label[0] }) + if (containsWrappedNames) { + if (referrer) + throw new AdditionalParameterSpecifiedError({ + parameter: 'referrer', + allowedParameters: [ + 'nameOrNames', + 'duration', + 'value', + 'containsWrappedNames', + ], + details: 'referrer cannot be specified when renewing wrapped names', + }) + + if (labels.length === 1) + return { + to: getChainContractAddress({ + client: wallet, + contract: 'ensWrappedEthRegistrarController', + }), + data: encodeFunctionData({ + abi: wrappedEthRegistrarControllerRenewSnippet, + functionName: 'renew', + args: [labels[0], BigInt(duration)], + }), + value, + } + + return { + to: getChainContractAddress({ + client: wallet, + contract: 'ensWrappedBulkRenewal', + }), + data: encodeFunctionData({ + abi: wrappedBulkRenewalRenewAllSnippet, + functionName: 'renewAll', + args: [labels, BigInt(duration)], + }), + value, + } + } + + // For unwrapped names, use referrer (default to zeroHash if not provided) + const effectiveReferrer = referrer ?? zeroHash + if (labels.length === 1) { return { to: getChainContractAddress({ @@ -71,7 +137,7 @@ export const makeFunctionData = < data: encodeFunctionData({ abi: ethRegistrarControllerRenewSnippet, functionName: 'renew', - args: [labels[0], BigInt(duration)], + args: [labels[0], BigInt(duration), effectiveReferrer], }), value, } @@ -85,12 +151,32 @@ export const makeFunctionData = < data: encodeFunctionData({ abi: bulkRenewalRenewAllSnippet, functionName: 'renewAll', - args: [labels, BigInt(duration)], + args: [labels, BigInt(duration), effectiveReferrer], }), value, } } +/** + * Checks if any of the provided names are wrapped + * @param wallet - Client to use for checking + * @param names - Array of names to check + * @returns True if any name is wrapped + */ +async function checkContainsWrappedNames( + wallet: ClientWithAccount, + names: string[], +): Promise { + const checks = await Promise.all( + names.map(async (name) => { + const wrapperData = await getWrapperData(wallet, { name }) + // getWrapperData returns null for unwrapped names (owner === EMPTY_ADDRESS) + return wrapperData !== null + }), + ) + return checks.some((isWrapped) => isWrapped) +} + /** * Renews a name or names for a specified duration. * @param wallet - {@link ClientWithAccount} @@ -137,10 +223,25 @@ async function renewNames< nameOrNames, duration, value, + containsWrappedNames, + referrer, ...txArgs }: RenewNamesParameters, ): Promise { - const data = makeFunctionData(wallet, { nameOrNames, duration, value }) + // If containsWrappedNames is not provided, auto-detect it + let hasWrappedNames = containsWrappedNames + if (hasWrappedNames === undefined) { + const names = Array.isArray(nameOrNames) ? nameOrNames : [nameOrNames] + hasWrappedNames = await checkContainsWrappedNames(wallet, names) + } + + const data = makeFunctionData(wallet, { + nameOrNames, + duration, + value, + containsWrappedNames: hasWrappedNames, + referrer, + }) const writeArgs = { ...data, ...txArgs, diff --git a/packages/ensjs/src/test/addTestContracts.ts b/packages/ensjs/src/test/addTestContracts.ts index 17e67b4d0..264493b23 100644 --- a/packages/ensjs/src/test/addTestContracts.ts +++ b/packages/ensjs/src/test/addTestContracts.ts @@ -44,7 +44,9 @@ export const deploymentAddresses = JSON.parse( | 'ENSRegistry' | 'LegacyPublicResolver' | 'NoMulticallResolver' - | 'OldestResolver', + | 'OldestResolver' + | 'WrappedBulkRenewal' + | 'WrappedEthRegistrarController', Address > @@ -84,6 +86,12 @@ export const localhost = { ensDnssecImpl: { address: deploymentAddresses.DNSSECImpl, }, + ensWrappedBulkRenewal: { + address: deploymentAddresses.WrappedBulkRenewal, + }, + ensWrappedEthRegistrarController: { + address: deploymentAddresses.WrappedEthRegistrarController, + }, legacyEthRegistrarController: { address: deploymentAddresses.LegacyETHRegistrarController, }, diff --git a/packages/ensjs/src/utils/index.ts b/packages/ensjs/src/utils/index.ts index 9c54f5729..56a0ddfa5 100644 --- a/packages/ensjs/src/utils/index.ts +++ b/packages/ensjs/src/utils/index.ts @@ -142,14 +142,12 @@ export { type ValidToken, } from './normalise.js' export { - makeCommitment, - makeCommitmentFromTuple, - makeCommitmentTuple, - makeRegistrationTuple, + createCommitmentHash, + createCommitmentHashWithDefaults, randomSecret, - type CommitmentTuple, + registrationParametersWithDefaults, + type PreparedRegistrationParameters, type RegistrationParameters, - type RegistrationTuple, } from './registerHelpers.js' export { checkIsDotEth, diff --git a/packages/ensjs/src/utils/registerHelpers.test.ts b/packages/ensjs/src/utils/registerHelpers.test.ts index fdd8574b9..add979968 100644 --- a/packages/ensjs/src/utils/registerHelpers.test.ts +++ b/packages/ensjs/src/utils/registerHelpers.test.ts @@ -1,13 +1,10 @@ -import { labelhash } from 'viem' import { describe, expect, it } from 'vitest' import { namehash } from './normalise.js' import { - type RegistrationParameters, - makeCommitment, - makeCommitmentFromTuple, - makeCommitmentTuple, - makeRegistrationTuple, + createCommitmentHash, + createCommitmentHashWithDefaults, randomSecret, + registrationParametersWithDefaults, } from './registerHelpers.js' describe('randomSecret()', () => { @@ -30,105 +27,105 @@ describe('randomSecret()', () => { expect(() => randomSecret({ campaign: 0xffffffff + 1 }), ).toThrowErrorMatchingInlineSnapshot(` - [CampaignReferenceTooLargeError: Campaign reference 4294967296 is too large + [CampaignReferenceTooLargeError: Campaign reference 4294967296 is too large - - Max campaign reference: 4294967295 + - Max campaign reference: 4294967295 - Version: @ensdomains/ensjs@1.0.0-mock.0] + Version: @ensdomains/ensjs@1.0.0-mock.0] `) }) }) -describe('makeCommitmentTuple()', () => { - it('generates a commitment tuple', () => { - const tuple = makeCommitmentTuple({ +describe('registrationParametersWithDefaults()', () => { + it('generates prepared registration parameters', () => { + const params = registrationParametersWithDefaults({ name: 'test.eth', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', duration: 31536000, secret: '0xsecret', }) - // labelhash - expect(tuple[0]).toBe(labelhash('test')) + // label (not labelhash) + expect(params.label).toBe('test') // owner - expect(tuple[1]).toBe('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266') + expect(params.owner).toBe('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266') // duration - expect(tuple[2]).toBe(31536000n) + expect(params.duration).toBe(31536000n) // secret - expect(tuple[3]).toBe('0xsecret') + expect(params.secret).toBe('0xsecret') // resolver address - expect(tuple[4]).toBe('0x0000000000000000000000000000000000000000') - // records - expect(tuple[5]).toStrictEqual([]) + expect(params.resolver).toBe('0x0000000000000000000000000000000000000000') + // records data + expect(params.data).toStrictEqual([]) // reverse record - expect(tuple[6]).toBe(false) - // owner controlled fuses - expect(tuple[7]).toBe(0) + expect(params.reverseRecord).toBe(0) + // referrer defaults to zeroHash + expect(params.referrer).toBe( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ) }) - it('encodes fuses when supplied', () => { - const tuple = makeCommitmentTuple({ + + it('handles referrer when supplied', () => { + const params = registrationParametersWithDefaults({ name: 'test.eth', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', duration: 31536000, secret: '0xsecret', - fuses: { - named: ['CANNOT_UNWRAP', 'CANNOT_BURN_FUSES'], - }, + referrer: + '0x0000000000000000000000000000000000000000000000000000000000000001', }) - expect(tuple[7]).toBe(3) + expect(params.referrer).toBe( + '0x0000000000000000000000000000000000000000000000000000000000000001', + ) }) + it('adds ETH coin when reverse record is supplied and no ETH coin is supplied', () => { - const tuple = makeCommitmentTuple({ + const params = registrationParametersWithDefaults({ name: 'test.eth', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', duration: 31536000, secret: '0xsecret', - reverseRecord: true, - resolverAddress: '0xresolverAddress', + reverseRecord: ['ethereum'], + resolverAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', }) - expect(tuple[5]).toMatchInlineSnapshot(` - [ - "0x8b95dd71eb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f1000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000", - ] - `) - expect(tuple[6]).toBe(true) + // Should add ETH coin data + expect(params.data).toHaveLength(1) + expect(params.data[0]).toMatch(/^0x8b95dd71/) + // Should set reverse record bitmask for ethereum + expect(params.reverseRecord).toBe(1) }) + it('does not add ETH coin when reverse record is supplied and ETH coin is supplied', () => { - const tuple = makeCommitmentTuple({ + const params = registrationParametersWithDefaults({ name: 'test.eth', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', duration: 31536000, + secret: '0xsecret', + reverseRecord: ['ethereum'], + resolverAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', records: { coins: [ - { coin: 'ETH', value: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' }, + { + coin: 60, + value: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', + }, ], }, - resolverAddress: '0xresolverAddress', - secret: '0xsecret', - reverseRecord: true, }) - expect(tuple[5]).toMatchInlineSnapshot(` - [ - "0x8b95dd71eb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f1000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000", - ] - `) - expect(tuple[6]).toBe(true) + expect(params.data).toHaveLength(1) + expect(params.data[0]).toMatch(/^0x8b95dd71/) }) + it('throws when records are supplied without a resolver address', () => { expect(() => - makeCommitmentTuple({ + registrationParametersWithDefaults({ name: 'test.eth', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', duration: 31536000, + secret: '0xsecret', records: { - coins: [ - { - coin: 'ETH', - value: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - }, - ], + texts: [{ key: 'text', value: 'text' }], }, - secret: '0xsecret', - reverseRecord: true, + reverseRecord: ['default'], }), ).toThrowErrorMatchingInlineSnapshot(` [ResolverAddressRequiredError: Resolver address is required when data is supplied @@ -139,48 +136,33 @@ describe('makeCommitmentTuple()', () => { - duration: 31536000 - resolverAddress: 0x0000000000000000000000000000000000000000 - records: [object Object] - - reverseRecord: true - - fuses: undefined + - reverseRecord: default + - referrer: 0x0000000000000000000000000000000000000000000000000000000000000000 Version: @ensdomains/ensjs@1.0.0-mock.0] `) }) }) -describe('makeRegistrationTuple()', () => { - it('replaces labelhash from commitment tuple with label', () => { - const data: RegistrationParameters = { - name: 'test.eth', - owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - duration: 31536000, - secret: '0xsecret', - } - const commitmentTuple = makeCommitmentTuple(data) - const registrationTuple = makeRegistrationTuple(data) - expect(registrationTuple[0]).toBe('test') - expect(registrationTuple.slice(1)).toEqual(commitmentTuple.slice(1)) - }) -}) - -describe('makeCommitmentFromTuple()', () => { - it('generates a commitment from a tuple', () => { - const tuple = makeCommitmentTuple({ +describe('createCommitmentHash()', () => { + it('generates a commitment hash from prepared parameters', () => { + const params = registrationParametersWithDefaults({ name: 'test.eth', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', duration: 31536000, secret: '0xde99acb8241826c5b3012b2c7a05dc28043428744a9c39445b4707c92b3fc054', }) - const commitment = makeCommitmentFromTuple(tuple) + const commitment = createCommitmentHash(params) expect(commitment).toMatchInlineSnapshot( - `"0x0d7fe28313600187945700f6c6374cc0ba4a360df039b3e62d435506e69dbe63"`, + `"0x47f42065b3983ae263ef6f461bc4e0987e8a03c13f6cb5bf7b92d90d2c1d33c4"`, ) }) }) -describe('makeCommitment()', () => { - it('generates a commitment from a RegistrationParameters', () => { - const commitment = makeCommitment({ +describe('createCommitmentHashWithDefaults()', () => { + it('generates a commitment hash from RegistrationParameters', () => { + const commitment = createCommitmentHashWithDefaults({ name: 'test.eth', owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', duration: 31536000, @@ -188,7 +170,7 @@ describe('makeCommitment()', () => { '0xde99acb8241826c5b3012b2c7a05dc28043428744a9c39445b4707c92b3fc054', }) expect(commitment).toMatchInlineSnapshot( - `"0x0d7fe28313600187945700f6c6374cc0ba4a360df039b3e62d435506e69dbe63"`, + `"0x47f42065b3983ae263ef6f461bc4e0987e8a03c13f6cb5bf7b92d90d2c1d33c4"`, ) }) }) diff --git a/packages/ensjs/src/utils/registerHelpers.ts b/packages/ensjs/src/utils/registerHelpers.ts index c4d712eba..aa9bea3b7 100644 --- a/packages/ensjs/src/utils/registerHelpers.ts +++ b/packages/ensjs/src/utils/registerHelpers.ts @@ -1,19 +1,19 @@ +import { evmChainIdToCoinType } from '@ensdomains/address-encoder/utils' import { type Address, type Hex, encodeAbiParameters, keccak256, - labelhash, pad, toBytes, toHex, + zeroHash, } from 'viem' import { CampaignReferenceTooLargeError, ResolverAddressRequiredError, } from '../errors/utils.js' import { EMPTY_ADDRESS } from './consts.js' -import { type EncodeChildFusesInputObject, encodeFuses } from './fuses.js' import { type RecordOptions, generateRecordCallArray, @@ -34,32 +34,21 @@ export type RegistrationParameters = { /** Records to set upon registration */ records?: RecordOptions /** Sets primary name upon registration */ - reverseRecord?: boolean - /** Fuses to set upon registration */ - fuses?: EncodeChildFusesInputObject + reverseRecord?: ('ethereum' | 'default')[] + /** Referrer value to use for registration */ + referrer?: Hex } -export type CommitmentTuple = [ - labelHash: Hex, - owner: Address, - duration: bigint, - secret: Hex, - resolver: Address, - data: Hex[], - reverseRecord: boolean, - ownerControlledFuses: number, -] - -export type RegistrationTuple = [ - label: string, - owner: Address, - duration: bigint, - secret: Hex, - resolver: Address, - data: Hex[], - reverseRecord: boolean, - ownerControlledFuses: number, -] +export type PreparedRegistrationParameters = { + label: string + owner: Address + duration: bigint + secret: Hex + resolver: Address + data: Hex[] + reverseRecord: number + referrer: Hex +} const cryptoRef = (typeof crypto !== 'undefined' && crypto) || @@ -93,24 +82,27 @@ export const randomSecret = ({ return toHex(bytes) } -export const makeCommitmentTuple = ({ +const reverseRecordBitmask = { + ethereum: 1, + default: 2, +} + +export const registrationParametersWithDefaults = ({ name, owner, duration, resolverAddress = EMPTY_ADDRESS, records: { coins = [], ...records } = { texts: [], coins: [] }, - reverseRecord, - fuses, + reverseRecord = [], secret, -}: RegistrationParameters): CommitmentTuple => { - const labelHash = labelhash(name.split('.')[0]) + referrer = zeroHash, +}: RegistrationParameters): PreparedRegistrationParameters => { const hash = namehash(name) - const fuseData = fuses - ? encodeFuses({ restriction: 'child', input: fuses }) - : 0 - if ( - reverseRecord && + if (reverseRecord.includes('default') && !coins.length) + coins.push({ coin: evmChainIdToCoinType(0), value: owner }) + else if ( + reverseRecord.includes('ethereum') && !coins.find( (c) => (typeof c.coin === 'string' && c.coin.toLowerCase() === 'eth') || @@ -118,14 +110,14 @@ export const makeCommitmentTuple = ({ ? Number.parseInt(c.coin) === 60 : c.coin === 60), ) - ) { + ) coins.push({ coin: 60, value: owner }) - } const data = records ? generateRecordCallArray({ namehash: hash, coins, ...records }) : [] + // this will throw if reverseRecord is going to be set as well if (data.length > 0 && resolverAddress === EMPTY_ADDRESS) throw new ResolverAddressRequiredError({ data: { @@ -135,48 +127,75 @@ export const makeCommitmentTuple = ({ resolverAddress, records, reverseRecord, - fuses, + referrer, }, }) - return [ - labelHash, + return { + label: name.split('.')[0], owner, - BigInt(duration), + duration: BigInt(duration), secret, - resolverAddress, + resolver: resolverAddress, data, - !!reverseRecord, - fuseData, - ] -} - -export const makeRegistrationTuple = ( - params: RegistrationParameters, -): RegistrationTuple => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [_labelhash, ...commitmentData] = makeCommitmentTuple(params) - const label = params.name.split('.')[0] - return [label, ...commitmentData] + reverseRecord: reverseRecord.reduce( + (acc, curr) => acc | reverseRecordBitmask[curr], + 0, + ), + referrer, + } } -export const makeCommitmentFromTuple = (params: CommitmentTuple): Hex => { +export const createCommitmentHash = ( + params: PreparedRegistrationParameters, +): Hex => { return keccak256( encodeAbiParameters( [ - { name: 'name', type: 'bytes32' }, - { name: 'owner', type: 'address' }, - { name: 'duration', type: 'uint256' }, - { name: 'secret', type: 'bytes32' }, - { name: 'resolver', type: 'address' }, - { name: 'data', type: 'bytes[]' }, - { name: 'reverseRecord', type: 'bool' }, - { name: 'ownerControlledFuses', type: 'uint16' }, + { + components: [ + { + name: 'label', + type: 'string', + }, + { + name: 'owner', + type: 'address', + }, + { + name: 'duration', + type: 'uint256', + }, + { + name: 'secret', + type: 'bytes32', + }, + { + name: 'resolver', + type: 'address', + }, + { + name: 'data', + type: 'bytes[]', + }, + { + name: 'reverseRecord', + type: 'uint8', + }, + { + name: 'referrer', + type: 'bytes32', + }, + ], + name: 'registration', + type: 'tuple', + }, ], - params, + [params], ), ) } -export const makeCommitment = (params: RegistrationParameters): Hex => - makeCommitmentFromTuple(makeCommitmentTuple(params)) +export const createCommitmentHashWithDefaults = ( + params: RegistrationParameters, +): Hex => createCommitmentHash(registrationParametersWithDefaults(params))