From 7ccd00b7b446ebb698d111742e789e6f5a313ada Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 1 Jun 2026 10:52:21 -0500 Subject: [PATCH 1/6] fix: remove unnecessary local resolver --- packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts b/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts index 665f56c68..e901e46be 100644 --- a/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts +++ b/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts @@ -37,12 +37,6 @@ export const omnigraphCacheExchange = cacheExchange({ return typeof key === "string" ? key : null; }, - // ResolvedRecords are keyable by just `id` - ResolvedRecords: (data) => { - const key = data.id; - return typeof key === "string" ? key : null; - }, - // These entities are Embedded Data and don't have a relevant key Label: EMBEDDED_DATA, WrappedBaseRegistrarRegistration: EMBEDDED_DATA, From 8838b24c454fd89b3f3313d67a3616b2f158dae8 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 1 Jun 2026 11:09:42 -0500 Subject: [PATCH 2/6] fix: remove id from ResolvedRecords and make it embedded data --- apps/ensapi/src/omnigraph-api/schema/records.ts | 6 ------ packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts | 1 + packages/enssdk/src/omnigraph/generated/schema.graphql | 5 ----- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/apps/ensapi/src/omnigraph-api/schema/records.ts b/apps/ensapi/src/omnigraph-api/schema/records.ts index 6b984e4dd..bf8c25972 100644 --- a/apps/ensapi/src/omnigraph-api/schema/records.ts +++ b/apps/ensapi/src/omnigraph-api/schema/records.ts @@ -132,12 +132,6 @@ export const ResolvedRecordsRef = builder.objectRef("Resol ResolvedRecordsRef.implement({ description: "Records resolved for a specific ENS name via the ENS protocol.", fields: (t) => ({ - id: t.field({ - description: "Stable cache key for these records: the InterpretedName used to resolve them.", - type: "InterpretedName", - nullable: false, - resolve: (parent) => parent.id, - }), reverseName: t.string({ description: "The `name` record value used in Reverse Resolution (ENSIP-19), or null if not set. To reduce a common point of developer confusion the Omnigraph API represents this as the `reverseName` rather than the `name` record which is what this field actually resolves to onchain.", diff --git a/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts b/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts index e901e46be..c4a291943 100644 --- a/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts +++ b/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts @@ -45,6 +45,7 @@ export const omnigraphCacheExchange = cacheExchange({ DomainResolver: EMBEDDED_DATA, ForwardResolve: EMBEDDED_DATA, ReverseResolve: EMBEDDED_DATA, + ResolvedRecords: EMBEDDED_DATA, ResolutionStatus: EMBEDDED_DATA, PrimaryNameRecord: EMBEDDED_DATA, AccelerationStatus: EMBEDDED_DATA, diff --git a/packages/enssdk/src/omnigraph/generated/schema.graphql b/packages/enssdk/src/omnigraph/generated/schema.graphql index de0e27967..a0a4fe39d 100644 --- a/packages/enssdk/src/omnigraph/generated/schema.graphql +++ b/packages/enssdk/src/omnigraph/generated/schema.graphql @@ -1542,11 +1542,6 @@ type ResolvedRecords { """The IDNSZoneResolver zonehash raw bytes, or null if not set.""" dnszonehash: Hex - """ - Stable cache key for these records: the InterpretedName used to resolve them. - """ - id: InterpretedName! - """Resolved ERC-165 interface implementer records for the requested ids.""" interfaces( """ERC-165 interface ids to resolve (4-byte hex selectors).""" From faa399b9fd5389abcf761d99f1399eaa76e150af Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 1 Jun 2026 11:29:34 -0500 Subject: [PATCH 3/6] fix: hex handling --- ...ccelerate-known-onchain-static-resolver.ts | 4 +-- .../src/lib/resolution/execute-operations.ts | 2 +- .../resolution/make-records-response.test.ts | 32 +++++++++---------- .../src/omnigraph-api/schema/records.ts | 2 +- .../protocol-acceleration.schema.ts | 2 +- .../interpret-record-values.test.ts | 13 ++++---- .../interpretation/interpret-record-values.ts | 31 ++++++++---------- .../src/omnigraph/generated/introspection.ts | 12 ------- .../src/omnigraph/generated/schema.graphql | 2 +- 9 files changed, 43 insertions(+), 57 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 0ce3f5f85..98b8d461c 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, Hex, Node } from "enssdk"; +import type { AccountId, 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) as Hex | null }; + return { ...op, result: found?.value ?? null }; } case "text": { const key = op.args[1]; diff --git a/apps/ensapi/src/lib/resolution/execute-operations.ts b/apps/ensapi/src/lib/resolution/execute-operations.ts index 8c87e82a3..6b91ea4dd 100644 --- a/apps/ensapi/src/lib/resolution/execute-operations.ts +++ b/apps/ensapi/src/lib/resolution/execute-operations.ts @@ -144,7 +144,7 @@ export function interpretOperationWithRawResult(call: Operation, raw: unknown): case "name": return { ...call, result: interpretNameRecordValue(asLiteralName(raw as string)) }; case "addr": - return { ...call, result: interpretAddressRecordValue(raw as string) }; + return { ...call, result: interpretAddressRecordValue(raw as Hex) }; case "text": return { ...call, result: interpretTextRecordValue(raw as string) }; case "contenthash": 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 6022fc481..e7f462c8c 100644 --- a/apps/ensapi/src/lib/resolution/make-records-response.test.ts +++ b/apps/ensapi/src/lib/resolution/make-records-response.test.ts @@ -1,4 +1,4 @@ -import { asInterpretedName, type CoinType, type Hex, type InterfaceId } from "enssdk"; +import { asInterpretedName, type CoinType, type InterfaceId } from "enssdk"; import { describe, expect, it } from "vitest"; import type { ResolverRecordsSelection } from "@ensnode/ensnode-sdk"; @@ -6,20 +6,20 @@ import type { ResolverRecordsSelection } from "@ensnode/ensnode-sdk"; import { makeRecordsResponse } from "./make-records-response"; import { makeOperations, type Operation } from "./operations"; -describe("makeRecordsResponse", () => { - const node = `0x${"00".repeat(32)}` as Hex; +const ZERO_NODE = `0x${"00".repeat(32)}` as const; +describe("makeRecordsResponse", () => { it("writes a resolved name record", () => { const operations = [ - { functionName: "name", args: [node], result: asInterpretedName("test.eth") }, + { functionName: "name", args: [ZERO_NODE], result: asInterpretedName("test.eth") }, ] satisfies Operation[]; expect(makeRecordsResponse(operations)).toEqual({ name: "test.eth" }); }); it("writes resolved address records keyed by CoinType", () => { const operations = [ - { functionName: "addr", args: [node, 60n], result: "0x1234" as Hex }, - { functionName: "addr", args: [node, 1001n], result: "0x5678" as Hex }, + { functionName: "addr", args: [ZERO_NODE, 60n], result: "0x1234" }, + { functionName: "addr", args: [ZERO_NODE, 1001n], result: "0x5678" }, ] satisfies Operation[]; expect(makeRecordsResponse(operations)).toEqual({ addresses: { 60: "0x1234", 1001: "0x5678" }, @@ -28,8 +28,8 @@ describe("makeRecordsResponse", () => { it("writes resolved text records keyed by key", () => { const operations = [ - { functionName: "text", args: [node, "com.twitter"], result: "@test" }, - { functionName: "text", args: [node, "avatar"], result: "ipfs://..." }, + { functionName: "text", args: [ZERO_NODE, "com.twitter"], result: "@test" }, + { functionName: "text", args: [ZERO_NODE, "avatar"], result: "ipfs://..." }, ] satisfies Operation[]; expect(makeRecordsResponse(operations)).toEqual({ texts: { "com.twitter": "@test", avatar: "ipfs://..." }, @@ -38,14 +38,14 @@ describe("makeRecordsResponse", () => { it("writes resolved contenthash / pubkey / dnszonehash / version", () => { const operations = [ - { functionName: "contenthash", args: [node], result: "0xdeadbeef" as Hex }, + { functionName: "contenthash", args: [ZERO_NODE], result: "0xdeadbeef" }, { functionName: "pubkey", - args: [node], - result: { x: `0x${"11".repeat(32)}` as Hex, y: `0x${"22".repeat(32)}` as Hex }, + args: [ZERO_NODE], + result: { x: `0x${"11".repeat(32)}`, y: `0x${"22".repeat(32)}` }, }, - { functionName: "zonehash", args: [node], result: "0xcafe" as Hex }, - { functionName: "recordVersions", args: [node], result: 7n }, + { functionName: "zonehash", args: [ZERO_NODE], result: "0xcafe" }, + { functionName: "recordVersions", args: [ZERO_NODE], result: 7n }, ] satisfies Operation[]; expect(makeRecordsResponse(operations)).toEqual({ contenthash: "0xdeadbeef", @@ -59,8 +59,8 @@ describe("makeRecordsResponse", () => { const operations = [ { functionName: "ABI", - args: [node, 1n], - result: { contentType: 1n, data: "0xabcd" as Hex }, + args: [ZERO_NODE, 1n], + result: { contentType: 1n, data: "0xabcd" }, }, ] satisfies Operation[]; expect(makeRecordsResponse(operations)).toEqual({ @@ -82,7 +82,7 @@ describe("makeRecordsResponse", () => { interfaces: [id], }; // operations generated from selection, every entry unresolved - const operations = makeOperations(node, selection); + const operations = makeOperations(ZERO_NODE, selection); expect(makeRecordsResponse(operations)).toEqual({ name: null, addresses: { 60: null }, diff --git a/apps/ensapi/src/omnigraph-api/schema/records.ts b/apps/ensapi/src/omnigraph-api/schema/records.ts index bf8c25972..b2b6d0271 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 "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.', + '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. Guaranteed to be at least one byte of hex data. There is no guarantee that an EVM CoinType returns an address value of any particular byte length.', nullable: true, }), }), diff --git a/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts b/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts index 5bfe03815..177309a62 100644 --- a/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts +++ b/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts @@ -202,7 +202,7 @@ export const resolverAddressRecord = onchainTable( * The value of this field is interpreted by `interpretAddressRecordValue` — see its implementation * for additional context and specific guarantees. */ - value: t.text().notNull(), + value: t.hex().notNull(), }), (t) => ({ pk: primaryKey({ columns: [t.chainId, t.address, t.node, t.coinType] }), 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 b7b12c647..f772e9ca9 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 @@ -1,4 +1,5 @@ import { asLiteralName } from "enssdk"; +import { type Hex, toHex } from "viem"; import { describe, expect, it } from "vitest"; import { @@ -23,16 +24,16 @@ describe("interpretNameRecordValue", () => { }); describe("interpretAddressRecordValue", () => { - it("returns null for empty string", () => { - expect(interpretAddressRecordValue("")).toBeNull(); + it("returns null for empty string (type override)", () => { + expect(interpretAddressRecordValue("" as Hex)).toBeNull(); }); it("returns null for '0x'", () => { expect(interpretAddressRecordValue("0x")).toBeNull(); }); - it("returns null for non-hex values", () => { - expect(interpretAddressRecordValue("someNonHexString")).toBeNull(); + it("returns null for non-hex values (type override)", () => { + expect(interpretAddressRecordValue("someNonHexString" as Hex)).toBeNull(); }); it("returns lowercase hex for non-EVM address bytes", () => { @@ -54,8 +55,8 @@ describe("interpretAddressRecordValue", () => { ); }); - it("interprets null-byte-containing string as deletion", () => { - expect(interpretAddressRecordValue("example\u0000")).toBeNull(); + it("represents null-bytes correctly", () => { + expect(interpretAddressRecordValue(toHex("\u0000"))).toBe("0x00"); }); }); 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 8f6719b07..5d67bf42a 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 { Hex, InterpretedName, LiteralName } from "enssdk"; import { isInterpretedName } from "enssdk"; -import { isAddress, isAddressEqual, isHex, zeroAddress } from "viem"; +import { isHex, zeroAddress } from "viem"; import { hasNullByte } from "../null-bytes"; @@ -33,34 +33,31 @@ export function interpretNameRecordValue(value: LiteralName): InterpretedName | * * The interpreted record value is either: * a) null, representing a non-existent or deletion of the record, or - * i. contains null bytes - * ii. empty string - * iii. empty hex (0x) - * iv. zeroAddress - * v. not valid hex + * i. empty hex (0x) + * ii. not valid hex + * iii. zeroAddress * b) the on-chain record bytes as hex * * @param value - The address record value to interpret. * @returns The interpreted address bytes as hex or null if deleted. */ -export function interpretAddressRecordValue(value: string): Hex | null { - // TODO(null-bytes): store null bytes correctly — for now, interpret as deletion - if (hasNullByte(value)) return null; - - // interpret empty string as deletion of address record - if (value === "") return null; - +export function interpretAddressRecordValue(value: Hex): Hex | null { // interpret empty bytes as deletion of address record if (value === "0x") return null; - if (!isHex(value)) return null; + // interpret malformed hex as non-existence of address record + if (!isHex(value, { strict: true })) return null; - const hex = value.toLowerCase() as Hex; + // normalize to lowercase + const normalized = value.toLowerCase() as Hex; // interpret zeroAddress as deletion - if (isAddress(hex, { strict: false }) && isAddressEqual(hex, zeroAddress)) return null; + // NOTE: direct string compare is ok here because both zeroAddress and `normalized` are + // normalized to lowercase 0x-prefixed hex strings + if (normalized === zeroAddress) return null; - return hex; + // otherwise return the address record bytes as-is + return normalized; } /** diff --git a/packages/enssdk/src/omnigraph/generated/introspection.ts b/packages/enssdk/src/omnigraph/generated/introspection.ts index 2092a9769..1d5f45d22 100644 --- a/packages/enssdk/src/omnigraph/generated/introspection.ts +++ b/packages/enssdk/src/omnigraph/generated/introspection.ts @@ -6308,18 +6308,6 @@ const introspection = { "args": [], "isDeprecated": false }, - { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "InterpretedName" - } - }, - "args": [], - "isDeprecated": false - }, { "name": "interfaces", "type": { diff --git a/packages/enssdk/src/omnigraph/generated/schema.graphql b/packages/enssdk/src/omnigraph/generated/schema.graphql index a0a4fe39d..e7973262a 100644 --- a/packages/enssdk/src/omnigraph/generated/schema.graphql +++ b/packages/enssdk/src/omnigraph/generated/schema.graphql @@ -1485,7 +1485,7 @@ type ResolvedAbiRecord { """A resolved address record for an ENS name.""" type ResolvedAddressRecord { """ - 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. + 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. Guaranteed to be at least one byte of hex data. There is no guarantee that an EVM CoinType returns an address value of any particular byte length. """ address: Hex From 3426141e365f18691dcd2ae7bf280d04248f5d86 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 1 Jun 2026 11:32:36 -0500 Subject: [PATCH 4/6] fix: expand expose shorthands --- apps/ensapi/src/omnigraph-api/schema/account-id.ts | 4 ++-- apps/ensapi/src/omnigraph-api/schema/records.ts | 11 ++++++++--- apps/ensapi/src/omnigraph-api/schema/resolution.ts | 8 ++++++-- .../src/omnigraph-api/schema/resolver-records.ts | 3 ++- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/ensapi/src/omnigraph-api/schema/account-id.ts b/apps/ensapi/src/omnigraph-api/schema/account-id.ts index 2235dd696..cf49a14c8 100644 --- a/apps/ensapi/src/omnigraph-api/schema/account-id.ts +++ b/apps/ensapi/src/omnigraph-api/schema/account-id.ts @@ -6,8 +6,8 @@ export const AccountIdRef = builder.objectRef("AccountId"); AccountIdRef.implement({ description: "A CAIP-10 Account ID including chainId and address.", fields: (t) => ({ - chainId: t.expose("chainId", { type: "ChainId", nullable: false }), - address: t.expose("address", { type: "Address", nullable: false }), + chainId: t.field({ type: "ChainId", nullable: false, resolve: (parent) => parent.chainId }), + address: t.field({ type: "Address", nullable: false, resolve: (parent) => parent.address }), }), }); diff --git a/apps/ensapi/src/omnigraph-api/schema/records.ts b/apps/ensapi/src/omnigraph-api/schema/records.ts index b2b6d0271..c9ed66d89 100644 --- a/apps/ensapi/src/omnigraph-api/schema/records.ts +++ b/apps/ensapi/src/omnigraph-api/schema/records.ts @@ -14,14 +14,18 @@ ResolvedRawTextRecordRef.implement({ description: "A resolved 'raw' text record for an ENS name. Value is any possible string and may require additional validation or preprocessing before use.", fields: (t) => ({ - key: t.exposeString("key", { + key: t.field({ + type: "String", description: "The text record key.", nullable: false, + resolve: (r) => r.key, }), - value: t.exposeString("value", { + value: t.field({ + type: "String", description: "The 'raw' text record value, or null if not set. Value is any possible string and may require additional validation or preprocessing before use.", nullable: true, + resolve: (r) => r.value, }), }), }); @@ -43,11 +47,12 @@ ResolvedAddressRecordRef.implement({ nullable: false, resolve: (r) => r.coinType, }), - address: t.expose("address", { + address: t.field({ type: "Hex", description: '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. Guaranteed to be at least one byte of hex data. There is no guarantee that an EVM CoinType returns an address value of any particular byte length.', nullable: true, + resolve: (r) => r.address, }), }), }); diff --git a/apps/ensapi/src/omnigraph-api/schema/resolution.ts b/apps/ensapi/src/omnigraph-api/schema/resolution.ts index 4ddf1fdda..f62ce013a 100644 --- a/apps/ensapi/src/omnigraph-api/schema/resolution.ts +++ b/apps/ensapi/src/omnigraph-api/schema/resolution.ts @@ -15,13 +15,17 @@ export const AccelerationStatusRef = AccelerationStatusRef.implement({ description: "Execution status metadata for a resolver strategy.", fields: (t) => ({ - requested: t.exposeBoolean("requested", { + requested: t.field({ + type: "Boolean", description: "Whether protocol acceleration was requested by the caller.", nullable: false, + resolve: (parent) => parent.requested, }), - attempted: t.exposeBoolean("attempted", { + attempted: t.field({ + type: "Boolean", description: "Whether protocol acceleration was attempted at runtime.", nullable: false, + resolve: (parent) => parent.attempted, }), }), }); diff --git a/apps/ensapi/src/omnigraph-api/schema/resolver-records.ts b/apps/ensapi/src/omnigraph-api/schema/resolver-records.ts index 4b9fc04db..a47e0eba5 100644 --- a/apps/ensapi/src/omnigraph-api/schema/resolver-records.ts +++ b/apps/ensapi/src/omnigraph-api/schema/resolver-records.ts @@ -43,10 +43,11 @@ ResolverRecordsRef.implement({ //////////////////////// // ResolverRecords.name //////////////////////// - name: t.expose("name", { + name: t.field({ description: "The `name` record for this `node`, if any.", type: "String", nullable: true, + resolve: (parent) => parent.name, }), //////////////////////// From ec97f9029901a3f928e0286b2f25e383642d3089 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 1 Jun 2026 15:24:11 -0500 Subject: [PATCH 5/6] docs: clarify address record null semantics; ensdb value column hex Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/ensapi/src/omnigraph-api/schema/records.ts | 2 +- .../docs/docs/services/ensdb/concepts/database-schemas.mdx | 2 +- packages/enssdk/src/omnigraph/generated/schema.graphql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ensapi/src/omnigraph-api/schema/records.ts b/apps/ensapi/src/omnigraph-api/schema/records.ts index c9ed66d89..2e81d925f 100644 --- a/apps/ensapi/src/omnigraph-api/schema/records.ts +++ b/apps/ensapi/src/omnigraph-api/schema/records.ts @@ -50,7 +50,7 @@ ResolvedAddressRecordRef.implement({ address: t.field({ type: "Hex", description: - '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. Guaranteed to be at least one byte of hex data. There is no guarantee that an EVM CoinType returns an address value of any particular byte length.', + 'The "raw" resolved address record as hex, or null if not set, empty ("0x"), or zeroAddress. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type. Guaranteed to be at least one byte of hex data. There is no guarantee that an EVM CoinType returns an address value of any particular byte length.', nullable: true, resolve: (r) => r.address, }), diff --git a/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx b/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx index 0dfa1b10c..b8c660cbb 100644 --- a/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx +++ b/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx @@ -159,7 +159,7 @@ Keyed by `(chain_id, resolver, node, coin_type)`, where the composite key segmen | `address` | `text` | no | Resolver contract address. Part of primary key. | | `node` | `text` | no | Name namehash. Part of primary key. | | `coin_type` | `numeric(78)` | no | All well-known CoinTypes fit into a JavaScript number but NOT a Postgres `integer`, and must be stored as `bigint`. Part of primary key. | -| `value` | `text` | no | The value of the Address Record specified by `((chain_id, resolver, node), coin_type)`. Interpreted by `interpretAddressRecordValue` — see its implementation for additional context and specific guarantees. | +| `value` | `hex` | no | The value of the Address Record specified by `((chain_id, resolver, node), coin_type)`. Interpreted by `interpretAddressRecordValue` — see its implementation for additional context and specific guarantees. | **Primary key:** `(chain_id, address, node, coin_type)`. diff --git a/packages/enssdk/src/omnigraph/generated/schema.graphql b/packages/enssdk/src/omnigraph/generated/schema.graphql index 56ea2116a..f9722a4aa 100644 --- a/packages/enssdk/src/omnigraph/generated/schema.graphql +++ b/packages/enssdk/src/omnigraph/generated/schema.graphql @@ -1490,7 +1490,7 @@ type ResolvedAbiRecord { """A resolved address record for an ENS name.""" type ResolvedAddressRecord { """ - 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. Guaranteed to be at least one byte of hex data. There is no guarantee that an EVM CoinType returns an address value of any particular byte length. + The "raw" resolved address record as hex, or null if not set, empty ("0x"), or zeroAddress. Decode with ENSIP-9 (https://docs.ens.domains/ensip/9) and address-encoder (https://github.com/ensdomains/address-encoder) for the associated coin type. Guaranteed to be at least one byte of hex data. There is no guarantee that an EVM CoinType returns an address value of any particular byte length. """ address: Hex From 1fdeafb420fd3767adf52a2ec9033b330cc9a058 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 1 Jun 2026 15:41:41 -0500 Subject: [PATCH 6/6] fix: typo Addresss -> Address in protocol-acceleration schema comment Co-Authored-By: Claude Opus 4.8 (1M context) --- .agents/skills/agent-dx-cli-scale/SKILL.md | 114 ++++++++++++++++++ .../protocol-acceleration.schema.ts | 2 +- skills-lock.json | 11 ++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/agent-dx-cli-scale/SKILL.md create mode 100644 skills-lock.json diff --git a/.agents/skills/agent-dx-cli-scale/SKILL.md b/.agents/skills/agent-dx-cli-scale/SKILL.md new file mode 100644 index 000000000..9abf97760 --- /dev/null +++ b/.agents/skills/agent-dx-cli-scale/SKILL.md @@ -0,0 +1,114 @@ +--- +name: agent-dx-cli-scale +description: A scoring scale for evaluating how well a CLI is designed for AI agents, based on the "Rewrite Your CLI for AI Agents" principles. +--- + +# Agent DX CLI Scale + +Use this skill to **evaluate any CLI** against the principles of agent-first design. Score each axis from 0–3, then sum for a total between 0–21. + +> Human DX optimizes for discoverability and forgiveness. +> Agent DX optimizes for predictability and defense-in-depth. +> — [You Need to Rewrite Your CLI for AI Agents](/posts/rewrite-your-cli-for-ai-agents) + +--- + +## Scoring Axes + +### 1. Machine-Readable Output + +Can an agent parse the CLI's output without heuristics? + +| Score | Criteria | +| ----- | ----------------------------------------------------------------------------------------------------- | +| 0 | Human-only output (tables, color codes, prose). No structured format available. | +| 1 | `--output json` or equivalent exists but is incomplete or inconsistent across commands. | +| 2 | Consistent JSON output across all commands. Errors also return structured JSON. | +| 3 | NDJSON streaming for paginated results. Structured output is the default in non-TTY (piped) contexts. | + +### 2. Raw Payload Input + +Can an agent send the full API payload without translation through bespoke flags? + +| Score | Criteria | +| ----- | ------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | Only bespoke flags. No way to pass structured input. | +| 1 | Accepts `--json` or stdin JSON for some commands, but most require flags. | +| 2 | All mutating commands accept a raw JSON payload that maps directly to the underlying API schema. | +| 3 | Raw payload is first-class alongside convenience flags. The agent can use the API schema as documentation with zero translation loss. | + +### 3. Schema Introspection + +Can an agent discover what the CLI accepts at runtime without pre-stuffed documentation? + +| Score | Criteria | +| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | Only `--help` text. No machine-readable schema. | +| 1 | `--help --json` or a `describe` command for some surfaces, but incomplete. | +| 2 | Full schema introspection for all commands — params, types, required fields — as JSON. | +| 3 | Live, runtime-resolved schemas (e.g., from a discovery document) that always reflect the current API version. Includes scopes, enums, and nested types. | + +### 4. Context Window Discipline + +Does the CLI help agents control response size to protect their context window? + +| Score | Criteria | +| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | Returns full API responses with no way to limit fields or paginate. | +| 1 | Supports `--fields` or field masks on some commands. | +| 2 | Field masks on all read commands. Pagination with `--page-all` or equivalent. | +| 3 | Streaming pagination (NDJSON per page). Explicit guidance in context/skill files on field mask usage. The CLI actively protects the agent from token waste. | + +### 5. Input Hardening + +Does the CLI defend against the specific ways agents fail (hallucinations, not typos)? + +| Score | Criteria | +| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | No input validation beyond basic type checks. | +| 1 | Validates some inputs, but does not cover agent-specific hallucination patterns (path traversals, embedded query params, double encoding). | +| 2 | Rejects control characters, path traversals (`../`), percent-encoded segments (`%2e`), and embedded query params (`?`, `#`) in resource IDs. | +| 3 | Comprehensive hardening: all of the above, plus output path sandboxing to CWD, HTTP-layer percent-encoding, and an explicit security posture — _"The agent is not a trusted operator."_ | + +### 6. Safety Rails + +Can agents validate before acting, and are responses sanitized against prompt injection? + +| Score | Criteria | +| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | No dry-run mode. No response sanitization. | +| 1 | `--dry-run` exists for some mutating commands. | +| 2 | `--dry-run` for all mutating commands. Agent can validate requests without side effects. | +| 3 | Dry-run plus response sanitization (e.g., via Model Armor) to defend against prompt injection embedded in API data. The full request→response loop is defended. | + +### 7. Agent Knowledge Packaging + +Does the CLI ship knowledge in formats agents can consume at conversation start? + +| Score | Criteria | +| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | Only `--help` and a docs site. No agent-specific context files. | +| 1 | A `CONTEXT.md` or `AGENTS.md` with basic usage guidance. | +| 2 | Structured skill files (YAML frontmatter + Markdown) covering per-command or per-API-surface workflows and invariants. | +| 3 | Comprehensive skill library encoding agent-specific guardrails (_"always use --dry-run"_, _"always use --fields"_). Skills are versioned, discoverable, and follow a standard like OpenClaw. | + +--- + +## Interpreting the Total + +| Range | Rating | Description | +| ----- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | +| 0–5 | **Human-only** | Built for humans. Agents will struggle with parsing, hallucinate inputs, and lack safety rails. | +| 6–10 | **Agent-tolerant** | Agents can use it, but they'll waste tokens, make avoidable errors, and require heavy prompt engineering to compensate. | +| 11–15 | **Agent-ready** | Solid agent support. Structured I/O, input validation, and some introspection. A few gaps remain. | +| 16–21 | **Agent-first** | Purpose-built for agents. Full schema introspection, comprehensive input hardening, safety rails, and packaged agent knowledge. | + +--- + +## Bonus: Multi-Surface Readiness + +Not scored, but note whether the CLI exposes multiple agent surfaces from the same binary: + +- [ ] **MCP (stdio JSON-RPC)** — typed tool invocation, no shell escaping +- [ ] **Extension / plugin install** — agent treats the CLI as a native capability +- [ ] **Headless auth** — env vars for tokens/credentials, no browser redirect required diff --git a/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts b/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts index 177309a62..976278f38 100644 --- a/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts +++ b/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts @@ -197,7 +197,7 @@ export const resolverAddressRecord = onchainTable( coinType: t.bigint().notNull(), /** - * Represents the value of the Addresss Record specified by ((chainId, resolver, node), coinType). + * Represents the value of the Address Record specified by ((chainId, resolver, node), coinType). * * The value of this field is interpreted by `interpretAddressRecordValue` — see its implementation * for additional context and specific guarantees. diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 000000000..4a784c2f8 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "agent-dx-cli-scale": { + "source": "jpoehnelt/skills", + "sourceType": "github", + "skillPath": "agent-dx-cli-scale/SKILL.md", + "computedHash": "1d2aa0d3cfa03501af8656283631b61f572f6458ed7958f232f8acd5601386fa" + } + } +}