import { ethRegistrarAvailableSnippet } from '@ensdomains/ensjs-abi/v2/ethRegistrar' import { permissionedRegistryGetStatusSnippet } from '@ensdomains/ensjs-abi/v2/permissionedRegistry' import { universalHelperFindParentRegistrySnippet } from '@ensdomains/ensjs-abi/v2/universalHelper' import type { Chain, GetChainContractAddressErrorType, ReadContractErrorType, } from 'viem' import { labelhash, toHex, zeroAddress } from 'viem' import { readContract } from 'viem/actions' import { packetToBytes } from 'viem/ens' import { getAction } from 'viem/utils' import type { RequireClientContracts } from '../../../clients/chain.js' import { getChainContractAddress } from '../../../clients/chain.js' import { UnsupportedNameTypeError } from '../../../errors/general.js' import type { ErrorType } from '../../../errors/utils.js' import { ASSERT_NO_TYPE_ERROR } from '../../../types/internal.js' import { getNameType } from '../../../utils/name/getNameType.js' export type GetAvailableParameters = { /** * Name to check availability for. Only `.eth` names are supported: * - `eth-2ld` names (e.g. `name.eth`) * - `eth-subname` names of any depth (e.g. `sub.name.eth`) * * DNS names are not supported (they have both on-chain and off-chain * import paths, so a single availability answer would be ambiguous). */ name: string } export type GetAvailableReturnType = boolean export type GetAvailableErrorType = | UnsupportedNameTypeError | ReadContractErrorType | GetChainContractAddressErrorType | ErrorType /** * Gets the availability of a `.eth` name to register. * * For `eth-2ld` names, availability is read from the `.eth` registrar via * `isAvailable`, which accounts for grace periods and premium decay. * * For `eth-subname` names, the UniversalHelper's `findParentRegistry` * is used to traverse the registry tree and locate the parent registry, then * the leaf label's status is checked against it. * * @param client - {@link Client} * @param parameters - {@link GetAvailableParameters} * @returns Availability as boolean. {@link GetAvailableReturnType} * * @example * import { createPublicClient, http } from 'viem' * import { sepolia } from 'viem/chains' * import { addEnsContracts } from '@ensdomains/ensjs' * import { getAvailable } from '@ensdomains/ensjs/public' * * const client = createPublicClient({ * chain: addEnsContracts(sepolia), * transport: http(), * }) * * // eth-2ld via the .eth registrar * const a = await getAvailable(client, { name: 'ens.eth' }) * // false * * // eth-subname with auto-traversal via the UniversalHelper * const b = await getAvailable(client, { name: 'sub.ens.eth' }) */ export async function getAvailable( client: RequireClientContracts< chain, 'ensEthRegistrar' | 'ensUniversalHelper' >, { name }: GetAvailableParameters, ): Promise { ASSERT_NO_TYPE_ERROR(client) const labels = name.split('.') const nameType = getNameType(name) if (nameType !== 'eth-2ld' && nameType !== 'eth-subname') throw new UnsupportedNameTypeError({ nameType, supportedNameTypes: ['eth-2ld', 'eth-subname'], details: 'Only .eth 2LD and subname names can be checked for availability', }) const readContractAction = getAction(client, readContract, 'readContract') const leafLabel = labels[0] // eth-2ld: availability comes from the .eth registrar (rent/premium aware). if (nameType === 'eth-2ld') { return readContractAction({ address: getChainContractAddress({ chain: client.chain, contract: 'ensEthRegistrar', }), abi: ethRegistrarAvailableSnippet, functionName: 'isAvailable', args: [leafLabel], }) } // eth-subname: use the UniversalHelper to find the parent registry, then // check the leaf label's status. A missing parent registry means the name is // available. The configured `ensUniversalHelper` exposes `findParentRegistry` // for both v1 and v2 names. const universalHelperAddress = getChainContractAddress({ chain: client.chain, contract: 'ensUniversalHelper', }) const parentRegistry = await readContractAction({ address: universalHelperAddress, abi: universalHelperFindParentRegistrySnippet, functionName: 'findParentRegistry', args: [toHex(packetToBytes(name))], }) if (parentRegistry === zeroAddress) return true const status = await readContractAction({ address: parentRegistry, abi: permissionedRegistryGetStatusSnippet, functionName: 'getStatus', args: [BigInt(labelhash(leafLabel))], }) return status === 0 }