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/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/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 6b984e4dd..2e81d925f 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. 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, 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, }), }), }); @@ -132,12 +137,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/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, }), //////////////////////// 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/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts b/packages/ensdb-sdk/src/ensindexer-abstract/protocol-acceleration.schema.ts index 5bfe03815..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,12 +197,12 @@ 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. */ - value: t.text().notNull(), + value: t.hex().notNull(), }), (t) => ({ pk: primaryKey({ columns: [t.chainId, t.address, t.node, t.coinType] }), diff --git a/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts b/packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts index 665f56c68..c4a291943 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, @@ -51,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/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 294f4202f..06071e436 100644 --- a/packages/enssdk/src/omnigraph/generated/introspection.ts +++ b/packages/enssdk/src/omnigraph/generated/introspection.ts @@ -6320,18 +6320,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 e5ea4f917..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. 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, 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 @@ -1547,11 +1547,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).""" 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" + } + } +}