Skip to content
62 changes: 28 additions & 34 deletions src/actions/ens/getEnsAddress.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeAll, describe, expect, test } from 'vitest'
import { beforeAll, expect, test } from 'vitest'

import { createHttpServer, setVitalikResolver } from '~test/src/utils.js'
import { anvilMainnet } from '../../../test/src/anvil.js'
Expand All @@ -12,7 +12,7 @@ const client = anvilMainnet.getClient()

beforeAll(async () => {
await reset(client, {
blockNumber: 19_258_213n,
blockNumber: 23_085_558n,
jsonRpcUrl: anvilMainnet.forkUrl,
})
await setVitalikResolver()
Expand Down Expand Up @@ -52,15 +52,29 @@ test('gets address that starts with 0s for name', async () => {

test('gets address for name with coinType', async () => {
await expect(
getEnsAddress(client, { name: 'awkweb.eth', coinType: 60 }),
getEnsAddress(client, { name: 'awkweb.eth', coinType: 60n }),
).resolves.toMatchInlineSnapshot(
'"0xa0cf798816d4b9b9866b5330eea46a18382f251e"',
)
})

test('name without address with coinType', async () => {
await expect(
getEnsAddress(client, { name: 'awkweb.eth', coinType: 61 }),
getEnsAddress(client, { name: 'awkweb.eth', coinType: 61n }),
).resolves.toBeNull()
})

test('name with address with chainId', async () => {
await expect(
getEnsAddress(client, { name: 'taytems.eth', chainId: 10 }),
).resolves.toMatchInlineSnapshot(
'"0x8e8db5ccef88cca9d624701db544989c996e3216"',
)
})

test('name without address with chainId', async () => {
await expect(
getEnsAddress(client, { name: 'awkweb.eth', chainId: 10 }),
).resolves.toBeNull()
})

Expand All @@ -80,15 +94,15 @@ test('name with resolver that does not support addr - strict', async () => {
await expect(
getEnsAddress(client, { name: 'vitalik.eth', strict: true }),
).rejects.toMatchInlineSnapshot(`
[ContractFunctionExecutionError: The contract function "resolve" reverted.
[ContractFunctionExecutionError: The contract function "resolveWithGateways" reverted.

Error: ResolverError(bytes returnData)
Error: ResolverError(bytes errorData)
(0x)

Contract Call:
address: 0x0000000000000000000000000000000000000000
function: resolve(bytes name, bytes data, string[] gateways)
args: (0x07766974616c696b0365746800, 0x3b3b57deee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835, ["x-batch-gateway:true"])
function: resolveWithGateways(bytes name, bytes data, string[] gateways)
args: (0x07766974616c696b0365746800, 0x3b3b57deee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835, ["x-batch-gateway:true"])

Docs: https://viem.sh/docs/contract/readContract
Version: viem@x.y.z]
Expand All @@ -112,7 +126,6 @@ test('name with a label larger than 255 bytes', async () => {
await expect(
getEnsAddress(client, {
name: `${'9'.repeat(291)}.eth`,
universalResolverAddress: '0xc0497e381f536be9ce14b0dd3817cbcae57d2f62',
}),
).resolves.toMatchInlineSnapshot(
`"0xcdf14B42e1D3c264F6955521944a50d9A4d5CF3a"`,
Expand All @@ -132,7 +145,7 @@ test('offchain: name without address', async () => {
getEnsAddress(client, {
name: 'loalsdsladasdhjasgdhasjdghasgdjgasjdasd.cb.id',
}),
).resolves.toMatchInlineSnapshot('null')
).resolves.toBeNull()
})

test('offchain: aggregated', async () => {
Expand Down Expand Up @@ -165,32 +178,13 @@ test('custom universal resolver address', async () => {
await expect(
getEnsAddress(client, {
name: 'awkweb.eth',
universalResolverAddress: '0x74E20Bd2A1fE0cdbe45b9A1d89cb7e0a45b36376',
universalResolverAddress: '0xED73a03F19e8D849E44a39252d222c6ad5217E1e',
}),
).resolves.toMatchInlineSnapshot(
'"0xA0Cf798816D4b9b9866b5330EEa46a18382f251e"',
)
})

describe('universal resolver with custom errors', () => {
test('name without resolver', async () => {
await expect(
getEnsAddress(client, {
name: 'random123.zzz',
universalResolverAddress: '0x9380F1974D2B7064eA0c0EC251968D8c69f0Ae31',
}),
).resolves.toBeNull()
})
test('name with invalid wildcard resolver', async () => {
await expect(
getEnsAddress(client, {
name: 'another-unregistered-name.eth',
universalResolverAddress: '0x9380F1974D2B7064eA0c0EC251968D8c69f0Ae31',
}),
).resolves.toBeNull()
})
})

test('chain not provided', async () => {
await expect(
getEnsAddress(
Expand Down Expand Up @@ -230,7 +224,7 @@ test('universal resolver contract deployed on later block', async () => {
[ChainDoesNotSupportContract: Chain "Ethereum (Local)" does not support contract "ensUniversalResolver".

This could be due to any of the following:
- The contract "ensUniversalResolver" was not deployed until block 19258213 (current block 14353601).
- The contract "ensUniversalResolver" was not deployed until block 23085558 (current block 14353601).

Version: viem@x.y.z]
`)
Expand All @@ -243,12 +237,12 @@ test('invalid universal resolver address', async () => {
universalResolverAddress: '0xecb504d39723b0be0e3a9aa33d646642d1051ee1',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[ContractFunctionExecutionError: The contract function "resolve" reverted.
[ContractFunctionExecutionError: The contract function "resolveWithGateways" reverted.

Contract Call:
address: 0x0000000000000000000000000000000000000000
function: resolve(bytes name, bytes data, string[] gateways)
args: (0x0661776b7765620365746800, 0x3b3b57de52d0f5fbf348925621be297a61b88ec492ebbbdfa9477d82892e2786020ad61c, ["x-batch-gateway:true"])
function: resolveWithGateways(bytes name, bytes data, string[] gateways)
args: (0x0661776b7765620365746800, 0x3b3b57de52d0f5fbf348925621be297a61b88ec492ebbbdfa9477d82892e2786020ad61c, ["x-batch-gateway:true"])

Docs: https://viem.sh/docs/contract/readContract
Version: viem@x.y.z]
Expand Down
48 changes: 37 additions & 11 deletions src/actions/ens/getEnsAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import {
import { type TrimErrorType, trim } from '../../utils/data/trim.js'
import { type ToHexErrorType, toHex } from '../../utils/encoding/toHex.js'
import { isNullUniversalResolverError } from '../../utils/ens/errors.js'
import {
type EvmChainIdToCoinTypeError,
evmChainIdToCoinType,
} from '../../utils/ens/evmChainIdToCoinType.js'
import { localBatchGatewayUrl } from '../../utils/ens/localBatchGatewayRequest.js'
import { type NamehashErrorType, namehash } from '../../utils/ens/namehash.js'
import {
Expand All @@ -38,8 +42,6 @@ import {

export type GetEnsAddressParameters = Prettify<
Pick<ReadContractParameters, 'blockNumber' | 'blockTag'> & {
/** ENSIP-9 compliant coinType used to resolve addresses for other chains */
coinType?: number | undefined
/** Universal Resolver gateway URLs to use for resolving CCIP-read requests. */
gatewayUrls?: string[] | undefined
/** Name to get the address for. */
Expand All @@ -48,12 +50,24 @@ export type GetEnsAddressParameters = Prettify<
strict?: boolean | undefined
/** Address of ENS Universal Resolver Contract. */
universalResolverAddress?: Address | undefined
}
} & (
| {
/** ENSIP-11 chainId used to resolve addresses for other chains */
chainId?: number | undefined
coinType?: undefined
}
| {
chainId?: undefined
/** ENSIP-9 compliant coinType used to resolve addresses for other chains */
coinType?: bigint | undefined
}
)
>

export type GetEnsAddressReturnType = Address | null

export type GetEnsAddressErrorType =
| EvmChainIdToCoinTypeError
| GetChainContractAddressErrorType
| EncodeFunctionDataErrorType
| NamehashErrorType
Expand Down Expand Up @@ -95,8 +109,15 @@ export async function getEnsAddress<chain extends Chain | undefined>(
client: Client<Transport, chain>,
parameters: GetEnsAddressParameters,
): Promise<GetEnsAddressReturnType> {
const { blockNumber, blockTag, coinType, name, gatewayUrls, strict } =
parameters
const {
blockNumber,
blockTag,
chainId,
coinType,
name,
gatewayUrls,
strict,
} = parameters
const { chain } = client

const universalResolverAddress = (() => {
Expand All @@ -116,19 +137,24 @@ export async function getEnsAddress<chain extends Chain | undefined>(
const tlds = chain?.ensTlds
if (tlds && !tlds.some((tld) => name.endsWith(tld))) return null

const args = (() => {
if (coinType != null) return [namehash(name), BigInt(coinType)] as const
if (chainId != null)
return [namehash(name), evmChainIdToCoinType(chainId)] as const
return [namehash(name)] as const
})()

try {
const functionData = encodeFunctionData({
abi: addressResolverAbi,
functionName: 'addr',
...(coinType != null
? { args: [namehash(name), BigInt(coinType)] }
: { args: [namehash(name)] }),
args,
})

const readContractParameters = {
address: universalResolverAddress,
abi: universalResolverResolveAbi,
functionName: 'resolve',
functionName: 'resolveWithGateways',
args: [
toHex(packetToBytes(name)),
functionData,
Expand All @@ -146,7 +172,7 @@ export async function getEnsAddress<chain extends Chain | undefined>(

const address = decodeFunctionResult({
abi: addressResolverAbi,
args: coinType != null ? [namehash(name), BigInt(coinType)] : undefined,
args,
functionName: 'addr',
data: res[0],
})
Expand All @@ -156,7 +182,7 @@ export async function getEnsAddress<chain extends Chain | undefined>(
return address
} catch (err) {
if (strict) throw err
if (isNullUniversalResolverError(err, 'resolve')) return null
if (isNullUniversalResolverError(err)) return null
throw err
}
}
2 changes: 1 addition & 1 deletion src/actions/ens/getEnsAvatar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ beforeAll(async () => {
address: address.vitalik,
})
await reset(client, {
blockNumber: 19_258_213n,
blockNumber: 23_085_558n,
jsonRpcUrl: anvilMainnet.forkUrl,
})
})
Expand Down
Loading
Loading