From 41807c42c284f4769a8198e4564999a7dd4d2e63 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Mon, 1 Jun 2026 13:41:18 +0300 Subject: [PATCH 1/5] small fixes for omnigraph api --- ...ccelerate-known-onchain-static-resolver.ts | 4 +- .../resolution/make-records-response.test.ts | 6 +- apps/ensapi/src/lib/resolution/operations.ts | 2 +- .../account-primary-names-selection.test.ts | 13 +- .../lib/resolution/chain-coin-type.ts | 1 - .../lib/resolution/primary-name-input.ts | 8 +- .../resolve-primary-name-records.ts | 2 +- .../schema/account.integration.test.ts | 119 ++++-------------- .../schema/domain.integration.test.ts | 1 - .../schema/primary-name-record.ts | 6 +- .../src/omnigraph-api/schema/records.ts | 7 +- .../src/omnigraph-api/schema/resolution.ts | 10 +- .../omnigraph-api/schema/reverse-resolve.ts | 8 +- .../src/omnigraph-api/example-queries.ts | 4 +- .../resolution/resolver-records-response.ts | 10 +- .../interpret-record-values.test.ts | 10 +- .../interpretation/interpret-record-values.ts | 28 +++-- .../src/omnigraph/generated/introspection.ts | 12 +- .../src/omnigraph/generated/schema.graphql | 29 +++-- 19 files changed, 106 insertions(+), 174 deletions(-) diff --git a/apps/ensapi/src/lib/resolution/accelerate-known-onchain-static-resolver.ts b/apps/ensapi/src/lib/resolution/accelerate-known-onchain-static-resolver.ts index 98b8d461c7..0ce3f5f85a 100644 --- a/apps/ensapi/src/lib/resolution/accelerate-known-onchain-static-resolver.ts +++ b/apps/ensapi/src/lib/resolution/accelerate-known-onchain-static-resolver.ts @@ -1,4 +1,4 @@ -import type { AccountId, Node } from "enssdk"; +import type { AccountId, Hex, Node } from "enssdk"; import type { ResolverRecordsSelection } from "@ensnode/ensnode-sdk"; @@ -50,7 +50,7 @@ function resolveOperationWithIndex(op: Operation, records: IndexedRecords): Oper case "addr": { const coinType = op.args[1]; const found = records?.addressRecords.find((r) => r.coinType === coinType); - return { ...op, result: found?.value ?? null }; + return { ...op, result: (found?.value ?? null) as Hex | null }; } case "text": { const key = op.args[1]; diff --git a/apps/ensapi/src/lib/resolution/make-records-response.test.ts b/apps/ensapi/src/lib/resolution/make-records-response.test.ts index e9c31db5c1..6022fc481d 100644 --- a/apps/ensapi/src/lib/resolution/make-records-response.test.ts +++ b/apps/ensapi/src/lib/resolution/make-records-response.test.ts @@ -18,11 +18,11 @@ describe("makeRecordsResponse", () => { it("writes resolved address records keyed by CoinType", () => { const operations = [ - { functionName: "addr", args: [node, 60n], result: "0x123" }, - { functionName: "addr", args: [node, 1001n], result: "0x456" }, + { functionName: "addr", args: [node, 60n], result: "0x1234" as Hex }, + { functionName: "addr", args: [node, 1001n], result: "0x5678" as Hex }, ] satisfies Operation[]; expect(makeRecordsResponse(operations)).toEqual({ - addresses: { 60: "0x123", 1001: "0x456" }, + addresses: { 60: "0x1234", 1001: "0x5678" }, }); }); diff --git a/apps/ensapi/src/lib/resolution/operations.ts b/apps/ensapi/src/lib/resolution/operations.ts index 88e3f1dc27..52c2b12b9b 100644 --- a/apps/ensapi/src/lib/resolution/operations.ts +++ b/apps/ensapi/src/lib/resolution/operations.ts @@ -18,7 +18,7 @@ import type { ResolverRecordsSelection } from "@ensnode/ensnode-sdk"; */ type OperationMap = { name: { args: readonly [Node]; result: InterpretedName | null }; - addr: { args: readonly [Node, bigint]; result: string | null }; + addr: { args: readonly [Node, bigint]; result: Hex | null }; text: { args: readonly [Node, string]; result: string | null }; contenthash: { args: readonly [Node]; result: Hex | null }; pubkey: { args: readonly [Node]; result: { x: Hex; y: Hex } | null }; diff --git a/apps/ensapi/src/omnigraph-api/lib/resolution/account-primary-names-selection.test.ts b/apps/ensapi/src/omnigraph-api/lib/resolution/account-primary-names-selection.test.ts index 75ddd4b0f1..187fb5e356 100644 --- a/apps/ensapi/src/omnigraph-api/lib/resolution/account-primary-names-selection.test.ts +++ b/apps/ensapi/src/omnigraph-api/lib/resolution/account-primary-names-selection.test.ts @@ -23,7 +23,7 @@ const PrimaryNameByInputType = new GraphQLInputObjectType({ name: "PrimaryNameByInput", fields: { coinType: { type: GraphQLInt }, - chain: { type: GraphQLString }, + chainName: { type: GraphQLString }, }, }); @@ -31,7 +31,7 @@ const PrimaryNamesWhereInputType = new GraphQLInputObjectType({ name: "PrimaryNamesWhereInput", fields: { coinTypes: { type: new GraphQLList(GraphQLInt) }, - chains: { type: new GraphQLList(GraphQLString) }, + chainNames: { type: new GraphQLList(GraphQLString) }, }, }); @@ -85,9 +85,9 @@ describe("buildAccountPrimaryNamesSelection", () => { expect(buildAccountPrimaryNamesSelection(info)).toEqual([60, 0]); }); - it("extracts coin type from primaryName(by: { chain: ETHEREUM })", () => { + it("extracts coin type from primaryName(by: { chainName: ETHEREUM })", () => { const info = resolveInfoForAccountResolveSubselection( - 'primaryName(by: { chain: "ETHEREUM" }) { name }', + 'primaryName(by: { chainName: "ETHEREUM" }) { name }', ); expect(buildAccountPrimaryNamesSelection(info)).toEqual([coinNameToTypeMap.eth]); }); @@ -105,14 +105,13 @@ describe("buildAccountPrimaryNamesSelection", () => { one: primaryName(by: { coinType: ${coinNameToTypeMap.btc} }) { name } two: primaryName(by: { coinType: ${coinNameToTypeMap.ltc} }) { name } three: primaryNames(where: { coinTypes: [${coinNameToTypeMap.doge}, ${coinNameToTypeMap.sol}] }) { name } - four: primaryNames(where: { chains: ["DEFAULT", "ETHEREUM", "ARBITRUM_ONE"] }) { name } - five: primaryName(by: { chain: "BASE" }) { name } + four: primaryNames(where: { chainNames: ["ETHEREUM", "ARBITRUM_ONE"] }) { name } + five: primaryName(by: { chainName: "BASE" }) { name } `); expect(buildAccountPrimaryNamesSelection(info)).toEqual([ coinNameToTypeMap.doge, coinNameToTypeMap.sol, - coinNameToTypeMap.default, coinNameToTypeMap.eth, coinNameToTypeMap.arb1, coinNameToTypeMap.btc, diff --git a/apps/ensapi/src/omnigraph-api/lib/resolution/chain-coin-type.ts b/apps/ensapi/src/omnigraph-api/lib/resolution/chain-coin-type.ts index 83f2e248cb..d6621b26f4 100644 --- a/apps/ensapi/src/omnigraph-api/lib/resolution/chain-coin-type.ts +++ b/apps/ensapi/src/omnigraph-api/lib/resolution/chain-coin-type.ts @@ -7,7 +7,6 @@ import type { CoinType } from "enssdk"; * GraphQL `ChainName` enum values. */ export const CHAIN_NAME_ENTRIES = [ - ["default", "DEFAULT"], ["eth", "ETHEREUM"], ["base", "BASE"], ["op", "OPTIMISM"], diff --git a/apps/ensapi/src/omnigraph-api/lib/resolution/primary-name-input.ts b/apps/ensapi/src/omnigraph-api/lib/resolution/primary-name-input.ts index 088ba708e4..9c0e7df02c 100644 --- a/apps/ensapi/src/omnigraph-api/lib/resolution/primary-name-input.ts +++ b/apps/ensapi/src/omnigraph-api/lib/resolution/primary-name-input.ts @@ -11,9 +11,9 @@ import type { */ export const normalizePrimaryNameByInput = (by: PrimaryNameByInputValue): CoinType => { if (by.coinType != null) return by.coinType; - if (by.chain != null) return chainNameToCoinType(by.chain); + if (by.chainName != null) return chainNameToCoinType(by.chainName); // this should never happen as the schema with `@oneOf` prevents it - throw new Error("PrimaryNameByInput must specify exactly one of coinType or chain."); + throw new Error("PrimaryNameByInput must specify exactly one of coinType or chainName."); }; /** @@ -23,7 +23,7 @@ export const normalizeAccountPrimaryNamesWhereInput = ( where: PrimaryNamesWhereInputValue, ): CoinType[] => { if (where.coinTypes != null) return where.coinTypes; - if (where.chains != null) return where.chains.map(chainNameToCoinType); + if (where.chainNames != null) return where.chainNames.map(chainNameToCoinType); // this should never happen as the schema with `@oneOf` prevents it - throw new Error("PrimaryNamesWhereInput must specify exactly one of coinTypes or chains."); + throw new Error("PrimaryNamesWhereInput must specify exactly one of coinTypes or chainNames."); }; diff --git a/apps/ensapi/src/omnigraph-api/lib/resolution/resolve-primary-name-records.ts b/apps/ensapi/src/omnigraph-api/lib/resolution/resolve-primary-name-records.ts index f8e04c462d..55a4573dd6 100644 --- a/apps/ensapi/src/omnigraph-api/lib/resolution/resolve-primary-name-records.ts +++ b/apps/ensapi/src/omnigraph-api/lib/resolution/resolve-primary-name-records.ts @@ -27,7 +27,7 @@ const toPrimaryNameRecord = ( ): PrimaryNameRecordModel => ({ address, coinType, - chain: coinTypeToChainName(coinType), + chainName: coinTypeToChainName(coinType), name, }); diff --git a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts index 55e4efac97..f34d7f4a24 100644 --- a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts @@ -1,9 +1,4 @@ -import { - DEFAULT_EVM_COIN_TYPE, - ETH_COIN_TYPE, - evmChainIdToCoinType, - type InterpretedName, -} from "enssdk"; +import { ETH_COIN_TYPE, evmChainIdToCoinType, type Hex, type InterpretedName } from "enssdk"; import { base } from "viem/chains"; import { beforeAll, describe, expect, it } from "vitest"; @@ -329,10 +324,10 @@ describe("Account.primaryName and Account.primaryNames", () => { type PrimaryNameRecordResult = { coinType: number; - chain: string | null; + chainName: string | null; name: CanonicalNameResult; resolve?: { - records?: { addresses: Array<{ coinType: number; address: string | null }> } | null; + records?: { addresses: Array<{ coinType: number; address: Hex | null }> } | null; } | null; }; @@ -363,7 +358,7 @@ describe("Account.primaryName and Account.primaryNames", () => { resolve { primaryName(by: { coinType: $coinType }) { coinType - chain + chainName name { interpreted beautified } } } @@ -375,37 +370,9 @@ describe("Account.primaryName and Account.primaryNames", () => { query AccountPrimaryNameByChain($address: Address!) { account(by: { address: $address }) { resolve { - primaryName(by: { chain: ETHEREUM }) { + primaryName(by: { chainName: ETHEREUM }) { coinType - chain - name { interpreted beautified } - } - } - } - } - `; - - const AccountPrimaryNameByDefaultChain = gql` - query AccountPrimaryNameByDefaultChain($address: Address!) { - account(by: { address: $address }) { - resolve { - primaryName(by: { chain: DEFAULT }) { - coinType - chain - name { interpreted beautified } - } - } - } - } - `; - - const AccountPrimaryNamesByDefaultChain = gql` - query AccountPrimaryNamesByDefaultChain($address: Address!) { - account(by: { address: $address }) { - resolve { - primaryNames(where: { chains: [DEFAULT] }) { - coinType - chain + chainName name { interpreted beautified } } } @@ -419,7 +386,7 @@ describe("Account.primaryName and Account.primaryNames", () => { resolve { primaryNames(where: { coinTypes: $coinTypes }) { coinType - chain + chainName name { interpreted beautified } } } @@ -431,9 +398,9 @@ describe("Account.primaryName and Account.primaryNames", () => { query AccountPrimaryNamesByChains($address: Address!) { account(by: { address: $address }) { resolve { - primaryNames(where: { chains: [ETHEREUM, BASE] }) { + primaryNames(where: { chainNames: [ETHEREUM, BASE] }) { coinType - chain + chainName name { interpreted beautified } } } @@ -447,7 +414,7 @@ describe("Account.primaryName and Account.primaryNames", () => { resolve { primaryName(by: { coinType: 0 }) { coinType - chain + chainName name { interpreted beautified } resolve { records { @@ -486,13 +453,13 @@ describe("Account.primaryName and Account.primaryNames", () => { ).resolves.toEqual({ account: { resolve: { - primaryName: { coinType: ETH_COIN_TYPE, chain: "ETHEREUM", name: TEST_ETH_NAME }, + primaryName: { coinType: ETH_COIN_TYPE, chainName: "ETHEREUM", name: TEST_ETH_NAME }, }, }, }); }); - it("resolves the same primary name by chain as by coinType", async () => { + it("resolves the same primary name by chainName as by coinType", async () => { await expect( request(AccountPrimaryNameByChain, { address: accounts.owner.address, @@ -500,45 +467,7 @@ describe("Account.primaryName and Account.primaryNames", () => { ).resolves.toEqual({ account: { resolve: { - primaryName: { coinType: ETH_COIN_TYPE, chain: "ETHEREUM", name: TEST_ETH_NAME }, - }, - }, - }); - }); - - it("accepts DEFAULT and maps it to the ENSIP-19 default EVM coin type", async () => { - await expect( - request(AccountPrimaryNameByDefaultChain, { - address: accounts.owner.address, - }), - ).resolves.toEqual({ - account: { - resolve: { - primaryName: { - coinType: DEFAULT_EVM_COIN_TYPE, - chain: "DEFAULT", - name: null, - }, - }, - }, - }); - }); - - it("resolves primary names for DEFAULT", async () => { - await expect( - request(AccountPrimaryNamesByDefaultChain, { - address: accounts.owner.address, - }), - ).resolves.toEqual({ - account: { - resolve: { - primaryNames: [ - { - coinType: DEFAULT_EVM_COIN_TYPE, - chain: "DEFAULT", - name: null, - }, - ], + primaryName: { coinType: ETH_COIN_TYPE, chainName: "ETHEREUM", name: TEST_ETH_NAME }, }, }, }); @@ -553,7 +482,7 @@ describe("Account.primaryName and Account.primaryNames", () => { ).resolves.toEqual({ account: { resolve: { - primaryName: { coinType: ETH_COIN_TYPE, chain: "ETHEREUM", name: null }, + primaryName: { coinType: ETH_COIN_TYPE, chainName: "ETHEREUM", name: null }, }, }, }); @@ -569,15 +498,15 @@ describe("Account.primaryName and Account.primaryNames", () => { account: { resolve: { primaryNames: [ - { coinType: ETH_COIN_TYPE, chain: "ETHEREUM", name: TEST_ETH_NAME }, - { coinType: BASE_COIN_TYPE, chain: "BASE", name: null }, + { coinType: ETH_COIN_TYPE, chainName: "ETHEREUM", name: TEST_ETH_NAME }, + { coinType: BASE_COIN_TYPE, chainName: "BASE", name: null }, ], }, }, }); }); - it("resolves primary names for requested chains", async () => { + it("resolves primary names for requested chainNames", async () => { await expect( request(AccountPrimaryNamesByChains, { address: accounts.owner.address, @@ -586,15 +515,15 @@ describe("Account.primaryName and Account.primaryNames", () => { account: { resolve: { primaryNames: [ - { coinType: ETH_COIN_TYPE, chain: "ETHEREUM", name: TEST_ETH_NAME }, - { coinType: BASE_COIN_TYPE, chain: "BASE", name: null }, + { coinType: ETH_COIN_TYPE, chainName: "ETHEREUM", name: TEST_ETH_NAME }, + { coinType: BASE_COIN_TYPE, chainName: "BASE", name: null }, ], }, }, }); }); - it("returns null name and chain for non-ENSIP-19 coin types", async () => { + it("returns null name and chainName for non-ENSIP-19 coin types", async () => { await expect( request(AccountPrimaryNameNonEnsip19, { address: accounts.owner.address, @@ -604,7 +533,7 @@ describe("Account.primaryName and Account.primaryNames", () => { resolve: { primaryName: { coinType: 0, - chain: null, + chainName: null, name: null, resolve: { records: null, @@ -645,14 +574,14 @@ describe("Account.primaryName and Account.primaryNames", () => { ).rejects.toThrow(); }); - it("rejects empty chains at GraphQL validation", async () => { + it("rejects empty chainNames at GraphQL validation", async () => { await expect( request( gql` - query AccountPrimaryNamesEmptyChains($address: Address!) { + query AccountPrimaryNamesEmptyChainNames($address: Address!) { account(by: { address: $address }) { resolve { - primaryNames(where: { chains: [] }) { coinType } + primaryNames(where: { chainNames: [] }) { coinType } } } } diff --git a/apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts b/apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts index 4e4a2ffd27..df9c6aa712 100644 --- a/apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts @@ -515,7 +515,6 @@ describe("Domain.records", () => { version: string | null; abi: { contentType: ContentType; data: string } | null; interfaces: Array<{ interfaceId: string; implementer: string | null }>; - // address is string (not Hex/NormalizedAddress) because non-EVM records may use non-Ethereum formats; matches GraphQL String field addresses: Array<{ coinType: CoinType; address: Hex | null }>; texts: Array<{ key: string; value: string | null }>; } | null; diff --git a/apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts b/apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts index 1148c42791..edcdef198c 100644 --- a/apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts +++ b/apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts @@ -16,7 +16,7 @@ import { ChainName } from "@/omnigraph-api/schema/resolution"; export type PrimaryNameRecordModel = { address: Address; coinType: CoinType; - chain: ChainNameValue | null; + chainName: ChainNameValue | null; name: InterpretedName | null; }; @@ -36,12 +36,12 @@ PrimaryNameRecordRef.implement({ nullable: false, resolve: (r) => r.coinType, }), - chain: t.field({ + chainName: t.field({ description: "The chain corresponding to `coinType`, or null when `coinType` is not represented in `ChainName`.", type: ChainName, nullable: true, - resolve: (r) => r.chain, + resolve: (r) => r.chainName, }), name: t.field({ description: diff --git a/apps/ensapi/src/omnigraph-api/schema/records.ts b/apps/ensapi/src/omnigraph-api/schema/records.ts index 0a4a9ccecc..a6ce1f3bb3 100644 --- a/apps/ensapi/src/omnigraph-api/schema/records.ts +++ b/apps/ensapi/src/omnigraph-api/schema/records.ts @@ -31,7 +31,7 @@ ResolvedRawTextRecordRef.implement({ /////////////////////////// export const ResolvedAddressRecordRef = builder.objectRef<{ coinType: CoinType; - address: string | null; + address: Hex | null; }>("ResolvedAddressRecord"); ResolvedAddressRecordRef.implement({ @@ -43,9 +43,10 @@ ResolvedAddressRecordRef.implement({ nullable: false, resolve: (r) => r.coinType, }), - address: t.exposeString("address", { + address: t.expose("address", { + type: "Hex", description: - "The raw address record value, or null if not set. May be any address format for the associated coin type and may require validation or preprocessing before use. Only EVM addresses are guaranteed to be NormalizedAddress values; see ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder).", + "The on-chain address record bytes as hex, or null if not set. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type.", nullable: true, }), }), diff --git a/apps/ensapi/src/omnigraph-api/schema/resolution.ts b/apps/ensapi/src/omnigraph-api/schema/resolution.ts index 5722d7eb1a..13608dfa12 100644 --- a/apps/ensapi/src/omnigraph-api/schema/resolution.ts +++ b/apps/ensapi/src/omnigraph-api/schema/resolution.ts @@ -31,7 +31,7 @@ AccelerationStatusRef.implement({ ////////////////// export const ChainName = builder.enumType("ChainName", { description: - "Primary-name chains supported by the Omnigraph API. Use `DEFAULT` for the default EVM chain.\n@see https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md for more details.", + "Primary-name chains supported by the Omnigraph API.\n@see https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md for more details.", values: CHAIN_NAME_VALUES, }); @@ -40,14 +40,14 @@ export const ChainName = builder.enumType("ChainName", { /////////////////////// export const PrimaryNameByInput = builder.inputType("PrimaryNameByInput", { description: - "Select a primary name lookup target. Exactly one of `coinType` or `chain` must be provided.", + "Select a primary name lookup target. Exactly one of `coinType` or `chainName` must be provided.", isOneOf: true, fields: (t) => ({ coinType: t.field({ type: "CoinType", description: "The ENSIP-9 coin type to resolve the primary name for.", }), - chain: t.field({ + chainName: t.field({ type: ChainName, description: "A `ChainName` to resolve the primary name for.", }), @@ -58,7 +58,7 @@ export type PrimaryNameByInputValue = typeof PrimaryNameByInput.$inferInput; export const PrimaryNamesWhereInput = builder.inputType("PrimaryNamesWhereInput", { description: - "Filter primary name lookups. Exactly one of `coinTypes` or `chains` must be provided.", + "Filter primary name lookups. Exactly one of `coinTypes` or `chainNames` must be provided.", isOneOf: true, fields: (t) => ({ coinTypes: t.field({ @@ -66,7 +66,7 @@ export const PrimaryNamesWhereInput = builder.inputType("PrimaryNamesWhereInput" description: "Coin types to resolve primary names for.", validate: { minLength: 1 }, }), - chains: t.field({ + chainNames: t.field({ type: [ChainName], description: "`ChainName` values to resolve primary names for.", validate: { minLength: 1 }, diff --git a/apps/ensapi/src/omnigraph-api/schema/reverse-resolve.ts b/apps/ensapi/src/omnigraph-api/schema/reverse-resolve.ts index 091cd48d2b..9f6f74709a 100644 --- a/apps/ensapi/src/omnigraph-api/schema/reverse-resolve.ts +++ b/apps/ensapi/src/omnigraph-api/schema/reverse-resolve.ts @@ -49,14 +49,14 @@ ReverseResolveRef.implement({ }), }), primaryName: t.field({ - description: "The primary name for this Account on a specific coin type or chain.", + description: "The primary name for this Account on a specific coin type or chain name.", type: PrimaryNameRecordRef, nullable: false, args: { by: t.arg({ type: PrimaryNameByInput, required: true, - description: "Select a coin type or chain to resolve a primary name for.", + description: "Select a coin type or chain name to resolve a primary name for.", }), }, resolve: ({ records, accelerate }, { by }) => { @@ -69,14 +69,14 @@ ReverseResolveRef.implement({ }, }), primaryNames: t.field({ - description: "Primary names for this Account on the requested coin types or chains.", + description: "Primary names for this Account on the requested coin types or chain names.", type: [PrimaryNameRecordRef], nullable: false, args: { where: t.arg({ type: PrimaryNamesWhereInput, required: true, - description: "Select coin types or chains to resolve primary names for.", + description: "Select coin types or chain names to resolve primary names for.", }), }, resolve: ({ records, accelerate }, { where }) => { diff --git a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts index ca8f741064..de2675e9e3 100644 --- a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts +++ b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts @@ -341,9 +341,9 @@ query AccountPrimaryNames($address: Address!) { account(by: { address: $address }) { address resolve { - primaryNames(where: { chains: [ETHEREUM, BASE] }) { + primaryNames(where: { chainNames: [ETHEREUM, BASE] }) { coinType - chain + chainName name { interpreted beautified } resolve { records { diff --git a/packages/ensnode-sdk/src/resolution/resolver-records-response.ts b/packages/ensnode-sdk/src/resolution/resolver-records-response.ts index e0d0d77f72..0d92177b07 100644 --- a/packages/ensnode-sdk/src/resolution/resolver-records-response.ts +++ b/packages/ensnode-sdk/src/resolution/resolver-records-response.ts @@ -18,11 +18,9 @@ export type ResolverRecordsResponseBase = { * Address records, keyed by CoinType. * Value is null if no record for the specified CoinType is set. * - * NOTE: ENS resolver address records can store arbitrary string values, - * including non-EVM addresses — always validate the record value against - * the format your application expects. + * Values are the on-chain record bytes as hex. */ - addresses: Record; + addresses: Record; /** * Text records, keyed by key. @@ -82,7 +80,7 @@ export type ResolverRecordsResponseBase = { * // results in the following type * type Response = { * readonly name: Name | null; - * readonly addresses: Record<"60", string | null>; + * readonly addresses: Record<"60", Hex | null>; * readonly texts: Record<"avatar" | "com.twitter", string | null>; * } * ``` @@ -92,7 +90,7 @@ export type ResolverRecordsResponse : K extends "texts" ? Record diff --git a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts index ddfe16ce05..41fd6656af 100644 --- a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts +++ b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts @@ -31,8 +31,14 @@ describe("interpretAddressRecordValue", () => { expect(interpretAddressRecordValue("0x")).toBeNull(); }); - it("returns as-is for non-EVM address", () => { - expect(interpretAddressRecordValue("someNonEvmAddress")).toBe("someNonEvmAddress"); + it("returns null for non-hex values", () => { + expect(interpretAddressRecordValue("someNonHexString")).toBeNull(); + }); + + it("returns lowercase hex for non-EVM address bytes", () => { + expect(interpretAddressRecordValue(`0x${"05".repeat(25).toUpperCase()}`)).toBe( + `0x${"05".repeat(25)}`, + ); }); it("returns null for zeroAddress", () => { diff --git a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts index 6e62c712b1..e48817420a 100644 --- a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts +++ b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts @@ -1,6 +1,6 @@ -import type { InterpretedName, LiteralName } from "enssdk"; +import type { Hex, InterpretedName, LiteralName } from "enssdk"; import { isInterpretedName, toNormalizedAddress } from "enssdk"; -import { isAddress, isAddressEqual, zeroAddress } from "viem"; +import { isAddress, isAddressEqual, isHex, zeroAddress } from "viem"; import { hasNullByte } from "../null-bytes"; @@ -29,7 +29,7 @@ export function interpretNameRecordValue(value: LiteralName): InterpretedName | } /** - * Interprets an address record value string and returns null if the value is interpreted as a deletion. + * Interprets an address record value and returns null if the value is interpreted as a deletion. * * The interpreted record value is either: * a) null, representing a non-existent or deletion of the record, or @@ -37,14 +37,13 @@ export function interpretNameRecordValue(value: LiteralName): InterpretedName | * ii. empty string * iii. empty hex (0x) * iv. zeroAddress - * b) an address record value that - * i. does not contain null bytes - * ii. (if is an EVM address) is lowercase + * v. not valid hex + * b) the on-chain record bytes as hex * * @param value - The address record value to interpret. - * @returns The interpreted address string or null if deleted. + * @returns The interpreted address bytes as hex or null if deleted. */ -export function interpretAddressRecordValue(value: string): string | null { +export function interpretAddressRecordValue(value: string): Hex | null { // TODO(null-bytes): store null bytes correctly — for now, interpret as deletion if (hasNullByte(value)) return null; @@ -54,14 +53,17 @@ export function interpretAddressRecordValue(value: string): string | null { // interpret empty bytes as deletion of address record if (value === "0x") return null; - // if it's not an EVM address, return as-is - if (!isAddress(value, { strict: false })) return value; + if (!isHex(value)) return null; + + const hex = value.toLowerCase() as Hex; // interpret zeroAddress as deletion - if (isAddressEqual(value, zeroAddress)) return null; + if (isAddress(hex, { strict: false }) && isAddressEqual(hex, zeroAddress)) return null; + + // normalize EVM addresses to lowercase NormalizedAddress hex + if (isAddress(hex, { strict: false })) return toNormalizedAddress(hex); - // otherwise normalize and return - return toNormalizedAddress(value); + return hex; } /** diff --git a/packages/enssdk/src/omnigraph/generated/introspection.ts b/packages/enssdk/src/omnigraph/generated/introspection.ts index b51975a7d0..2092a9769d 100644 --- a/packages/enssdk/src/omnigraph/generated/introspection.ts +++ b/packages/enssdk/src/omnigraph/generated/introspection.ts @@ -1144,10 +1144,6 @@ const introspection = { "name": "BASE", "isDeprecated": false }, - { - "name": "DEFAULT", - "isDeprecated": false - }, { "name": "ETHEREUM", "isDeprecated": false @@ -4832,7 +4828,7 @@ const introspection = { "name": "PrimaryNameByInput", "inputFields": [ { - "name": "chain", + "name": "chainName", "type": { "kind": "ENUM", "name": "ChainName" @@ -4853,7 +4849,7 @@ const introspection = { "name": "PrimaryNameRecord", "fields": [ { - "name": "chain", + "name": "chainName", "type": { "kind": "ENUM", "name": "ChainName" @@ -4902,7 +4898,7 @@ const introspection = { "name": "PrimaryNamesWhereInput", "inputFields": [ { - "name": "chains", + "name": "chainNames", "type": { "kind": "LIST", "ofType": { @@ -6128,7 +6124,7 @@ const introspection = { "name": "address", "type": { "kind": "SCALAR", - "name": "String" + "name": "Hex" }, "args": [], "isDeprecated": false diff --git a/packages/enssdk/src/omnigraph/generated/schema.graphql b/packages/enssdk/src/omnigraph/generated/schema.graphql index 27d0a5a62d..67c904be57 100644 --- a/packages/enssdk/src/omnigraph/generated/schema.graphql +++ b/packages/enssdk/src/omnigraph/generated/schema.graphql @@ -254,13 +254,12 @@ type CanonicalName { scalar ChainId """ -Primary-name chains supported by the Omnigraph API. Use `DEFAULT` for the default EVM chain. +Primary-name chains supported by the Omnigraph API. @see https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md for more details. """ enum ChainName { ARBITRUM_ONE BASE - DEFAULT ETHEREUM LINEA OPTIMISM @@ -1174,11 +1173,11 @@ type PermissionsUserEventsConnectionEdge { scalar PermissionsUserId """ -Select a primary name lookup target. Exactly one of `coinType` or `chain` must be provided. +Select a primary name lookup target. Exactly one of `coinType` or `chainName` must be provided. """ input PrimaryNameByInput @oneOf { """A `ChainName` to resolve the primary name for.""" - chain: ChainName + chainName: ChainName """The ENSIP-9 coin type to resolve the primary name for.""" coinType: CoinType @@ -1189,7 +1188,7 @@ type PrimaryNameRecord { """ The chain corresponding to `coinType`, or null when `coinType` is not represented in `ChainName`. """ - chain: ChainName + chainName: ChainName """The canonical ENSIP-9 coin type for this primary name lookup.""" coinType: CoinType! @@ -1204,11 +1203,11 @@ type PrimaryNameRecord { } """ -Filter primary name lookups. Exactly one of `coinTypes` or `chains` must be provided. +Filter primary name lookups. Exactly one of `coinTypes` or `chainNames` must be provided. """ input PrimaryNamesWhereInput @oneOf { """`ChainName` values to resolve primary names for.""" - chains: [ChainName!] + chainNames: [ChainName!] """Coin types to resolve primary names for.""" coinTypes: [CoinType!] @@ -1487,9 +1486,9 @@ type ResolvedAbiRecord { """A resolved address record for an ENS name.""" type ResolvedAddressRecord { """ - The raw address record value, or null if not set. May be any address format for the associated coin type and may require validation or preprocessing before use. Only EVM addresses are guaranteed to be NormalizedAddress values; see ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder). + The on-chain address record bytes as hex, or null if not set. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type. """ - address: String + address: Hex """The coin type for this address record.""" coinType: CoinType! @@ -1677,15 +1676,19 @@ type ReverseResolve { """ acceleration: AccelerationStatus! - """The primary name for this Account on a specific coin type or chain.""" + """ + The primary name for this Account on a specific coin type or chain name. + """ primaryName( - """Select a coin type or chain to resolve a primary name for.""" + """Select a coin type or chain name to resolve a primary name for.""" by: PrimaryNameByInput! ): PrimaryNameRecord! - """Primary names for this Account on the requested coin types or chains.""" + """ + Primary names for this Account on the requested coin types or chain names. + """ primaryNames( - """Select coin types or chains to resolve primary names for.""" + """Select coin types or chain names to resolve primary names for.""" where: PrimaryNamesWhereInput! ): [PrimaryNameRecord!]! From 2e4a1969c5529a91e5b7c8d104917b3717113b2a Mon Sep 17 00:00:00 2001 From: sevenzing Date: Mon, 1 Jun 2026 13:51:10 +0300 Subject: [PATCH 2/5] docs(changeset): omnigraph: rename chain to chainName, remove DEFAULT chain, add Hex type to address. --- .changeset/neat-dingos-attend.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/neat-dingos-attend.md diff --git a/.changeset/neat-dingos-attend.md b/.changeset/neat-dingos-attend.md new file mode 100644 index 0000000000..8b5a8c1b5f --- /dev/null +++ b/.changeset/neat-dingos-attend.md @@ -0,0 +1,7 @@ +--- +"@ensnode/ensnode-sdk": patch +"enssdk": patch +"ensapi": patch +--- + +omnigraph: rename chain to chainName, remove DEFAULT chain, add Hex type to address. From 3e4c2097de30a7dbf631beb6e269c919929fff79 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Mon, 1 Jun 2026 14:10:14 +0300 Subject: [PATCH 3/5] fixing tests --- .../src/shared/interpretation/interpret-record-values.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts index 41fd6656af..b7b12c647e 100644 --- a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts +++ b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.test.ts @@ -36,8 +36,8 @@ describe("interpretAddressRecordValue", () => { }); it("returns lowercase hex for non-EVM address bytes", () => { - expect(interpretAddressRecordValue(`0x${"05".repeat(25).toUpperCase()}`)).toBe( - `0x${"05".repeat(25)}`, + expect(interpretAddressRecordValue(`0x${"05ab".repeat(20).toUpperCase()}`)).toBe( + `0x${"05ab".repeat(20)}`, ); }); From 7d14b8919afbd8f99a0d21765870419cc6096b80 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Mon, 1 Jun 2026 16:38:01 +0300 Subject: [PATCH 4/5] apply PR suggestions --- .changeset/neat-dingos-attend.md | 7 ------- .changeset/omnigraph-resolution-api.md | 2 +- apps/ensapi/src/omnigraph-api/schema/records.ts | 2 +- apps/ensapi/src/omnigraph-api/schema/resolution.ts | 2 +- .../src/resolution/resolver-records-response.ts | 3 ++- .../src/shared/interpretation/interpret-record-values.ts | 5 +---- packages/enssdk/src/omnigraph/generated/schema.graphql | 5 ++--- 7 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 .changeset/neat-dingos-attend.md diff --git a/.changeset/neat-dingos-attend.md b/.changeset/neat-dingos-attend.md deleted file mode 100644 index 8b5a8c1b5f..0000000000 --- a/.changeset/neat-dingos-attend.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@ensnode/ensnode-sdk": patch -"enssdk": patch -"ensapi": patch ---- - -omnigraph: rename chain to chainName, remove DEFAULT chain, add Hex type to address. diff --git a/.changeset/omnigraph-resolution-api.md b/.changeset/omnigraph-resolution-api.md index 8c6f14748d..06316634ca 100644 --- a/.changeset/omnigraph-resolution-api.md +++ b/.changeset/omnigraph-resolution-api.md @@ -5,5 +5,5 @@ Changes related to **Omnigraph**: - add `Domain.resolve { records, trace, acceleration, profile? }` for forward resolution driven by the GraphQL selection set -- add `Account.resolve { primaryName(by: ...), primaryNames(where: ...) }` for reverse (ENSIP-19 primary name) resolution with `@oneOf` inputs (`coinType`/`chain`, `coinTypes`/`chains`) +- add `Account.resolve { primaryName(by: ...), primaryNames(where: ...) }` for reverse (ENSIP-19 primary name) resolution with `@oneOf` inputs (`coinType`/`chainNames`, `coinTypes`/`chainNames`) - add `PrimaryNameRecord.resolve { records, ... }` for forward resolution of the resolved primary name diff --git a/apps/ensapi/src/omnigraph-api/schema/records.ts b/apps/ensapi/src/omnigraph-api/schema/records.ts index a6ce1f3bb3..6b984e4dd8 100644 --- a/apps/ensapi/src/omnigraph-api/schema/records.ts +++ b/apps/ensapi/src/omnigraph-api/schema/records.ts @@ -46,7 +46,7 @@ ResolvedAddressRecordRef.implement({ address: t.expose("address", { type: "Hex", description: - "The on-chain address record bytes as hex, or null if not set. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type.", + 'The "raw" resolved address record as hex, or null if not set. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type. May be a hex value representing 0 or more bytes. There is no guarantee that an EVM coinType returns an address value of any particular byte length.', nullable: true, }), }), diff --git a/apps/ensapi/src/omnigraph-api/schema/resolution.ts b/apps/ensapi/src/omnigraph-api/schema/resolution.ts index 13608dfa12..4ddf1fddaa 100644 --- a/apps/ensapi/src/omnigraph-api/schema/resolution.ts +++ b/apps/ensapi/src/omnigraph-api/schema/resolution.ts @@ -31,7 +31,7 @@ AccelerationStatusRef.implement({ ////////////////// export const ChainName = builder.enumType("ChainName", { description: - "Primary-name chains supported by the Omnigraph API.\n@see https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md for more details.", + "The names of chains that the Omnigraph API supports identifying by name as a syntactic convenience. The Omnigraph API supports identification of additional chains beyond this list, but those chains must be identified through other mechanisms such as `coinType` or `chainId`.", values: CHAIN_NAME_VALUES, }); diff --git a/packages/ensnode-sdk/src/resolution/resolver-records-response.ts b/packages/ensnode-sdk/src/resolution/resolver-records-response.ts index 0d92177b07..16acb3e449 100644 --- a/packages/ensnode-sdk/src/resolution/resolver-records-response.ts +++ b/packages/ensnode-sdk/src/resolution/resolver-records-response.ts @@ -18,7 +18,8 @@ export type ResolverRecordsResponseBase = { * Address records, keyed by CoinType. * Value is null if no record for the specified CoinType is set. * - * Values are the on-chain record bytes as hex. + * Values are the "raw" resolved address record as hex. May be a hex value representing 0 or more bytes. + * There is no guarantee that an EVM coinType returns an address value of any particular byte length. */ addresses: Record; diff --git a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts index e48817420a..8f6719b07a 100644 --- a/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts +++ b/packages/ensnode-sdk/src/shared/interpretation/interpret-record-values.ts @@ -1,5 +1,5 @@ import type { Hex, InterpretedName, LiteralName } from "enssdk"; -import { isInterpretedName, toNormalizedAddress } from "enssdk"; +import { isInterpretedName } from "enssdk"; import { isAddress, isAddressEqual, isHex, zeroAddress } from "viem"; import { hasNullByte } from "../null-bytes"; @@ -60,9 +60,6 @@ export function interpretAddressRecordValue(value: string): Hex | null { // interpret zeroAddress as deletion if (isAddress(hex, { strict: false }) && isAddressEqual(hex, zeroAddress)) return null; - // normalize EVM addresses to lowercase NormalizedAddress hex - if (isAddress(hex, { strict: false })) return toNormalizedAddress(hex); - return hex; } diff --git a/packages/enssdk/src/omnigraph/generated/schema.graphql b/packages/enssdk/src/omnigraph/generated/schema.graphql index 67c904be57..de0e279676 100644 --- a/packages/enssdk/src/omnigraph/generated/schema.graphql +++ b/packages/enssdk/src/omnigraph/generated/schema.graphql @@ -254,8 +254,7 @@ type CanonicalName { scalar ChainId """ -Primary-name chains supported by the Omnigraph API. -@see https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md for more details. +The names of chains that the Omnigraph API supports identifying by name as a syntactic convenience. The Omnigraph API supports identification of additional chains beyond this list, but those chains must be identified through other mechanisms such as `coinType` or `chainId`. """ enum ChainName { ARBITRUM_ONE @@ -1486,7 +1485,7 @@ type ResolvedAbiRecord { """A resolved address record for an ENS name.""" type ResolvedAddressRecord { """ - The on-chain address record bytes as hex, or null if not set. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type. + The "raw" resolved address record as hex, or null if not set. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type. May be a hex value representing 0 or more bytes. There is no guarantee that an EVM coinType returns an address value of any particular byte length. """ address: Hex From c0bd2e9d68ddd94cb82bea8154002b7046de2fcc Mon Sep 17 00:00:00 2001 From: sevenzing Date: Mon, 1 Jun 2026 16:44:33 +0300 Subject: [PATCH 5/5] fix changeset --- .changeset/omnigraph-resolution-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/omnigraph-resolution-api.md b/.changeset/omnigraph-resolution-api.md index 06316634ca..6312e6f99d 100644 --- a/.changeset/omnigraph-resolution-api.md +++ b/.changeset/omnigraph-resolution-api.md @@ -5,5 +5,5 @@ Changes related to **Omnigraph**: - add `Domain.resolve { records, trace, acceleration, profile? }` for forward resolution driven by the GraphQL selection set -- add `Account.resolve { primaryName(by: ...), primaryNames(where: ...) }` for reverse (ENSIP-19 primary name) resolution with `@oneOf` inputs (`coinType`/`chainNames`, `coinTypes`/`chainNames`) +- add `Account.resolve { primaryName(by: ...), primaryNames(where: ...) }` for reverse (ENSIP-19 primary name) resolution with `@oneOf` inputs (`coinType`/`chainName`, `coinTypes`/`chainNames`) - add `PrimaryNameRecord.resolve { records, ... }` for forward resolution of the resolved primary name