From 477befdc26104501880219ca5028b333e3cf7886 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 24 Jun 2026 18:49:52 +0300 Subject: [PATCH] fix(ensjs): enforce normalization in getName getName previously coerced the reverse-resolved name via normalise(), silently returning a different name than what was resolved. It now uses viem's normalize() to verify the name is already normalised and returns null otherwise, matching viem's getEnsName (wevm/viem#4756) and v5. Applies to both the match and allowMismatch paths. Preserves the encode/decode batching API and the existing return body. Closes WEB-533 --- .changeset/getname-enforce-normalization.md | 5 ++ .../src/functions/public/getName.test.ts | 67 ++++++++++++++++++- .../ensjs/src/functions/public/getName.ts | 25 +++++-- 3 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 .changeset/getname-enforce-normalization.md diff --git a/.changeset/getname-enforce-normalization.md b/.changeset/getname-enforce-normalization.md new file mode 100644 index 000000000..c1b6b3c83 --- /dev/null +++ b/.changeset/getname-enforce-normalization.md @@ -0,0 +1,5 @@ +--- +"@ensdomains/ensjs": patch +--- + +`getName` now enforces normalization: it only returns a primary name if the value returned by reverse resolution is already in its normalised form, returning `null` otherwise (instead of silently coercing it). This mirrors viem's `getEnsName` behaviour (wevm/viem#4756) and brings v4 in line with v5 (WEB-533). The normalization check is applied to both the matching and `allowMismatch` paths. diff --git a/packages/ensjs/src/functions/public/getName.test.ts b/packages/ensjs/src/functions/public/getName.test.ts index 8e724c9d3..83b6e4137 100644 --- a/packages/ensjs/src/functions/public/getName.test.ts +++ b/packages/ensjs/src/functions/public/getName.test.ts @@ -4,10 +4,15 @@ import { RawContractError, bytesToHex, encodeErrorResult, + encodeFunctionResult, + zeroAddress, } from 'viem' import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest' import type { ClientWithEns } from '../../contracts/consts.js' -import { universalResolverErrors } from '../../contracts/universalResolver.js' +import { + universalResolverErrors, + universalResolverReverseSnippet, +} from '../../contracts/universalResolver.js' import { deploymentAddresses, publicClient, @@ -150,6 +155,66 @@ describe('getName', () => { Version: viem@2.37.12] `) }) + it('should return null for an unnormalised name in the match path', async () => { + const result = await getName.decode( + {} as ClientWithEns, + encodeFunctionResult({ + abi: universalResolverReverseSnippet, + functionName: 'reverse', + result: ['Nick.eth', zeroAddress, zeroAddress], + }), + { + address: '0x1234567890abcdef', + args: ['0x', 60n], + }, + { + address: accounts[0], + strict: false, + }, + ) + expect(result).toBeNull() + }) + it('should return a normalised match unchanged', async () => { + const result = await getName.decode( + {} as ClientWithEns, + encodeFunctionResult({ + abi: universalResolverReverseSnippet, + functionName: 'reverse', + result: ['nick.eth', zeroAddress, zeroAddress], + }), + { + address: '0x1234567890abcdef', + args: ['0x', 60n], + }, + { + address: accounts[0], + strict: false, + }, + ) + expect(result).toMatchObject({ name: 'nick.eth', match: true }) + }) + it('should return null for an unnormalised name in the mismatch path', async () => { + const result = await getName.decode( + {} as ClientWithEns, + new RawContractError({ + data: encodeErrorResult({ + abi: universalResolverErrors, + errorName: 'ReverseAddressMismatch', + args: ['Nick.eth', accounts[0]], + }), + }), + { + address: '0x1234567890abcdef', + args: ['0x', 60n], + }, + { + address: accounts[0], + allowMismatch: true, + strict: false, + }, + ) + expect(result).toBeNull() + }) it('should not return unnormalised name', async () => { const tx1 = await createSubname(walletClient, { name: 'suB.with-profile.eth', diff --git a/packages/ensjs/src/functions/public/getName.ts b/packages/ensjs/src/functions/public/getName.ts index 1c8f24945..633967ba5 100644 --- a/packages/ensjs/src/functions/public/getName.ts +++ b/packages/ensjs/src/functions/public/getName.ts @@ -8,6 +8,7 @@ import { encodeFunctionData, zeroAddress, } from 'viem' +import { normalize } from 'viem/ens' import type { ClientWithEns } from '../../contracts/consts.js' import { getChainContractAddress } from '../../contracts/getChainContractAddress.js' import { @@ -24,7 +25,21 @@ import { generateFunction, } from '../../utils/generateFunction.js' import { getRevertErrorData } from '../../utils/getRevertErrorData.js' -import { normalise } from '../../utils/normalise.js' + +/** + * Checks whether a name is already normalised, without coercing it. + * + * Mirrors viem's `getEnsName` normalization enforcement (wevm/viem#4756): + * a name is only returned if it is already in its normalised form. Returns + * `false` if the name cannot be normalised (i.e. `normalize` throws). + */ +const isNormalised = (name: string): boolean => { + try { + return name === normalize(name) + } catch { + return false + } +} type GetNameCoinTypeParameters = { coinType: number @@ -134,8 +149,10 @@ const decode = async ( data: errorData, }) if (decodedError.errorName !== 'ReverseAddressMismatch') return null + const [name] = decodedError.args + if (!isNormalised(name)) return null return { - name: decodedError.args[0], + name, match: false, reverseResolverAddress: zeroAddress, resolverAddress: zeroAddress, @@ -155,9 +172,9 @@ const decode = async ( if (!unnormalisedName) return null - const normalisedName = normalise(unnormalisedName) + if (!isNormalised(unnormalisedName)) return null return { - name: normalisedName, + name: unnormalisedName, match: true, reverseResolverAddress, resolverAddress,