From 9fe129a87a49b290a62e15cda5fec613e66ff219 Mon Sep 17 00:00:00 2001 From: shrugs Date: Thu, 19 Mar 2026 15:09:48 -0500 Subject: [PATCH 01/29] feat: reserve top-level package names for narrative overhaul --- packages/enscli/README.md | 5 +++++ packages/enscli/package.json | 12 ++++++++++++ packages/enskit/README.md | 5 +++++ packages/enskit/package.json | 12 ++++++++++++ packages/enssdk/README.md | 5 +++++ packages/enssdk/package.json | 12 ++++++++++++ packages/ensskills/README.md | 5 +++++ packages/ensskills/package.json | 12 ++++++++++++ 8 files changed, 68 insertions(+) create mode 100644 packages/enscli/README.md create mode 100644 packages/enscli/package.json create mode 100644 packages/enskit/README.md create mode 100644 packages/enskit/package.json create mode 100644 packages/enssdk/README.md create mode 100644 packages/enssdk/package.json create mode 100644 packages/ensskills/README.md create mode 100644 packages/ensskills/package.json diff --git a/packages/enscli/README.md b/packages/enscli/README.md new file mode 100644 index 0000000000..3ce7543ea3 --- /dev/null +++ b/packages/enscli/README.md @@ -0,0 +1,5 @@ +# enscli + +This package name is reserved for the [ENSNode](https://ensnode.io) project by [NameHash Labs](https://namehashlabs.org). + +For more information, visit [ensnode.io](https://ensnode.io). diff --git a/packages/enscli/package.json b/packages/enscli/package.json new file mode 100644 index 0000000000..0e1365bd6e --- /dev/null +++ b/packages/enscli/package.json @@ -0,0 +1,12 @@ +{ + "name": "enscli", + "version": "0.0.1", + "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", + "repository": { + "type": "git", + "url": "https://github.com/namehash/ensnode.git", + "directory": "packages/enscli" + }, + "license": "MIT", + "homepage": "https://ensnode.io" +} diff --git a/packages/enskit/README.md b/packages/enskit/README.md new file mode 100644 index 0000000000..12214ac9d2 --- /dev/null +++ b/packages/enskit/README.md @@ -0,0 +1,5 @@ +# enskit + +This package name is reserved for the [ENSNode](https://ensnode.io) project by [NameHash Labs](https://namehashlabs.org). + +For more information, visit [ensnode.io](https://ensnode.io). diff --git a/packages/enskit/package.json b/packages/enskit/package.json new file mode 100644 index 0000000000..0b9431558a --- /dev/null +++ b/packages/enskit/package.json @@ -0,0 +1,12 @@ +{ + "name": "enskit", + "version": "0.0.1", + "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", + "repository": { + "type": "git", + "url": "https://github.com/namehash/ensnode.git", + "directory": "packages/enskit" + }, + "license": "MIT", + "homepage": "https://ensnode.io" +} diff --git a/packages/enssdk/README.md b/packages/enssdk/README.md new file mode 100644 index 0000000000..4319d5ff6d --- /dev/null +++ b/packages/enssdk/README.md @@ -0,0 +1,5 @@ +# enssdk + +This package name is reserved for the [ENSNode](https://ensnode.io) project by [NameHash Labs](https://namehashlabs.org). + +For more information, visit [ensnode.io](https://ensnode.io). diff --git a/packages/enssdk/package.json b/packages/enssdk/package.json new file mode 100644 index 0000000000..b953a7db79 --- /dev/null +++ b/packages/enssdk/package.json @@ -0,0 +1,12 @@ +{ + "name": "enssdk", + "version": "0.0.1", + "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", + "repository": { + "type": "git", + "url": "https://github.com/namehash/ensnode.git", + "directory": "packages/enssdk" + }, + "license": "MIT", + "homepage": "https://ensnode.io" +} diff --git a/packages/ensskills/README.md b/packages/ensskills/README.md new file mode 100644 index 0000000000..60cd87e96c --- /dev/null +++ b/packages/ensskills/README.md @@ -0,0 +1,5 @@ +# ensskills + +This package name is reserved for the [ENSNode](https://ensnode.io) project by [NameHash Labs](https://namehashlabs.org). + +For more information, visit [ensnode.io](https://ensnode.io). diff --git a/packages/ensskills/package.json b/packages/ensskills/package.json new file mode 100644 index 0000000000..efe6f1e583 --- /dev/null +++ b/packages/ensskills/package.json @@ -0,0 +1,12 @@ +{ + "name": "ensskills", + "version": "0.0.1", + "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", + "repository": { + "type": "git", + "url": "https://github.com/namehash/ensnode.git", + "directory": "packages/ensskills" + }, + "license": "MIT", + "homepage": "https://ensnode.io" +} From 4880146f354882b8586eaa8e03331c63ea9e6b7a Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 12:04:00 -0500 Subject: [PATCH 02/29] checkpoint: update addresses, use docker-compose as source of truth for integration testing --- AGENTS.md | 2 +- docker-compose.yml | 20 +++++- package.json | 1 + packages/datasources/src/ens-test-env.ts | 24 +++---- .../src/graphql-api/example-queries.ts | 2 +- packages/integration-test-env/package.json | 5 +- .../integration-test-env/src/orchestrator.ts | 71 +++++++------------ packages/integration-test-env/tsconfig.json | 4 ++ pnpm-lock.yaml | 18 +---- 9 files changed, 68 insertions(+), 79 deletions(-) create mode 100644 packages/integration-test-env/tsconfig.json diff --git a/AGENTS.md b/AGENTS.md index dd62b4538a..abbc539a7d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,7 +42,7 @@ Runnable commands for validating changes; lint and format with Biome. - Run tests for a single package/app: `pnpm --filter test` (e.g. `pnpm --filter ensapi test`) - Lint and format: `pnpm lint` (fixes where applicable); CI lint: `pnpm lint:ci` - Type checking: `pnpm typecheck` (runs typecheck in all workspaces) -- Build (validate tsup/tsx bundling for the package you changed): `pnpm --filter build` + - Always prefer `pnpm -F typecheck` over `tsc` ## Testing diff --git a/docker-compose.yml b/docker-compose.yml index fe37eb55e6..86750baf68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: # Override environment variables to point to docker instances DATABASE_URL: postgresql://postgres:password@postgres:5432/postgres - DATABASE_SCHEMA: ensindexer_0 + DATABASE_SCHEMA: a_unique_schema_name ENSRAINBOW_URL: http://ensrainbow:3223 env_file: # NOTE: must define apps/ensindexer/.env.local (see apps/ensindexer/.env.local.example) @@ -35,7 +35,7 @@ services: environment: # Override environment variables to point to docker instances DATABASE_URL: postgresql://postgres:password@postgres:5432/postgres - ENSINDEXER_SCHEMA_NAME: ensindexer_0 + ENSINDEXER_SCHEMA_NAME: a_unique_schema_name env_file: # NOTE: must define apps/ensapi/.env.local (see apps/ensapi/.env.local.example) # Copy .env.local.example to .env.local and configure all required values @@ -103,6 +103,22 @@ services: volumes: - postgres_data:/var/lib/postgresql/data + devnet: + image: ghcr.io/ensdomains/contracts-v2:main-e8696c6 + command: ./script/runDevnet.ts --testNames + pull_policy: always + ports: + - "8545:8545" + environment: + ANVIL_IP_ADDR: "0.0.0.0" + healthcheck: + test: ["CMD", "curl", "--fail", "-s", "http://localhost:8000/health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + start_interval: 1s + volumes: postgres_data: driver: local diff --git a/package.json b/package.json index 80da54cb73..a7b8af8779 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "changeset-publish": "changeset publish", "changeset-publish:next": "changeset publish --no-git-tag --snapshot --tag next", "packages:prepublish": "pnpm -r prepublish", + "devnet": "docker compose up devnet", "docker:build:ensnode": "pnpm run -w --parallel \"/^docker:build:.*/\"", "docker:build:ensindexer": "docker build -f apps/ensindexer/Dockerfile -t ghcr.io/namehash/ensnode/ensindexer:latest .", "docker:build:ensadmin": "docker build -f apps/ensadmin/Dockerfile -t ghcr.io/namehash/ensnode/ensadmin:latest .", diff --git a/packages/datasources/src/ens-test-env.ts b/packages/datasources/src/ens-test-env.ts index 6d6aac9df4..b27f4b9047 100644 --- a/packages/datasources/src/ens-test-env.ts +++ b/packages/datasources/src/ens-test-env.ts @@ -61,13 +61,13 @@ export default { // NOTE: named BaseRegistrarImplementation in devnet BaseRegistrar: { abi: root_BaseRegistrar, - address: "0xcd8a1c3ba11cf5ecfa6267617243239504a98d90", + address: "0x8a791620dd6260079bf849dc5567adc3f2fdc318", startBlock: 0, }, // NOTE: named LegacyETHRegistrarController in devnet LegacyEthRegistrarController: { abi: root_LegacyEthRegistrarController, - address: "0xd8a5a9b31c3c0232e196d518e89fd8bf83acad43", + address: "0xfbc22278a96299d91d41c453234d97b4f5eb9b2d", startBlock: 0, }, // NOTE: named WrappedETHRegistrarController in devnet @@ -79,7 +79,7 @@ export default { // NOTE: named ETHRegistrarController in devnet UnwrappedEthRegistrarController: { abi: root_UnwrappedEthRegistrarController, - address: "0x36b58f5c1969b7b6591d752ea6f5486d069010ab", + address: "0x1c85638e118b37167e9298c2268758e058ddfda0", startBlock: 0, }, // NOTE: not in devnet, set to zeroAddress @@ -90,18 +90,18 @@ export default { }, NameWrapper: { abi: root_NameWrapper, - address: "0xcbeaf3bde82155f56486fb5a1072cb8baaf547cc", + address: "0x5081a39b8a5f0e35a8d959395a630b68b74dd30f", startBlock: 0, }, UniversalResolver: { abi: UniversalResolverV1, - address: "0xd84379ceae14aa33c123af12424a37803f885889", + address: "0x5067457698fd6fa1c6964e416b3f42713513b3dd", startBlock: 0, }, // NOTE: named UniversalResolverV2 in devnet UniversalResolverV2: { abi: UniversalResolverV2, - address: "0x162a433068f51e18b7d13932f27e66a3f99e6890", + address: "0x8198f5d8f8cffe8f9c413d98a0a55aeb8ab9fbb7", startBlock: 0, }, }, @@ -120,12 +120,12 @@ export default { }, ETHRegistry: { abi: Registry, - address: "0x9e545e3c0baab3e08cdfd552c960a1050f373042", + address: "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf", startBlock: 0, }, ETHRegistrar: { abi: ETHRegistrar, - address: "0x5f3f1dbd7b74c6b46e8c44f98792a1daf8d69154", + address: "0x4c4a2f8c81640e47606d3fd77b353e87ba015584", startBlock: 0, }, }, @@ -136,28 +136,28 @@ export default { contracts: { DefaultReverseRegistrar: { abi: StandaloneReverseRegistrar, - address: "0x998abeb3e57409262ae5b751f60747921b33613e", + address: "0x4c5859f0f772848b2d91f1d83e2fe57935348029", startBlock: 0, }, // NOTE: named DefaultReverseResolver in devnet DefaultReverseResolver3: { abi: ResolverABI, - address: "0x4826533b4897376654bb4d4ad88b7fafd0c98528", + address: "0x5f3f1dbd7b74c6b46e8c44f98792a1daf8d69154", startBlock: 0, }, // NOTE: named LegacyPublicResolver in devnet DefaultPublicResolver4: { abi: ResolverABI, - address: "0x4ee6ecad1c2dae9f525404de8555724e3c35d07b", + address: "0x86a2ee8faf9a840f7a2c64ca3d51209f9a02081d", startBlock: 0, }, // NOTE: named PublicResolver in devnet DefaultPublicResolver5: { abi: ResolverABI, - address: "0xbec49fa140acaa83533fb00a2bb19bddd0290f25", + address: "0xa4899d35897033b927acfcf422bc745916139776", startBlock: 0, }, }, diff --git a/packages/ensnode-sdk/src/graphql-api/example-queries.ts b/packages/ensnode-sdk/src/graphql-api/example-queries.ts index eb625bd638..28801ab213 100644 --- a/packages/ensnode-sdk/src/graphql-api/example-queries.ts +++ b/packages/ensnode-sdk/src/graphql-api/example-queries.ts @@ -31,7 +31,7 @@ const ENS_TEST_ENV_V2_ETH_REGISTRAR = maybeGetDatasourceContract( const DEVNET_DEPLOYER = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; const DEVNET_OWNER = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; // biome-ignore lint/correctness/noUnusedVariables: keeping it around for the future -const DEVNET_USER = "0x90F79bf6EB2c4f870365E785982E1f101E93b906"; +const DEVNET_USER = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"; const VITALIK_ADDRESS = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; diff --git a/packages/integration-test-env/package.json b/packages/integration-test-env/package.json index 10fdc216fc..97c10db21c 100644 --- a/packages/integration-test-env/package.json +++ b/packages/integration-test-env/package.json @@ -5,13 +5,14 @@ "type": "module", "description": "Integration test environment orchestration for ENSNode", "scripts": { - "start": "CI=1 tsx src/orchestrator.ts" + "start": "CI=1 tsx src/orchestrator.ts", + "typecheck": "tsc --noEmit" }, "dependencies": { + "@ensnode/shared-configs": "workspace:*", "@ensnode/datasources": "workspace:*", "@ensnode/ensdb-sdk": "workspace:*", "@ensnode/ensnode-sdk": "workspace:*", - "@testcontainers/postgresql": "^11.12.0", "execa": "^9.6.1", "testcontainers": "^11.12.0", "tsx": "^4.7.1" diff --git a/packages/integration-test-env/src/orchestrator.ts b/packages/integration-test-env/src/orchestrator.ts index 6596792612..7822f1e40c 100644 --- a/packages/integration-test-env/src/orchestrator.ts +++ b/packages/integration-test-env/src/orchestrator.ts @@ -5,15 +5,16 @@ * monorepo-level integration tests, then tears everything down. * * Phases: - * 1. Postgres (testcontainers, dynamic port) + devnet (fixed ports 8545/8000) — parallel + * 1. Postgres + devnet via docker-compose (testcontainers DockerComposeEnvironment) * 2. Download pre-built ENSRainbow LevelDB, extract, start ENSRainbow from source * 3. Start ENSIndexer, wait for omnichain-following / omnichain-completed * 4. Start ENSApi * 5. Run `pnpm test:integration` at the monorepo root * * Design decisions: - * - testcontainers for Postgres (dynamic port, built-in health check) and devnet - * (fixed ports required — ensTestEnvChain hardcodes localhost:8545). + * - Postgres and devnet are started from the root docker-compose.yml via + * testcontainers DockerComposeEnvironment, ensuring the orchestrator always + * uses the same images and configuration defined there. * - execa for child process management — automatic cleanup on parent exit, * forceKillAfterDelay (10s SIGKILL fallback), env inherited from parent. * - Services run from source (pnpm start/serve) rather than Docker so that @@ -30,9 +31,12 @@ import { mkdirSync } from "node:fs"; import { resolve } from "node:path"; -import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql"; import { execaSync, type ResultPromise, execa as spawn } from "execa"; -import { GenericContainer, type StartedTestContainer, Wait } from "testcontainers"; +import { + DockerComposeEnvironment, + type StartedDockerComposeEnvironment, + Wait, +} from "testcontainers"; import { ENSNamespaceIds } from "@ensnode/datasources"; import { OmnichainIndexingStatusIds } from "@ensnode/ensnode-sdk"; @@ -42,23 +46,18 @@ const ENSRAINBOW_DIR = resolve(MONOREPO_ROOT, "apps/ensrainbow"); const ENSINDEXER_DIR = resolve(MONOREPO_ROOT, "apps/ensindexer"); const ENSAPI_DIR = resolve(MONOREPO_ROOT, "apps/ensapi"); -// Docker images -const POSTGRES_IMAGE = "postgres:17"; -const DEVNET_IMAGE = "ghcr.io/ensdomains/contracts-v2:main-cb8e11c"; - -// Ports (devnet ports must be fixed — ensTestEnvChain hardcodes localhost:8545) -const DEVNET_RPC_PORT = 8545; -const DEVNET_HEALTH_PORT = 8000; +// Ports const ENSRAINBOW_PORT = 3223; const ENSINDEXER_PORT = 42069; const ENSAPI_PORT = 4334; // Shared config const ENSRAINBOW_URL = `http://localhost:${ENSRAINBOW_PORT}`; +const ENSINDEXER_SCHEMA_NAME = "ensindexer_0"; // Track resources for cleanup const subprocesses: ResultPromise[] = []; -const containers: (StartedTestContainer | StartedPostgreSqlContainer)[] = []; +let composeEnvironment: StartedDockerComposeEnvironment | undefined; // Abort flag — set when a spawned service crashes let aborted = false; @@ -95,12 +94,12 @@ async function cleanup() { } log("All child processes stopped"); - for (const container of containers) { + if (composeEnvironment) { try { - await container.stop(); + await composeEnvironment.down(); } catch (error) { logError( - `Failed to stop container during cleanup: ${ + `Failed to stop compose environment during cleanup: ${ error instanceof Error ? error.message : String(error) }`, ); @@ -231,42 +230,24 @@ function logVersions() { log(` Node.js: ${process.version}`); log(` pnpm: ${execaSync("pnpm", ["--version"]).stdout.trim()}`); log(` Docker: ${execaSync("docker", ["--version"]).stdout.trim()}`); - log(` Postgres image: ${POSTGRES_IMAGE}`); - log(` Devnet image: ${DEVNET_IMAGE}`); } async function main() { log("Starting integration test environment..."); logVersions(); - // Phase 1: Start Postgres + Devnet in parallel + // Phase 1: Start Postgres + Devnet via docker-compose log("Starting Postgres and devnet..."); - const postgresPromise = new PostgreSqlContainer(POSTGRES_IMAGE) - .withDatabase("ensnode") - .withUsername("postgres") - .withPassword("password") - .start() - .then((c) => { - containers.push(c); - return c; - }); - const devnetPromise = new GenericContainer(DEVNET_IMAGE) - .withEnvironment({ ANVIL_IP_ADDR: "0.0.0.0" }) - .withExposedPorts( - { container: DEVNET_RPC_PORT, host: DEVNET_RPC_PORT }, - { container: DEVNET_HEALTH_PORT, host: DEVNET_HEALTH_PORT }, - ) - .withCommand(["./script/runDevnet.ts", "--testNames"]) + composeEnvironment = await new DockerComposeEnvironment(MONOREPO_ROOT, "docker-compose.yml") + .withWaitStrategy("devnet-1", Wait.forHealthCheck()) + .withWaitStrategy("postgres-1", Wait.forListeningPorts()) .withStartupTimeout(120_000) - .withWaitStrategy(Wait.forHttp("/health", DEVNET_HEALTH_PORT)) - .start() - .then((c) => { - containers.push(c); - return c; - }); - const [postgres] = await Promise.all([postgresPromise, devnetPromise]); - const DATABASE_URL = postgres.getConnectionUri(); - log(`Postgres is ready (port ${postgres.getPort()})`); + .up(["postgres", "devnet"]); + + const postgresContainer = composeEnvironment.getContainer("postgres-1"); + const postgresPort = postgresContainer.getMappedPort(5432); + const DATABASE_URL = `postgresql://postgres:password@localhost:${postgresPort}/postgres`; + log(`Postgres is ready (port ${postgresPort})`); log("Devnet is ready"); // Phase 2: Download ENSRainbow database and start from source @@ -317,8 +298,6 @@ async function main() { await waitForHealth(`http://localhost:${ENSRAINBOW_PORT}/health`, 30_000, "ENSRainbow"); // Phase 3: Start ENSIndexer - const ENSINDEXER_SCHEMA_NAME = "ensindexer_0"; - log("Starting ENSIndexer..."); spawnService( "pnpm", diff --git a/packages/integration-test-env/tsconfig.json b/packages/integration-test-env/tsconfig.json new file mode 100644 index 0000000000..d6e0b230ac --- /dev/null +++ b/packages/integration-test-env/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@ensnode/shared-configs/tsconfig.lib.json", + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bb29a6c82..f693c35fe5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -979,9 +979,9 @@ importers: '@ensnode/ensnode-sdk': specifier: workspace:* version: link:../ensnode-sdk - '@testcontainers/postgresql': - specifier: ^11.12.0 - version: 11.12.0 + '@ensnode/shared-configs': + specifier: workspace:* + version: link:../shared-configs execa: specifier: ^9.6.1 version: 9.6.1 @@ -4279,9 +4279,6 @@ packages: '@tanstack/virtual-core@3.13.12': resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} - '@testcontainers/postgresql@11.12.0': - resolution: {integrity: sha512-w4ZK0H+WIYBUBk57H9wCSxPMSMZUNsFpx2MZAX4iru0Aevz9HFWDfAhFLAu+/SwsHtEJUD7XfWUDlqBGC3OF0Q==} - '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -13091,15 +13088,6 @@ snapshots: '@tanstack/virtual-core@3.13.12': {} - '@testcontainers/postgresql@11.12.0': - dependencies: - testcontainers: 11.12.0 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - - supports-color - '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 From cbb644978a64aed5f704afc3ccc992c85649b16a Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 12:17:08 -0500 Subject: [PATCH 03/29] fix: remove the Resolver.dedicated metadata in advance of permissionedresolver --- .../ensapi/src/graphql-api/schema/resolver.ts | 72 +------------------ packages/ensnode-sdk/src/rpc/index.ts | 1 - .../src/rpc/is-dedicated-resolver.ts | 12 ---- 3 files changed, 2 insertions(+), 83 deletions(-) delete mode 100644 packages/ensnode-sdk/src/rpc/is-dedicated-resolver.ts diff --git a/apps/ensapi/src/graphql-api/schema/resolver.ts b/apps/ensapi/src/graphql-api/schema/resolver.ts index f3119b1004..de194f89b8 100644 --- a/apps/ensapi/src/graphql-api/schema/resolver.ts +++ b/apps/ensapi/src/graphql-api/schema/resolver.ts @@ -4,13 +4,7 @@ import { type ResolveCursorConnectionArgs, resolveCursorConnection } from "@poth import { and, eq } from "drizzle-orm"; import { namehash } from "viem"; -import { - makePermissionsId, - makeResolverRecordsId, - NODE_ANY, - type ResolverId, - ROOT_RESOURCE, -} from "@ensnode/ensnode-sdk"; +import { makePermissionsId, makeResolverRecordsId, type ResolverId } from "@ensnode/ensnode-sdk"; import { isBridgedResolver } from "@ensnode/ensnode-sdk/internal"; import { builder } from "@/graphql-api/builder"; @@ -18,12 +12,11 @@ import { orderPaginationBy, paginateBy } from "@/graphql-api/lib/connection-help import { resolveFindEvents } from "@/graphql-api/lib/find-events/find-events-resolver"; import { getModelId } from "@/graphql-api/lib/get-model-id"; import { lazyConnection } from "@/graphql-api/lib/lazy-connection"; -import { AccountRef } from "@/graphql-api/schema/account"; import { AccountIdInput, AccountIdRef } from "@/graphql-api/schema/account-id"; import { ID_PAGINATED_CONNECTION_ARGS } from "@/graphql-api/schema/constants"; import { EventRef, EventsWhereInput } from "@/graphql-api/schema/event"; import { NameOrNodeInput } from "@/graphql-api/schema/name-or-node"; -import { PermissionsRef, type PermissionsUserResource } from "@/graphql-api/schema/permissions"; +import { PermissionsRef } from "@/graphql-api/schema/permissions"; import { ResolverRecordsRef } from "@/graphql-api/schema/resolver-records"; import { ensDb, ensIndexerSchema } from "@/lib/ensdb/singleton"; @@ -121,24 +114,6 @@ ResolverRef.implement({ }, }), - ////////////////////// - // Resolver.dedicated - ////////////////////// - dedicated: t.field({ - description: "If Resolver is a DedicatedResolver, additional DedicatedResolverMetadata.", - type: DedicatedResolverMetadataRef, - nullable: true, - resolve: async (parent, args, context) => - ensDb.query.permissionsUser.findFirst({ - where: (t, { eq, and }) => - and( - eq(t.chainId, parent.chainId), - eq(t.address, parent.address), - eq(t.resource, ROOT_RESOURCE), - ), - }), - }), - //////////////////// // Resolver.bridged //////////////////// @@ -178,49 +153,6 @@ ResolverRef.implement({ }), }); -///////////////////////////// -// DedicatedResolverMetadata -///////////////////////////// -export const DedicatedResolverMetadataRef = builder.objectRef( - "DedicatedResolverMetadataRef", -); -DedicatedResolverMetadataRef.implement({ - description: "Represents additional metadata available for DedicatedResolvers.", - fields: (t) => ({ - /////////////////////////// - // DedicatedResolver.owner - /////////////////////////// - owner: t.field({ - description: "The Account that owns this DedicatedResolver.", - type: AccountRef, - nullable: false, - resolve: (parent) => parent.user, - }), - - ///////////////////////////////// - // DedicatedResolver.permissions - ///////////////////////////////// - permissions: t.field({ - description: "TODO", - type: PermissionsRef, - nullable: false, - // TODO: render a DedicatedResolverPermissions model that parses the backing permissions into dedicated-resolver-semantic roles? - resolve: ({ chainId, address }) => makePermissionsId({ chainId, address }), - }), - - ///////////////////////////// - // Resolver.dedicatedRecords - ///////////////////////////// - records: t.field({ - description: "The ResolverRecords issued under `NODE_ANY`.", - type: ResolverRecordsRef, - nullable: true, - resolve: ({ chainId, address }, args) => - makeResolverRecordsId({ chainId, address }, NODE_ANY), - }), - }), -}); - ///////////////////// // Inputs ///////////////////// diff --git a/packages/ensnode-sdk/src/rpc/index.ts b/packages/ensnode-sdk/src/rpc/index.ts index 157a5f3957..8403bdafa3 100644 --- a/packages/ensnode-sdk/src/rpc/index.ts +++ b/packages/ensnode-sdk/src/rpc/index.ts @@ -1,3 +1,2 @@ export * from "./eip-165"; -export * from "./is-dedicated-resolver"; export * from "./is-extended-resolver"; diff --git a/packages/ensnode-sdk/src/rpc/is-dedicated-resolver.ts b/packages/ensnode-sdk/src/rpc/is-dedicated-resolver.ts deleted file mode 100644 index 9cd904b5db..0000000000 --- a/packages/ensnode-sdk/src/rpc/is-dedicated-resolver.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { makeSupportsInterfaceReader } from "./eip-165"; - -/** - * DedicatedResolver InterfaceId - * @see https://github.com/ensdomains/contracts-v2/blob/main/contracts/src/resolver/interfaces/IDedicatedResolverSetters.sol - */ -const IDedicatedResolverInterfaceId = "0x92349baa"; - -/** - * Determines whether a Resolver contract supports ENSIP-10. - */ -export const isDedicatedResolver = makeSupportsInterfaceReader(IDedicatedResolverInterfaceId); From 2ecd710cad1e17ee077532ba3793b660adcebf79 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 12:17:52 -0500 Subject: [PATCH 04/29] docs(changeset): Omnigraph API (BREAKING): Removed `Resolver.dedicated` field in advance of PermissionedResolver integration. --- .changeset/tricky-clouds-dress.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tricky-clouds-dress.md diff --git a/.changeset/tricky-clouds-dress.md b/.changeset/tricky-clouds-dress.md new file mode 100644 index 0000000000..a62ac85e59 --- /dev/null +++ b/.changeset/tricky-clouds-dress.md @@ -0,0 +1,5 @@ +--- +"ensapi": minor +--- + +Omnigraph API (BREAKING): Removed `Resolver.dedicated` field in advance of PermissionedResolver integration. From 2c0ddf3f06e240be202a895af808f962b0d1c817 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 12:46:29 -0500 Subject: [PATCH 05/29] checkpoint: update abis --- .../src/abis/ensv2/ETHRegistrar.ts | 1190 ++++++------- .../src/abis/ensv2/EnhancedAccessControl.ts | 393 +++-- .../datasources/src/abis/ensv2/Registry.ts | 1532 ++++++++++++++--- .../src/abis/ensv2/UniversalResolverV2.ts | 673 ++++---- packages/integration-test-env/README.md | 7 +- 5 files changed, 2410 insertions(+), 1385 deletions(-) diff --git a/packages/datasources/src/abis/ensv2/ETHRegistrar.ts b/packages/datasources/src/abis/ensv2/ETHRegistrar.ts index d3eead1ce6..1b71a1663f 100644 --- a/packages/datasources/src/abis/ensv2/ETHRegistrar.ts +++ b/packages/datasources/src/abis/ensv2/ETHRegistrar.ts @@ -2,1143 +2,1143 @@ import type { Abi } from "viem"; export const ETHRegistrar = [ { + type: "constructor", inputs: [ { - internalType: "contract IPermissionedRegistry", name: "registry", type: "address", + internalType: "contract IPermissionedRegistry", }, { - internalType: "contract IHCAFactoryBasic", name: "hcaFactory", type: "address", + internalType: "contract IHCAFactoryBasic", }, { - internalType: "address", name: "beneficiary", type: "address", + internalType: "address", }, { - internalType: "uint64", name: "minCommitmentAge", type: "uint64", + internalType: "uint64", }, { - internalType: "uint64", name: "maxCommitmentAge", type: "uint64", + internalType: "uint64", }, { - internalType: "uint64", name: "minRegisterDuration", type: "uint64", + internalType: "uint64", }, { - internalType: "contract IRentPriceOracle", name: "rentPriceOracle_", type: "address", + internalType: "contract IRentPriceOracle", }, ], stateMutability: "nonpayable", - type: "constructor", }, { - inputs: [ + type: "function", + name: "BENEFICIARY", + inputs: [], + outputs: [ { - internalType: "bytes32", - name: "commitment", - type: "bytes32", + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "HCA_FACTORY", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract IHCAFactoryBasic", }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "MAX_COMMITMENT_AGE", + inputs: [], + outputs: [ { - internalType: "uint64", - name: "validFrom", + name: "", type: "uint64", + internalType: "uint64", }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "MIN_COMMITMENT_AGE", + inputs: [], + outputs: [ { + name: "", + type: "uint64", internalType: "uint64", - name: "blockTimestamp", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "MIN_REGISTER_DURATION", + inputs: [], + outputs: [ + { + name: "", type: "uint64", + internalType: "uint64", }, ], - name: "CommitmentTooNew", - type: "error", + stateMutability: "view", }, { - inputs: [ + type: "function", + name: "REGISTRY", + inputs: [], + outputs: [ { - internalType: "bytes32", - name: "commitment", - type: "bytes32", + name: "", + type: "address", + internalType: "contract IPermissionedRegistry", }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "ROOT_RESOURCE", + inputs: [], + outputs: [ { - internalType: "uint64", - name: "validTo", - type: "uint64", + name: "", + type: "uint256", + internalType: "uint256", }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "commit", + inputs: [ { - internalType: "uint64", - name: "blockTimestamp", - type: "uint64", + name: "commitment", + type: "bytes32", + internalType: "bytes32", }, ], - name: "CommitmentTooOld", - type: "error", + outputs: [], + stateMutability: "nonpayable", }, { + type: "function", + name: "commitmentAt", inputs: [ { - internalType: "uint64", - name: "duration", - type: "uint64", + name: "commitment", + type: "bytes32", + internalType: "bytes32", }, + ], + outputs: [ { - internalType: "uint64", - name: "minDuration", + name: "commitTime", type: "uint64", + internalType: "uint64", }, ], - name: "DurationTooShort", - type: "error", + stateMutability: "view", }, { + type: "function", + name: "getAssigneeCount", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "counts", + type: "uint256", + internalType: "uint256", }, { - internalType: "address", - name: "account", - type: "address", + name: "mask", + type: "uint256", + internalType: "uint256", }, ], - name: "EACCannotGrantRoles", - type: "error", + stateMutability: "view", }, { + type: "function", + name: "grantRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "EACCannotRevokeRoles", - type: "error", - }, - { - inputs: [], - name: "EACInvalidAccount", - type: "error", - }, - { - inputs: [ + outputs: [ { - internalType: "uint256", - name: "roleBitmap", - type: "uint256", + name: "", + type: "bool", + internalType: "bool", }, ], - name: "EACInvalidRoleBitmap", - type: "error", + stateMutability: "nonpayable", }, { + type: "function", + name: "grantRootRoles", inputs: [ { - internalType: "uint256", - name: "resource", + name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", - name: "role", - type: "uint256", + name: "account", + type: "address", + internalType: "address", }, ], - name: "EACMaxAssignees", - type: "error", + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", }, { + type: "function", + name: "hasAssignees", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", - name: "role", + name: "roleBitmap", type: "uint256", + internalType: "uint256", }, ], - name: "EACMinAssignees", - type: "error", - }, - { - inputs: [], - name: "EACRootResourceNotAllowed", - type: "error", + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", }, { + type: "function", + name: "hasRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "EACUnauthorizedAccountRoles", - type: "error", - }, - { - inputs: [], - name: "InvalidOwner", - type: "error", - }, - { - inputs: [], - name: "MaxCommitmentAgeTooLow", - type: "error", + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", }, { + type: "function", + name: "hasRootRoles", inputs: [ { - internalType: "string", - name: "label", - type: "string", + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", }, ], - name: "NameIsAvailable", - type: "error", - }, - { - inputs: [ + outputs: [ { - internalType: "string", - name: "label", - type: "string", + name: "", + type: "bool", + internalType: "bool", }, ], - name: "NameNotAvailable", - type: "error", + stateMutability: "view", }, { + type: "function", + name: "isAvailable", inputs: [ { - internalType: "string", name: "label", type: "string", + internalType: "string", }, ], - name: "NotValid", - type: "error", + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", }, { + type: "function", + name: "isPaymentToken", inputs: [ { - internalType: "contract IERC20", name: "paymentToken", type: "address", + internalType: "contract IERC20", }, ], - name: "PaymentTokenNotSupported", - type: "error", - }, - { - inputs: [ + outputs: [ { - internalType: "address", - name: "token", - type: "address", + name: "", + type: "bool", + internalType: "bool", }, ], - name: "SafeERC20FailedOperation", - type: "error", + stateMutability: "view", }, { + type: "function", + name: "isValid", inputs: [ { - internalType: "bytes32", - name: "commitment", - type: "bytes32", + name: "label", + type: "string", + internalType: "string", }, ], - name: "UnexpiredCommitmentExists", - type: "error", - }, - { - anonymous: false, - inputs: [ + outputs: [ { - indexed: false, - internalType: "bytes32", - name: "commitment", - type: "bytes32", + name: "", + type: "bool", + internalType: "bool", }, ], - name: "CommitmentMade", - type: "event", + stateMutability: "view", }, { - anonymous: false, + type: "function", + name: "makeCommitment", inputs: [ { - indexed: true, - internalType: "uint256", - name: "resource", - type: "uint256", + name: "label", + type: "string", + internalType: "string", }, { - indexed: true, + name: "owner", + type: "address", internalType: "address", - name: "account", + }, + { + name: "secret", + type: "bytes32", + internalType: "bytes32", + }, + { + name: "subregistry", type: "address", + internalType: "contract IRegistry", }, { - indexed: false, - internalType: "uint256", - name: "oldRoleBitmap", - type: "uint256", + name: "resolver", + type: "address", + internalType: "address", }, { - indexed: false, - internalType: "uint256", - name: "newRoleBitmap", - type: "uint256", + name: "duration", + type: "uint64", + internalType: "uint64", + }, + { + name: "referrer", + type: "bytes32", + internalType: "bytes32", }, ], - name: "EACRolesChanged", - type: "event", + outputs: [ + { + name: "", + type: "bytes32", + internalType: "bytes32", + }, + ], + stateMutability: "pure", }, { - anonymous: false, + type: "function", + name: "register", inputs: [ { - indexed: true, - internalType: "uint256", - name: "tokenId", - type: "uint256", - }, - { - indexed: false, - internalType: "string", name: "label", type: "string", + internalType: "string", }, { - indexed: false, - internalType: "address", name: "owner", type: "address", + internalType: "address", + }, + { + name: "secret", + type: "bytes32", + internalType: "bytes32", }, { - indexed: false, - internalType: "contract IRegistry", name: "subregistry", type: "address", + internalType: "contract IRegistry", }, { - indexed: false, - internalType: "address", name: "resolver", type: "address", + internalType: "address", }, { - indexed: false, - internalType: "uint64", name: "duration", type: "uint64", + internalType: "uint64", }, { - indexed: false, - internalType: "contract IERC20", name: "paymentToken", type: "address", + internalType: "contract IERC20", }, { - indexed: false, - internalType: "bytes32", name: "referrer", type: "bytes32", + internalType: "bytes32", }, + ], + outputs: [ { - indexed: false, - internalType: "uint256", - name: "base", + name: "tokenId", type: "uint256", - }, - { - indexed: false, internalType: "uint256", - name: "premium", - type: "uint256", }, ], - name: "NameRegistered", - type: "event", + stateMutability: "nonpayable", }, { - anonymous: false, + type: "function", + name: "renew", inputs: [ { - indexed: true, - internalType: "uint256", - name: "tokenId", - type: "uint256", - }, - { - indexed: false, - internalType: "string", name: "label", type: "string", + internalType: "string", }, { - indexed: false, - internalType: "uint64", name: "duration", type: "uint64", - }, - { - indexed: false, internalType: "uint64", - name: "newExpiry", - type: "uint64", }, { - indexed: false, - internalType: "contract IERC20", name: "paymentToken", type: "address", + internalType: "contract IERC20", }, { - indexed: false, - internalType: "bytes32", name: "referrer", type: "bytes32", - }, - { - indexed: false, - internalType: "uint256", - name: "base", - type: "uint256", + internalType: "bytes32", }, ], - name: "NameRenewed", - type: "event", + outputs: [], + stateMutability: "nonpayable", }, { - anonymous: false, + type: "function", + name: "rentPrice", inputs: [ { - indexed: true, - internalType: "contract IERC20", - name: "paymentToken", + name: "label", + type: "string", + internalType: "string", + }, + { + name: "owner", type: "address", + internalType: "address", + }, + { + name: "duration", + type: "uint64", + internalType: "uint64", }, - ], - name: "PaymentTokenAdded", - type: "event", - }, - { - anonymous: false, - inputs: [ { - indexed: true, - internalType: "contract IERC20", name: "paymentToken", type: "address", + internalType: "contract IERC20", }, ], - name: "PaymentTokenRemoved", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "contract IRentPriceOracle", - name: "oracle", - type: "address", - }, - ], - name: "RentPriceOracleChanged", - type: "event", - }, - { - inputs: [], - name: "BENEFICIARY", - outputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "HCA_FACTORY", - outputs: [ - { - internalType: "contract IHCAFactoryBasic", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "MAX_COMMITMENT_AGE", outputs: [ { - internalType: "uint64", - name: "", - type: "uint64", + name: "base", + type: "uint256", + internalType: "uint256", }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "MIN_COMMITMENT_AGE", - outputs: [ { - internalType: "uint64", - name: "", - type: "uint64", + name: "premium", + type: "uint256", + internalType: "uint256", }, ], stateMutability: "view", - type: "function", }, { - inputs: [], - name: "MIN_REGISTER_DURATION", - outputs: [ - { - internalType: "uint64", - name: "", - type: "uint64", - }, - ], - stateMutability: "view", type: "function", - }, - { + name: "rentPriceOracle", inputs: [], - name: "REGISTRY", outputs: [ { - internalType: "contract IPermissionedRegistry", name: "", type: "address", + internalType: "contract IRentPriceOracle", }, ], stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "ROOT_RESOURCE", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "commitment", - type: "bytes32", - }, - ], - name: "commit", - outputs: [], - stateMutability: "nonpayable", - type: "function", }, { - inputs: [ - { - internalType: "bytes32", - name: "commitment", - type: "bytes32", - }, - ], - name: "commitmentAt", - outputs: [ - { - internalType: "uint64", - name: "commitTime", - type: "uint64", - }, - ], - stateMutability: "view", type: "function", - }, - { + name: "revokeRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", - }, - { - internalType: "uint256", - name: "roleBitmap", - type: "uint256", - }, - ], - name: "getAssigneeCount", - outputs: [ - { - internalType: "uint256", - name: "counts", - type: "uint256", - }, - { - internalType: "uint256", - name: "mask", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { internalType: "uint256", - name: "resource", - type: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "grantRoles", outputs: [ { - internalType: "bool", name: "", type: "bool", + internalType: "bool", }, ], stateMutability: "nonpayable", - type: "function", }, { + type: "function", + name: "revokeRootRoles", inputs: [ { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "grantRootRoles", outputs: [ { - internalType: "bool", name: "", type: "bool", + internalType: "bool", }, ], stateMutability: "nonpayable", - type: "function", }, { + type: "function", + name: "roleCount", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", - }, - { internalType: "uint256", - name: "roleBitmap", - type: "uint256", }, ], - name: "hasAssignees", outputs: [ { - internalType: "bool", name: "", - type: "bool", + type: "uint256", + internalType: "uint256", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "roles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", - }, - { internalType: "uint256", - name: "roleBitmap", - type: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "hasRoles", outputs: [ { - internalType: "bool", name: "", - type: "bool", + type: "uint256", + internalType: "uint256", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "setRentPriceOracle", inputs: [ { - internalType: "uint256", - name: "roleBitmap", - type: "uint256", - }, - { - internalType: "address", - name: "account", + name: "oracle", type: "address", + internalType: "contract IRentPriceOracle", }, ], - name: "hasRootRoles", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", + outputs: [], + stateMutability: "nonpayable", }, { + type: "function", + name: "supportsInterface", inputs: [ { - internalType: "string", - name: "label", - type: "string", + name: "interfaceId", + type: "bytes4", + internalType: "bytes4", }, ], - name: "isAvailable", outputs: [ { - internalType: "bool", name: "", type: "bool", + internalType: "bool", }, ], stateMutability: "view", - type: "function", }, { + type: "event", + name: "CommitmentMade", inputs: [ { - internalType: "contract IERC20", - name: "paymentToken", - type: "address", - }, - ], - name: "isPaymentToken", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", + name: "commitment", + type: "bytes32", + indexed: false, + internalType: "bytes32", }, ], - stateMutability: "view", - type: "function", + anonymous: false, }, { + type: "event", + name: "EACRolesChanged", inputs: [ { - internalType: "string", - name: "label", - type: "string", + name: "resource", + type: "uint256", + indexed: true, + internalType: "uint256", }, - ], - name: "isValid", - outputs: [ { - internalType: "bool", - name: "", - type: "bool", + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "oldRoleBitmap", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "newRoleBitmap", + type: "uint256", + indexed: false, + internalType: "uint256", }, ], - stateMutability: "view", - type: "function", + anonymous: false, }, { + type: "event", + name: "NameRegistered", inputs: [ { - internalType: "string", + name: "tokenId", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + { name: "label", type: "string", + indexed: false, + internalType: "string", }, { - internalType: "address", name: "owner", type: "address", + indexed: false, + internalType: "address", }, { - internalType: "bytes32", - name: "secret", - type: "bytes32", - }, - { - internalType: "contract IRegistry", name: "subregistry", type: "address", + indexed: false, + internalType: "contract IRegistry", }, { - internalType: "address", name: "resolver", type: "address", + indexed: false, + internalType: "address", }, { - internalType: "uint64", name: "duration", type: "uint64", + indexed: false, + internalType: "uint64", + }, + { + name: "paymentToken", + type: "address", + indexed: false, + internalType: "contract IERC20", }, { - internalType: "bytes32", name: "referrer", type: "bytes32", + indexed: false, + internalType: "bytes32", }, - ], - name: "makeCommitment", - outputs: [ { - internalType: "bytes32", - name: "", - type: "bytes32", + name: "base", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "premium", + type: "uint256", + indexed: false, + internalType: "uint256", }, ], - stateMutability: "pure", - type: "function", + anonymous: false, }, { + type: "event", + name: "NameRenewed", inputs: [ { - internalType: "string", - name: "label", - type: "string", - }, - { - internalType: "address", - name: "owner", - type: "address", - }, - { - internalType: "bytes32", - name: "secret", - type: "bytes32", + name: "tokenId", + type: "uint256", + indexed: true, + internalType: "uint256", }, { - internalType: "contract IRegistry", - name: "subregistry", - type: "address", + name: "label", + type: "string", + indexed: false, + internalType: "string", }, { - internalType: "address", - name: "resolver", - type: "address", + name: "duration", + type: "uint64", + indexed: false, + internalType: "uint64", }, { - internalType: "uint64", - name: "duration", + name: "newExpiry", type: "uint64", + indexed: false, + internalType: "uint64", }, { - internalType: "contract IERC20", name: "paymentToken", type: "address", + indexed: false, + internalType: "contract IERC20", }, { - internalType: "bytes32", name: "referrer", type: "bytes32", + indexed: false, + internalType: "bytes32", }, - ], - name: "register", - outputs: [ { - internalType: "uint256", - name: "tokenId", + name: "base", type: "uint256", + indexed: false, + internalType: "uint256", }, ], - stateMutability: "nonpayable", - type: "function", + anonymous: false, }, { + type: "event", + name: "PaymentTokenAdded", inputs: [ { - internalType: "string", - name: "label", - type: "string", - }, - { - internalType: "uint64", - name: "duration", - type: "uint64", + name: "paymentToken", + type: "address", + indexed: true, + internalType: "contract IERC20", }, + ], + anonymous: false, + }, + { + type: "event", + name: "PaymentTokenRemoved", + inputs: [ { - internalType: "contract IERC20", name: "paymentToken", type: "address", + indexed: true, + internalType: "contract IERC20", }, + ], + anonymous: false, + }, + { + type: "event", + name: "RentPriceOracleChanged", + inputs: [ { - internalType: "bytes32", - name: "referrer", - type: "bytes32", + name: "oracle", + type: "address", + indexed: false, + internalType: "contract IRentPriceOracle", }, ], - name: "renew", - outputs: [], - stateMutability: "nonpayable", - type: "function", + anonymous: false, }, { + type: "error", + name: "CommitmentTooNew", inputs: [ { - internalType: "string", - name: "label", - type: "string", + name: "commitment", + type: "bytes32", + internalType: "bytes32", }, { - internalType: "address", - name: "owner", - type: "address", + name: "validFrom", + type: "uint64", + internalType: "uint64", }, { - internalType: "uint64", - name: "duration", + name: "blockTimestamp", type: "uint64", + internalType: "uint64", }, + ], + }, + { + type: "error", + name: "CommitmentTooOld", + inputs: [ { - internalType: "contract IERC20", - name: "paymentToken", - type: "address", + name: "commitment", + type: "bytes32", + internalType: "bytes32", }, - ], - name: "rentPrice", - outputs: [ { - internalType: "uint256", - name: "base", - type: "uint256", + name: "validTo", + type: "uint64", + internalType: "uint64", }, { - internalType: "uint256", - name: "premium", - type: "uint256", + name: "blockTimestamp", + type: "uint64", + internalType: "uint64", }, ], - stateMutability: "view", - type: "function", }, { - inputs: [], - name: "rentPriceOracle", - outputs: [ + type: "error", + name: "DurationTooShort", + inputs: [ { - internalType: "contract IRentPriceOracle", - name: "", - type: "address", + name: "duration", + type: "uint64", + internalType: "uint64", + }, + { + name: "minDuration", + type: "uint64", + internalType: "uint64", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "EACCannotGrantRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "revokeRoles", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", }, { + type: "error", + name: "EACCannotRevokeRoles", inputs: [ { + name: "resource", + type: "uint256", internalType: "uint256", + }, + { name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "revokeRootRoles", - outputs: [ + }, + { + type: "error", + name: "EACInvalidAccount", + inputs: [], + }, + { + type: "error", + name: "EACInvalidRoleBitmap", + inputs: [ { - internalType: "bool", - name: "", - type: "bool", + name: "roleBitmap", + type: "uint256", + internalType: "uint256", }, ], - stateMutability: "nonpayable", - type: "function", }, { + type: "error", + name: "EACMaxAssignees", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", + }, + { + name: "role", + type: "uint256", + internalType: "uint256", }, ], - name: "roleCount", - outputs: [ + }, + { + type: "error", + name: "EACMinAssignees", + inputs: [ { + name: "resource", + type: "uint256", internalType: "uint256", - name: "", + }, + { + name: "role", type: "uint256", + internalType: "uint256", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "EACRootResourceNotAllowed", + inputs: [], + }, + { + type: "error", + name: "EACUnauthorizedAccountRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "roles", - outputs: [ + }, + { + type: "error", + name: "InvalidOwner", + inputs: [], + }, + { + type: "error", + name: "MaxCommitmentAgeTooLow", + inputs: [], + }, + { + type: "error", + name: "NameIsAvailable", + inputs: [ { - internalType: "uint256", - name: "", - type: "uint256", + name: "label", + type: "string", + internalType: "string", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "NameNotAvailable", inputs: [ { - internalType: "contract IRentPriceOracle", - name: "oracle", + name: "label", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "NotValid", + inputs: [ + { + name: "label", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "PaymentTokenNotSupported", + inputs: [ + { + name: "paymentToken", type: "address", + internalType: "contract IERC20", }, ], - name: "setRentPriceOracle", - outputs: [], - stateMutability: "nonpayable", - type: "function", }, { + type: "error", + name: "SafeERC20FailedOperation", inputs: [ { - internalType: "bytes4", - name: "interfaceId", - type: "bytes4", + name: "token", + type: "address", + internalType: "address", }, ], - name: "supportsInterface", - outputs: [ + }, + { + type: "error", + name: "UnexpiredCommitmentExists", + inputs: [ { - internalType: "bool", - name: "", - type: "bool", + name: "commitment", + type: "bytes32", + internalType: "bytes32", }, ], - stateMutability: "view", - type: "function", }, ] as const satisfies Abi; diff --git a/packages/datasources/src/abis/ensv2/EnhancedAccessControl.ts b/packages/datasources/src/abis/ensv2/EnhancedAccessControl.ts index adc9322083..e39127ead7 100644 --- a/packages/datasources/src/abis/ensv2/EnhancedAccessControl.ts +++ b/packages/datasources/src/abis/ensv2/EnhancedAccessControl.ts @@ -2,437 +2,450 @@ import type { Abi } from "viem"; export const EnhancedAccessControl = [ { - inputs: [ + type: "function", + name: "HCA_FACTORY", + inputs: [], + outputs: [ { - internalType: "uint256", - name: "resource", - type: "uint256", + name: "", + type: "address", + internalType: "contract IHCAFactoryBasic", }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "ROOT_RESOURCE", + inputs: [], + outputs: [ { - internalType: "uint256", - name: "roleBitmap", + name: "", type: "uint256", - }, - { - internalType: "address", - name: "account", - type: "address", + internalType: "uint256", }, ], - name: "EACCannotGrantRoles", - type: "error", + stateMutability: "view", }, { + type: "function", + name: "getAssigneeCount", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", - }, - { - internalType: "address", - name: "account", - type: "address", + internalType: "uint256", }, ], - name: "EACCannotRevokeRoles", - type: "error", - }, - { - inputs: [], - name: "EACInvalidAccount", - type: "error", - }, - { - inputs: [ + outputs: [ { + name: "counts", + type: "uint256", internalType: "uint256", - name: "roleBitmap", + }, + { + name: "mask", type: "uint256", + internalType: "uint256", }, ], - name: "EACInvalidRoleBitmap", - type: "error", + stateMutability: "view", }, { + type: "function", + name: "grantRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", - name: "role", + name: "roleBitmap", type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", }, ], - name: "EACMaxAssignees", - type: "error", + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", }, { + type: "function", + name: "grantRootRoles", inputs: [ { - internalType: "uint256", - name: "resource", + name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", - name: "role", - type: "uint256", + name: "account", + type: "address", + internalType: "address", }, ], - name: "EACMinAssignees", - type: "error", - }, - { - inputs: [], - name: "EACRootResourceNotAllowed", - type: "error", + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", }, { + type: "function", + name: "hasAssignees", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, + ], + outputs: [ { - internalType: "address", - name: "account", - type: "address", + name: "", + type: "bool", + internalType: "bool", }, ], - name: "EACUnauthorizedAccountRoles", - type: "error", + stateMutability: "view", }, { - anonymous: false, + type: "function", + name: "hasRoles", inputs: [ { - indexed: true, - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - indexed: true, - internalType: "address", - name: "account", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "oldRoleBitmap", + name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - indexed: false, - internalType: "uint256", - name: "newRoleBitmap", - type: "uint256", + name: "account", + type: "address", + internalType: "address", }, ], - name: "EACRolesChanged", - type: "event", - }, - { - inputs: [], - name: "ROOT_RESOURCE", outputs: [ { - internalType: "uint256", name: "", - type: "uint256", + type: "bool", + internalType: "bool", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "hasRootRoles", inputs: [ { - internalType: "uint256", - name: "resource", + name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", - name: "roleBitmap", - type: "uint256", + name: "account", + type: "address", + internalType: "address", }, ], - name: "getAssigneeCount", outputs: [ { - internalType: "uint256", - name: "counts", - type: "uint256", - }, - { - internalType: "uint256", - name: "mask", - type: "uint256", + name: "", + type: "bool", + internalType: "bool", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "revokeRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "grantRoles", outputs: [ { - internalType: "bool", name: "", type: "bool", + internalType: "bool", }, ], stateMutability: "nonpayable", - type: "function", }, { + type: "function", + name: "revokeRootRoles", inputs: [ { - internalType: "uint256", name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "grantRootRoles", outputs: [ { - internalType: "bool", name: "", type: "bool", + internalType: "bool", }, ], stateMutability: "nonpayable", - type: "function", }, { + type: "function", + name: "roleCount", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", - }, - { internalType: "uint256", - name: "roleBitmap", - type: "uint256", }, ], - name: "hasAssignees", outputs: [ { - internalType: "bool", name: "", - type: "bool", + type: "uint256", + internalType: "uint256", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "roles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", - }, - { internalType: "uint256", - name: "roleBitmap", - type: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "hasRoles", outputs: [ { - internalType: "bool", name: "", - type: "bool", + type: "uint256", + internalType: "uint256", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "supportsInterface", inputs: [ { - internalType: "uint256", - name: "roleBitmap", - type: "uint256", - }, - { - internalType: "address", - name: "account", - type: "address", + name: "interfaceId", + type: "bytes4", + internalType: "bytes4", }, ], - name: "hasRootRoles", outputs: [ { - internalType: "bool", name: "", type: "bool", + internalType: "bool", }, ], stateMutability: "view", - type: "function", }, { + type: "event", + name: "EACRolesChanged", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", - }, - { + indexed: true, internalType: "uint256", - name: "roleBitmap", - type: "uint256", }, { - internalType: "address", name: "account", type: "address", + indexed: true, + internalType: "address", }, - ], - name: "revokeRoles", - outputs: [ { - internalType: "bool", - name: "", - type: "bool", + name: "oldRoleBitmap", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "newRoleBitmap", + type: "uint256", + indexed: false, + internalType: "uint256", }, ], - stateMutability: "nonpayable", - type: "function", + anonymous: false, }, { + type: "error", + name: "EACCannotGrantRoles", inputs: [ { + name: "resource", + type: "uint256", internalType: "uint256", + }, + { name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - internalType: "address", name: "account", type: "address", + internalType: "address", }, ], - name: "revokeRootRoles", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", }, { + type: "error", + name: "EACCannotRevokeRoles", inputs: [ { - internalType: "uint256", name: "resource", type: "uint256", + internalType: "uint256", }, - ], - name: "roleCount", - outputs: [ { - internalType: "uint256", - name: "", + name: "roleBitmap", type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "EACInvalidAccount", + inputs: [], + }, + { + type: "error", + name: "EACInvalidRoleBitmap", inputs: [ { + name: "roleBitmap", + type: "uint256", internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "EACMaxAssignees", + inputs: [ + { name: "resource", type: "uint256", + internalType: "uint256", }, { - internalType: "address", - name: "account", - type: "address", + name: "role", + type: "uint256", + internalType: "uint256", }, ], - name: "roles", - outputs: [ + }, + { + type: "error", + name: "EACMinAssignees", + inputs: [ { + name: "resource", + type: "uint256", internalType: "uint256", - name: "", + }, + { + name: "role", type: "uint256", + internalType: "uint256", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "EACRootResourceNotAllowed", + inputs: [], + }, + { + type: "error", + name: "EACUnauthorizedAccountRoles", inputs: [ { - internalType: "bytes4", - name: "interfaceId", - type: "bytes4", + name: "resource", + type: "uint256", + internalType: "uint256", }, - ], - name: "supportsInterface", - outputs: [ { - internalType: "bool", - name: "", - type: "bool", + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", }, ], - stateMutability: "view", - type: "function", }, ] as const satisfies Abi; diff --git a/packages/datasources/src/abis/ensv2/Registry.ts b/packages/datasources/src/abis/ensv2/Registry.ts index 6a70bb7466..8e76e49b0f 100644 --- a/packages/datasources/src/abis/ensv2/Registry.ts +++ b/packages/datasources/src/abis/ensv2/Registry.ts @@ -2,589 +2,1583 @@ import type { Abi } from "viem"; export const Registry = [ { - anonymous: false, + type: "constructor", + inputs: [ + { + name: "hcaFactory", + type: "address", + internalType: "contract IHCAFactoryBasic", + }, + { + name: "metadata", + type: "address", + internalType: "contract IRegistryMetadata", + }, + { + name: "ownerAddress", + type: "address", + internalType: "address", + }, + { + name: "ownerRoles", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "HCA_FACTORY", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract IHCAFactoryBasic", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "METADATA_PROVIDER", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "contract IRegistryMetadata", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "ROOT_RESOURCE", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "balanceOf", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + { + name: "id", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "balanceOfBatch", + inputs: [ + { + name: "accounts", + type: "address[]", + internalType: "address[]", + }, + { + name: "ids", + type: "uint256[]", + internalType: "uint256[]", + }, + ], + outputs: [ + { + name: "", + type: "uint256[]", + internalType: "uint256[]", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getAssigneeCount", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "counts", + type: "uint256", + internalType: "uint256", + }, + { + name: "mask", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getExpiry", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint64", + internalType: "uint64", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getParent", + inputs: [], + outputs: [ + { + name: "parent", + type: "address", + internalType: "contract IRegistry", + }, + { + name: "label", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getResolver", + inputs: [ + { + name: "label", + type: "string", + internalType: "string", + }, + ], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getResource", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getState", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "state", + type: "tuple", + internalType: "struct IPermissionedRegistry.State", + components: [ + { + name: "status", + type: "uint8", + internalType: "enum IPermissionedRegistry.Status", + }, + { + name: "expiry", + type: "uint64", + internalType: "uint64", + }, + { + name: "latestOwner", + type: "address", + internalType: "address", + }, + { + name: "tokenId", + type: "uint256", + internalType: "uint256", + }, + { + name: "resource", + type: "uint256", + internalType: "uint256", + }, + ], + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getStatus", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint8", + internalType: "enum IPermissionedRegistry.Status", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getSubregistry", + inputs: [ + { + name: "label", + type: "string", + internalType: "string", + }, + ], + outputs: [ + { + name: "", + type: "address", + internalType: "contract IRegistry", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getTokenId", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "grantRoles", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "grantRootRoles", + inputs: [ + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "hasAssignees", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "hasRoles", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "hasRootRoles", + inputs: [ + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "isApprovedForAll", + inputs: [ + { + name: "account", + type: "address", + internalType: "address", + }, + { + name: "operator", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "latestOwnerOf", + inputs: [ + { + name: "tokenId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "ownerOf", + inputs: [ + { + name: "tokenId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "register", + inputs: [ + { + name: "label", + type: "string", + internalType: "string", + }, + { + name: "owner", + type: "address", + internalType: "address", + }, + { + name: "registry", + type: "address", + internalType: "contract IRegistry", + }, + { + name: "resolver", + type: "address", + internalType: "address", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "expiry", + type: "uint64", + internalType: "uint64", + }, + ], + outputs: [ + { + name: "tokenId", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "renew", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "newExpiry", + type: "uint64", + internalType: "uint64", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "revokeRoles", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "revokeRootRoles", + inputs: [ + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "roleCount", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "roles", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "safeBatchTransferFrom", inputs: [ { - indexed: true, + name: "from", + type: "address", + internalType: "address", + }, + { + name: "to", + type: "address", internalType: "address", - name: "account", + }, + { + name: "ids", + type: "uint256[]", + internalType: "uint256[]", + }, + { + name: "values", + type: "uint256[]", + internalType: "uint256[]", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "safeTransferFrom", + inputs: [ + { + name: "from", type: "address", + internalType: "address", }, { - indexed: true, + name: "to", + type: "address", internalType: "address", + }, + { + name: "id", + type: "uint256", + internalType: "uint256", + }, + { + name: "value", + type: "uint256", + internalType: "uint256", + }, + { + name: "data", + type: "bytes", + internalType: "bytes", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setApprovalForAll", + inputs: [ + { name: "operator", type: "address", + internalType: "address", }, { - indexed: false, - internalType: "bool", name: "approved", type: "bool", + internalType: "bool", }, ], - name: "ApprovalForAll", - type: "event", + outputs: [], + stateMutability: "nonpayable", }, { - anonymous: false, + type: "function", + name: "setParent", inputs: [ { - indexed: true, + name: "parent", + type: "address", + internalType: "contract IRegistry", + }, + { + name: "label", + type: "string", + internalType: "string", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setResolver", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "resolver", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setSubregistry", + inputs: [ + { + name: "anyId", + type: "uint256", + internalType: "uint256", + }, + { + name: "registry", + type: "address", + internalType: "contract IRegistry", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "supportsInterface", + inputs: [ + { + name: "interfaceId", + type: "bytes4", + internalType: "bytes4", + }, + ], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "unregister", + inputs: [ + { + name: "anyId", + type: "uint256", internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "uri", + inputs: [ + { name: "tokenId", type: "uint256", + internalType: "uint256", }, + ], + outputs: [ { - indexed: false, - internalType: "uint64", - name: "newExpiry", - type: "uint64", + name: "", + type: "string", + internalType: "string", }, + ], + stateMutability: "view", + }, + { + type: "event", + name: "Approval", + inputs: [ { + name: "owner", + type: "address", indexed: true, internalType: "address", - name: "sender", + }, + { + name: "approved", type: "address", + indexed: true, + internalType: "address", + }, + { + name: "tokenId", + type: "uint256", + indexed: true, + internalType: "uint256", }, ], - name: "ExpiryUpdated", - type: "event", + anonymous: false, }, { + type: "event", + name: "ApprovalForAll", + inputs: [ + { + name: "account", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "operator", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "approved", + type: "bool", + indexed: false, + internalType: "bool", + }, + ], anonymous: false, + }, + { + type: "event", + name: "EACRolesChanged", inputs: [ { + name: "resource", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + { + name: "account", + type: "address", indexed: true, + internalType: "address", + }, + { + name: "oldRoleBitmap", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "newRoleBitmap", + type: "uint256", + indexed: false, internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "ExpiryUpdated", + inputs: [ + { name: "tokenId", type: "uint256", + indexed: true, + internalType: "uint256", }, { + name: "newExpiry", + type: "uint64", indexed: true, - internalType: "bytes32", + internalType: "uint64", + }, + { + name: "sender", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "LabelRegistered", + inputs: [ + { + name: "tokenId", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + { name: "labelHash", type: "bytes32", + indexed: true, + internalType: "bytes32", }, { - indexed: false, - internalType: "string", name: "label", type: "string", + indexed: false, + internalType: "string", }, { - indexed: false, - internalType: "address", name: "owner", type: "address", + indexed: false, + internalType: "address", }, { - indexed: false, - internalType: "uint64", name: "expiry", type: "uint64", + indexed: false, + internalType: "uint64", }, { - indexed: true, - internalType: "address", name: "sender", type: "address", + indexed: true, + internalType: "address", }, ], - name: "NameRegistered", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "LabelReserved", inputs: [ { - indexed: true, - internalType: "uint256", name: "tokenId", type: "uint256", + indexed: true, + internalType: "uint256", }, { - indexed: true, - internalType: "bytes32", name: "labelHash", type: "bytes32", + indexed: true, + internalType: "bytes32", }, { - indexed: false, - internalType: "string", name: "label", type: "string", + indexed: false, + internalType: "string", }, { - indexed: false, - internalType: "uint64", name: "expiry", type: "uint64", + indexed: false, + internalType: "uint64", }, { - indexed: true, - internalType: "address", name: "sender", type: "address", + indexed: true, + internalType: "address", }, ], - name: "NameReserved", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "LabelUnregistered", inputs: [ { - indexed: true, - internalType: "uint256", name: "tokenId", type: "uint256", + indexed: true, + internalType: "uint256", }, { - indexed: true, - internalType: "address", name: "sender", type: "address", + indexed: true, + internalType: "address", }, ], - name: "NameUnregistered", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "ParentUpdated", inputs: [ { - indexed: true, - internalType: "contract IRegistry", name: "parent", type: "address", + indexed: true, + internalType: "contract IRegistry", }, { - indexed: false, - internalType: "string", name: "label", type: "string", + indexed: false, + internalType: "string", }, { - indexed: true, - internalType: "address", name: "sender", type: "address", + indexed: true, + internalType: "address", }, ], - name: "ParentUpdated", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "ResolverUpdated", inputs: [ { - indexed: true, - internalType: "uint256", name: "tokenId", type: "uint256", + indexed: true, + internalType: "uint256", }, { - indexed: false, - internalType: "address", name: "resolver", type: "address", - }, - { indexed: true, internalType: "address", + }, + { name: "sender", type: "address", + indexed: true, + internalType: "address", }, ], - name: "ResolverUpdated", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "SubregistryUpdated", inputs: [ { - indexed: true, - internalType: "uint256", name: "tokenId", type: "uint256", + indexed: true, + internalType: "uint256", }, { - indexed: false, - internalType: "contract IRegistry", name: "subregistry", type: "address", + indexed: true, + internalType: "contract IRegistry", }, { - indexed: true, - internalType: "address", name: "sender", type: "address", + indexed: true, + internalType: "address", }, ], - name: "SubregistryUpdated", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "TokenRegenerated", inputs: [ { + name: "oldTokenId", + type: "uint256", indexed: true, internalType: "uint256", - name: "oldTokenId", + }, + { + name: "newTokenId", type: "uint256", + indexed: true, + internalType: "uint256", }, + ], + anonymous: false, + }, + { + type: "event", + name: "TokenResource", + inputs: [ { + name: "tokenId", + type: "uint256", indexed: true, internalType: "uint256", - name: "newTokenId", + }, + { + name: "resource", type: "uint256", + indexed: true, + internalType: "uint256", }, ], - name: "TokenRegenerated", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "TransferBatch", inputs: [ { - indexed: true, - internalType: "address", name: "operator", type: "address", - }, - { indexed: true, internalType: "address", - name: "from", - type: "address", }, { + name: "from", + type: "address", indexed: true, internalType: "address", + }, + { name: "to", type: "address", + indexed: true, + internalType: "address", }, { - indexed: false, - internalType: "uint256[]", name: "ids", type: "uint256[]", - }, - { indexed: false, internalType: "uint256[]", + }, + { name: "values", type: "uint256[]", + indexed: false, + internalType: "uint256[]", }, ], - name: "TransferBatch", - type: "event", + anonymous: false, }, { - anonymous: false, + type: "event", + name: "TransferSingle", inputs: [ { - indexed: true, - internalType: "address", name: "operator", type: "address", - }, - { indexed: true, internalType: "address", + }, + { name: "from", type: "address", + indexed: true, + internalType: "address", }, { + name: "to", + type: "address", indexed: true, internalType: "address", - name: "to", + }, + { + name: "id", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "URI", + inputs: [ + { + name: "value", + type: "string", + indexed: false, + internalType: "string", + }, + { + name: "id", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + ], + anonymous: false, + }, + { + type: "error", + name: "CannotReduceExpiry", + inputs: [ + { + name: "oldExpiry", + type: "uint64", + internalType: "uint64", + }, + { + name: "newExpiry", + type: "uint64", + internalType: "uint64", + }, + ], + }, + { + type: "error", + name: "CannotSetPastExpiry", + inputs: [ + { + name: "expiry", + type: "uint64", + internalType: "uint64", + }, + ], + }, + { + type: "error", + name: "EACCannotGrantRoles", + inputs: [ + { + name: "resource", + type: "uint256", + internalType: "uint256", + }, + { + name: "roleBitmap", + type: "uint256", + internalType: "uint256", + }, + { + name: "account", type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "EACCannotRevokeRoles", + inputs: [ + { + name: "resource", + type: "uint256", + internalType: "uint256", }, { - indexed: false, - internalType: "uint256", - name: "id", + name: "roleBitmap", type: "uint256", + internalType: "uint256", }, { - indexed: false, - internalType: "uint256", - name: "value", - type: "uint256", + name: "account", + type: "address", + internalType: "address", }, ], - name: "TransferSingle", - type: "event", }, { - anonymous: false, + type: "error", + name: "EACInvalidAccount", + inputs: [], + }, + { + type: "error", + name: "EACInvalidRoleBitmap", inputs: [ { - indexed: false, - internalType: "string", - name: "value", - type: "string", - }, - { - indexed: true, - internalType: "uint256", - name: "id", + name: "roleBitmap", type: "uint256", + internalType: "uint256", }, ], - name: "URI", - type: "event", }, { + type: "error", + name: "EACMaxAssignees", inputs: [ { - internalType: "address", - name: "account", - type: "address", + name: "resource", + type: "uint256", + internalType: "uint256", }, { - internalType: "uint256", - name: "id", + name: "role", type: "uint256", + internalType: "uint256", }, ], - name: "balanceOf", - outputs: [ + }, + { + type: "error", + name: "EACMinAssignees", + inputs: [ { + name: "resource", + type: "uint256", internalType: "uint256", - name: "", + }, + { + name: "role", type: "uint256", + internalType: "uint256", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "EACRootResourceNotAllowed", + inputs: [], + }, + { + type: "error", + name: "EACUnauthorizedAccountRoles", inputs: [ { - internalType: "address[]", - name: "accounts", - type: "address[]", + name: "resource", + type: "uint256", + internalType: "uint256", }, { - internalType: "uint256[]", - name: "ids", - type: "uint256[]", + name: "roleBitmap", + type: "uint256", + internalType: "uint256", }, - ], - name: "balanceOfBatch", - outputs: [ { - internalType: "uint256[]", - name: "", - type: "uint256[]", + name: "account", + type: "address", + internalType: "address", }, ], - stateMutability: "view", - type: "function", }, { - inputs: [], - name: "getParent", - outputs: [ + type: "error", + name: "ERC1155InsufficientBalance", + inputs: [ { - internalType: "contract IRegistry", - name: "parent", + name: "sender", type: "address", + internalType: "address", }, { - internalType: "string", - name: "label", - type: "string", + name: "balance", + type: "uint256", + internalType: "uint256", + }, + { + name: "needed", + type: "uint256", + internalType: "uint256", + }, + { + name: "tokenId", + type: "uint256", + internalType: "uint256", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "ERC1155InvalidApprover", inputs: [ { - internalType: "string", - name: "label", - type: "string", - }, - ], - name: "getResolver", - outputs: [ - { - internalType: "address", - name: "", + name: "approver", type: "address", + internalType: "address", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "ERC1155InvalidArrayLength", inputs: [ { - internalType: "string", - name: "label", - type: "string", + name: "idsLength", + type: "uint256", + internalType: "uint256", }, - ], - name: "getSubregistry", - outputs: [ { - internalType: "contract IRegistry", - name: "", - type: "address", + name: "valuesLength", + type: "uint256", + internalType: "uint256", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "ERC1155InvalidOperator", inputs: [ { - internalType: "address", - name: "account", - type: "address", - }, - { - internalType: "address", name: "operator", type: "address", + internalType: "address", }, ], - name: "isApprovedForAll", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "ERC1155InvalidReceiver", inputs: [ { - internalType: "uint256", - name: "id", - type: "uint256", + name: "receiver", + type: "address", + internalType: "address", }, ], - name: "ownerOf", - outputs: [ + }, + { + type: "error", + name: "ERC1155InvalidSender", + inputs: [ { - internalType: "address", - name: "owner", + name: "sender", type: "address", + internalType: "address", }, ], - stateMutability: "view", - type: "function", }, { + type: "error", + name: "ERC1155MissingApprovalForAll", inputs: [ { - internalType: "address", - name: "from", + name: "operator", type: "address", - }, - { internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256[]", - name: "ids", - type: "uint256[]", - }, - { - internalType: "uint256[]", - name: "values", - type: "uint256[]", }, { - internalType: "bytes", - name: "data", - type: "bytes", + name: "owner", + type: "address", + internalType: "address", }, ], - name: "safeBatchTransferFrom", - outputs: [], - stateMutability: "nonpayable", - type: "function", }, { + type: "error", + name: "LabelAlreadyRegistered", inputs: [ { - internalType: "address", - name: "from", - type: "address", + name: "label", + type: "string", + internalType: "string", }, + ], + }, + { + type: "error", + name: "LabelAlreadyReserved", + inputs: [ { - internalType: "address", - name: "to", - type: "address", + name: "label", + type: "string", + internalType: "string", }, + ], + }, + { + type: "error", + name: "LabelExpired", + inputs: [ { - internalType: "uint256", - name: "id", + name: "tokenId", type: "uint256", - }, - { internalType: "uint256", - name: "value", - type: "uint256", - }, - { - internalType: "bytes", - name: "data", - type: "bytes", }, ], - name: "safeTransferFrom", - outputs: [], - stateMutability: "nonpayable", - type: "function", }, { + type: "error", + name: "LabelIsEmpty", + inputs: [], + }, + { + type: "error", + name: "LabelIsTooLong", inputs: [ { - internalType: "address", - name: "operator", - type: "address", - }, - { - internalType: "bool", - name: "approved", - type: "bool", + name: "label", + type: "string", + internalType: "string", }, ], - name: "setApprovalForAll", - outputs: [], - stateMutability: "nonpayable", - type: "function", }, { + type: "error", + name: "TransferDisallowed", inputs: [ { - internalType: "bytes4", - name: "interfaceId", - type: "bytes4", + name: "tokenId", + type: "uint256", + internalType: "uint256", }, - ], - name: "supportsInterface", - outputs: [ { - internalType: "bool", - name: "", - type: "bool", + name: "from", + type: "address", + internalType: "address", }, ], - stateMutability: "view", - type: "function", }, ] as const satisfies Abi; diff --git a/packages/datasources/src/abis/ensv2/UniversalResolverV2.ts b/packages/datasources/src/abis/ensv2/UniversalResolverV2.ts index 060784e0ce..827cac3f3c 100644 --- a/packages/datasources/src/abis/ensv2/UniversalResolverV2.ts +++ b/packages/datasources/src/abis/ensv2/UniversalResolverV2.ts @@ -2,856 +2,875 @@ import type { Abi } from "viem"; export const UniversalResolverV2 = [ { + type: "constructor", inputs: [ { - internalType: "contract IRegistry", name: "root", type: "address", + internalType: "contract IRegistry", }, { - internalType: "contract IGatewayProvider", name: "batchGatewayProvider", type: "address", + internalType: "contract IGatewayProvider", }, ], stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [ - { - internalType: "bytes", - name: "dns", - type: "bytes", - }, - ], - name: "DNSDecodingFailed", - type: "error", - }, - { - inputs: [ - { - internalType: "string", - name: "ens", - type: "string", - }, - ], - name: "DNSEncodingFailed", - type: "error", - }, - { - inputs: [], - name: "EmptyAddress", - type: "error", - }, - { - inputs: [ - { - internalType: "uint16", - name: "status", - type: "uint16", - }, - { - internalType: "string", - name: "message", - type: "string", - }, - ], - name: "HttpError", - type: "error", - }, - { - inputs: [], - name: "InvalidBatchGatewayResponse", - type: "error", - }, - { - inputs: [], - name: "LabelIsEmpty", - type: "error", - }, - { - inputs: [ - { - internalType: "string", - name: "label", - type: "string", - }, - ], - name: "LabelIsTooLong", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "string[]", - name: "urls", - type: "string[]", - }, - { - internalType: "bytes", - name: "callData", - type: "bytes", - }, - { - internalType: "bytes4", - name: "callbackFunction", - type: "bytes4", - }, - { - internalType: "bytes", - name: "extraData", - type: "bytes", - }, - ], - name: "OffchainLookup", - type: "error", - }, - { - inputs: [ - { - internalType: "uint256", - name: "offset", - type: "uint256", - }, - { - internalType: "uint256", - name: "length", - type: "uint256", - }, - ], - name: "OffsetOutOfBoundsError", - type: "error", - }, - { - inputs: [ - { - internalType: "bytes", - name: "errorData", - type: "bytes", - }, - ], - name: "ResolverError", - type: "error", - }, - { - inputs: [ - { - internalType: "bytes", - name: "name", - type: "bytes", - }, - { - internalType: "address", - name: "resolver", - type: "address", - }, - ], - name: "ResolverNotContract", - type: "error", - }, - { - inputs: [ - { - internalType: "bytes", - name: "name", - type: "bytes", - }, - ], - name: "ResolverNotFound", - type: "error", - }, - { - inputs: [ - { - internalType: "string", - name: "primary", - type: "string", - }, - { - internalType: "bytes", - name: "primaryAddress", - type: "bytes", - }, - ], - name: "ReverseAddressMismatch", - type: "error", - }, - { - inputs: [ - { - internalType: "bytes4", - name: "selector", - type: "bytes4", - }, - ], - name: "UnsupportedResolverProfile", - type: "error", }, { - inputs: [], + type: "function", name: "ROOT_REGISTRY", + inputs: [], outputs: [ { - internalType: "contract IRegistry", name: "", type: "address", + internalType: "contract IRegistry", }, ], stateMutability: "view", - type: "function", }, { - inputs: [], + type: "function", name: "batchGatewayProvider", + inputs: [], outputs: [ { - internalType: "contract IGatewayProvider", name: "", type: "address", + internalType: "contract IGatewayProvider", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "ccipBatch", inputs: [ { + name: "batch", + type: "tuple", + internalType: "struct CCIPBatcher.Batch", components: [ { + name: "lookups", + type: "tuple[]", + internalType: "struct CCIPBatcher.Lookup[]", components: [ { - internalType: "address", name: "target", type: "address", + internalType: "address", }, { - internalType: "bytes", name: "call", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "data", type: "bytes", + internalType: "bytes", }, { - internalType: "uint256", name: "flags", type: "uint256", + internalType: "uint256", }, ], - internalType: "struct CCIPBatcher.Lookup[]", - name: "lookups", - type: "tuple[]", }, { - internalType: "string[]", name: "gateways", type: "string[]", + internalType: "string[]", }, ], - internalType: "struct CCIPBatcher.Batch", - name: "batch", - type: "tuple", }, ], - name: "ccipBatch", outputs: [ { + name: "", + type: "tuple", + internalType: "struct CCIPBatcher.Batch", components: [ { + name: "lookups", + type: "tuple[]", + internalType: "struct CCIPBatcher.Lookup[]", components: [ { - internalType: "address", name: "target", type: "address", + internalType: "address", }, { - internalType: "bytes", name: "call", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "data", type: "bytes", + internalType: "bytes", }, { - internalType: "uint256", name: "flags", type: "uint256", + internalType: "uint256", }, ], - internalType: "struct CCIPBatcher.Lookup[]", - name: "lookups", - type: "tuple[]", }, { - internalType: "string[]", name: "gateways", type: "string[]", + internalType: "string[]", }, ], - internalType: "struct CCIPBatcher.Batch", - name: "", - type: "tuple", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "ccipBatchCallback", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "extraData", type: "bytes", + internalType: "bytes", }, ], - name: "ccipBatchCallback", outputs: [ { + name: "batch", + type: "tuple", + internalType: "struct CCIPBatcher.Batch", components: [ { + name: "lookups", + type: "tuple[]", + internalType: "struct CCIPBatcher.Lookup[]", components: [ { - internalType: "address", name: "target", type: "address", + internalType: "address", }, { - internalType: "bytes", name: "call", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "data", type: "bytes", + internalType: "bytes", }, { - internalType: "uint256", name: "flags", type: "uint256", + internalType: "uint256", }, ], - internalType: "struct CCIPBatcher.Lookup[]", - name: "lookups", - type: "tuple[]", }, { - internalType: "string[]", name: "gateways", type: "string[]", + internalType: "string[]", }, ], - internalType: "struct CCIPBatcher.Batch", - name: "batch", - type: "tuple", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "ccipReadCallback", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "extraData", type: "bytes", + internalType: "bytes", }, ], - name: "ccipReadCallback", outputs: [], stateMutability: "view", - type: "function", }, { + type: "function", + name: "findCanonicalName", inputs: [ { - internalType: "contract IRegistry", name: "registry", type: "address", + internalType: "contract IRegistry", }, ], - name: "findCanonicalName", outputs: [ { - internalType: "bytes", name: "", type: "bytes", + internalType: "bytes", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "findCanonicalRegistry", inputs: [ { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, ], - name: "findCanonicalRegistry", outputs: [ { - internalType: "contract IRegistry", name: "", type: "address", + internalType: "contract IRegistry", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "findExactRegistry", inputs: [ { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, ], + outputs: [ + { + name: "", + type: "address", + internalType: "contract IRegistry", + }, + ], + stateMutability: "view", + }, + { + type: "function", name: "findRegistries", + inputs: [ + { + name: "name", + type: "bytes", + internalType: "bytes", + }, + ], outputs: [ { - internalType: "contract IRegistry[]", name: "", type: "address[]", + internalType: "contract IRegistry[]", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "findResolver", inputs: [ { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, ], - name: "findResolver", outputs: [ { - internalType: "address", name: "resolver", type: "address", + internalType: "address", }, { - internalType: "bytes32", name: "node", type: "bytes32", + internalType: "bytes32", }, { - internalType: "uint256", name: "offset", type: "uint256", + internalType: "uint256", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "requireResolver", inputs: [ { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, ], - name: "requireResolver", outputs: [ { + name: "info", + type: "tuple", + internalType: "struct AbstractUniversalResolver.ResolverInfo", components: [ { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, { - internalType: "uint256", name: "offset", type: "uint256", + internalType: "uint256", }, { - internalType: "bytes32", name: "node", type: "bytes32", + internalType: "bytes32", }, { - internalType: "address", name: "resolver", type: "address", + internalType: "address", }, { - internalType: "bool", name: "extended", type: "bool", + internalType: "bool", }, ], - internalType: "struct AbstractUniversalResolver.ResolverInfo", - name: "info", - type: "tuple", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "resolve", inputs: [ { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "data", type: "bytes", + internalType: "bytes", }, ], - name: "resolve", outputs: [ { - internalType: "bytes", name: "", type: "bytes", + internalType: "bytes", }, { - internalType: "address", name: "", type: "address", + internalType: "address", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "resolveBatchCallback", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "extraData", type: "bytes", + internalType: "bytes", }, ], - name: "resolveBatchCallback", outputs: [], stateMutability: "view", - type: "function", }, { + type: "function", + name: "resolveCallback", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "extraData", type: "bytes", + internalType: "bytes", }, ], - name: "resolveCallback", outputs: [ { - internalType: "bytes", name: "", type: "bytes", + internalType: "bytes", }, { - internalType: "address", name: "", type: "address", + internalType: "address", }, ], stateMutability: "pure", - type: "function", }, { + type: "function", + name: "resolveDirectCallback", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "extraData", type: "bytes", + internalType: "bytes", }, ], - name: "resolveDirectCallback", outputs: [], stateMutability: "view", - type: "function", }, { + type: "function", + name: "resolveDirectCallbackError", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "", type: "bytes", + internalType: "bytes", }, ], - name: "resolveDirectCallbackError", outputs: [], stateMutability: "pure", - type: "function", }, { + type: "function", + name: "resolveWithGateways", inputs: [ { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "data", type: "bytes", + internalType: "bytes", }, { - internalType: "string[]", name: "gateways", type: "string[]", + internalType: "string[]", }, ], - name: "resolveWithGateways", outputs: [ { - internalType: "bytes", name: "result", type: "bytes", + internalType: "bytes", }, { - internalType: "address", name: "resolver", type: "address", + internalType: "address", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "resolveWithResolver", inputs: [ { - internalType: "address", name: "resolver", type: "address", + internalType: "address", }, { - internalType: "bytes", name: "name", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "data", type: "bytes", + internalType: "bytes", }, { - internalType: "string[]", name: "gateways", type: "string[]", + internalType: "string[]", }, ], - name: "resolveWithResolver", outputs: [ { - internalType: "bytes", name: "", type: "bytes", + internalType: "bytes", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "reverse", inputs: [ { - internalType: "bytes", name: "lookupAddress", type: "bytes", + internalType: "bytes", }, { - internalType: "uint256", name: "coinType", type: "uint256", + internalType: "uint256", }, ], - name: "reverse", outputs: [ { - internalType: "string", name: "", type: "string", + internalType: "string", }, { - internalType: "address", name: "", type: "address", + internalType: "address", }, { - internalType: "address", name: "", type: "address", + internalType: "address", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "reverseAddressCallback", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "extraData", type: "bytes", + internalType: "bytes", }, ], - name: "reverseAddressCallback", outputs: [ { - internalType: "string", name: "primary", type: "string", + internalType: "string", }, { - internalType: "address", name: "resolver", type: "address", + internalType: "address", }, { - internalType: "address", name: "reverseResolver", type: "address", + internalType: "address", }, ], stateMutability: "pure", - type: "function", }, { + type: "function", + name: "reverseNameCallback", inputs: [ { - internalType: "bytes", name: "response", type: "bytes", + internalType: "bytes", }, { - internalType: "bytes", name: "extraData", type: "bytes", + internalType: "bytes", }, ], - name: "reverseNameCallback", outputs: [ { - internalType: "string", name: "primary", type: "string", + internalType: "string", }, { - internalType: "address", name: "", type: "address", + internalType: "address", }, { - internalType: "address", name: "", type: "address", + internalType: "address", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "reverseWithGateways", inputs: [ { - internalType: "bytes", name: "lookupAddress", type: "bytes", + internalType: "bytes", }, { - internalType: "uint256", name: "coinType", type: "uint256", + internalType: "uint256", }, { - internalType: "string[]", name: "gateways", type: "string[]", + internalType: "string[]", }, ], - name: "reverseWithGateways", outputs: [ { - internalType: "string", name: "primary", type: "string", + internalType: "string", }, { - internalType: "address", name: "resolver", type: "address", + internalType: "address", }, { - internalType: "address", name: "reverseResolver", type: "address", + internalType: "address", }, ], stateMutability: "view", - type: "function", }, { + type: "function", + name: "supportsInterface", inputs: [ { - internalType: "bytes4", name: "interfaceId", type: "bytes4", + internalType: "bytes4", }, ], - name: "supportsInterface", outputs: [ { - internalType: "bool", name: "", type: "bool", + internalType: "bool", }, ], stateMutability: "view", - type: "function", + }, + { + type: "error", + name: "DNSDecodingFailed", + inputs: [ + { + name: "dns", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "DNSEncodingFailed", + inputs: [ + { + name: "ens", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "EmptyAddress", + inputs: [], + }, + { + type: "error", + name: "HttpError", + inputs: [ + { + name: "status", + type: "uint16", + internalType: "uint16", + }, + { + name: "message", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "InvalidBatchGatewayResponse", + inputs: [], + }, + { + type: "error", + name: "LabelIsEmpty", + inputs: [], + }, + { + type: "error", + name: "LabelIsTooLong", + inputs: [ + { + name: "label", + type: "string", + internalType: "string", + }, + ], + }, + { + type: "error", + name: "OffchainLookup", + inputs: [ + { + name: "sender", + type: "address", + internalType: "address", + }, + { + name: "urls", + type: "string[]", + internalType: "string[]", + }, + { + name: "callData", + type: "bytes", + internalType: "bytes", + }, + { + name: "callbackFunction", + type: "bytes4", + internalType: "bytes4", + }, + { + name: "extraData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "OffsetOutOfBoundsError", + inputs: [ + { + name: "offset", + type: "uint256", + internalType: "uint256", + }, + { + name: "length", + type: "uint256", + internalType: "uint256", + }, + ], + }, + { + type: "error", + name: "ResolverError", + inputs: [ + { + name: "errorData", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "ResolverNotContract", + inputs: [ + { + name: "name", + type: "bytes", + internalType: "bytes", + }, + { + name: "resolver", + type: "address", + internalType: "address", + }, + ], + }, + { + type: "error", + name: "ResolverNotFound", + inputs: [ + { + name: "name", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "ReverseAddressMismatch", + inputs: [ + { + name: "primary", + type: "string", + internalType: "string", + }, + { + name: "primaryAddress", + type: "bytes", + internalType: "bytes", + }, + ], + }, + { + type: "error", + name: "UnsupportedResolverProfile", + inputs: [ + { + name: "selector", + type: "bytes4", + internalType: "bytes4", + }, + ], }, ] as const satisfies Abi; diff --git a/packages/integration-test-env/README.md b/packages/integration-test-env/README.md index 0677653d80..e0d3fc1c33 100644 --- a/packages/integration-test-env/README.md +++ b/packages/integration-test-env/README.md @@ -6,12 +6,11 @@ Integration test environment orchestration for ENSNode. Spins up the full ENSNod The current devnet image is pinned to: - ``` -ghcr.io/ensdomains/contracts-v2:main-cb8e11c +ghcr.io/ensdomains/contracts-v2:main-e8696c6 ``` -Update the `DEVNET_IMAGE` constant in the orchestrator source to change the devnet version. +via the `docker-compose.yml` at the monorepo root. ## How It Works @@ -22,7 +21,7 @@ The orchestrator runs a 6-phase pipeline: 3. **ENSIndexer** — starts from source, waits for health 4. **Indexing** — polls until omnichain status reaches "Following" or "Completed" 5. **ENSApi** — starts from source, waits for health -6. **Integration tests** — runs `pnpm test:integration` at monorepo root +6. **Integration tests** — runs `pnpm test:integration` ## Usage From 3d1285151e4ba35c025f1ee89721b15b08538d51 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 13:54:42 -0500 Subject: [PATCH 06/29] feat: implement registration and reservation in latest devnet commit --- .../schema/account.integration.test.ts | 6 +- .../src/graphql-api/schema/registration.ts | 16 +- .../src/lib/ensv2/registry-db-helpers.ts | 17 ++ .../ensv2/handlers/ensv2/ENSv2Registry.ts | 211 +++++++++++------- .../ensv2/handlers/ensv2/ETHRegistrar.ts | 4 +- .../src/ensindexer-abstract/ensv2.schema.ts | 3 +- 6 files changed, 174 insertions(+), 83 deletions(-) create mode 100644 apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts diff --git a/apps/ensapi/src/graphql-api/schema/account.integration.test.ts b/apps/ensapi/src/graphql-api/schema/account.integration.test.ts index 7dd780369b..0125ccc927 100644 --- a/apps/ensapi/src/graphql-api/schema/account.integration.test.ts +++ b/apps/ensapi/src/graphql-api/schema/account.integration.test.ts @@ -25,7 +25,7 @@ import { // via devnet const DEVNET_DEPLOYER: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"; const DEFAULT_OWNER: Address = "0x70997970c51812dc3a010c7d01b50e0d17dc79c8"; -const NEW_OWNER: Address = "0x90f79bf6eb2c4f870365e785982e1f101e93b906"; +const NEW_OWNER_OWNER: Address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"; describe("Account.domains", () => { type AccountDomainsResult = { @@ -66,7 +66,9 @@ describe("Account.domains", () => { }); it("returns domains owned by the new owner", async () => { - const result = await request(AccountDomains, { address: NEW_OWNER }); + const result = await request(AccountDomains, { + address: NEW_OWNER_OWNER, + }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); diff --git a/apps/ensapi/src/graphql-api/schema/registration.ts b/apps/ensapi/src/graphql-api/schema/registration.ts index 7d2faf2daf..b46bcb276e 100644 --- a/apps/ensapi/src/graphql-api/schema/registration.ts +++ b/apps/ensapi/src/graphql-api/schema/registration.ts @@ -54,6 +54,7 @@ export type BaseRegistrarRegistration = RequiredAndNotNull< }; export type ThreeDNSRegistration = Registration; export type ENSv2RegistryRegistration = Registration; +export type ENSv2RegistryReservation = Registration; RegistrationInterfaceRef.implement({ description: @@ -275,7 +276,20 @@ export const ENSv2RegistryRegistrationRef = builder.objectRef (value as RegistrationInterface).type === "ENSv2Registry", + isTypeOf: (value) => (value as RegistrationInterface).type === "ENSv2RegistryRegistration", + fields: (t) => ({}), +}); + +//////////////////////////// +// ENSv2RegistryReservation +//////////////////////////// +export const ENSv2RegistryReservationRef = builder.objectRef( + "ENSv2RegistryReservation", +); +ENSv2RegistryReservationRef.implement({ + description: "ENSv2RegistryReservation represents a Reservation within an ENSv2 Registry.", + interfaces: [RegistrationInterfaceRef], + isTypeOf: (value) => (value as RegistrationInterface).type === "ENSv2RegistryReservation", fields: (t) => ({}), }); diff --git a/apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts b/apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts new file mode 100644 index 0000000000..3c348a8793 --- /dev/null +++ b/apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts @@ -0,0 +1,17 @@ +import { type AccountId, makeRegistryId } from "@ensnode/ensnode-sdk"; + +import { ensIndexerSchema, type IndexingEngineContext } from "@/lib/indexing-engines/ponder"; + +export async function ensureRegistry(context: IndexingEngineContext, registry: AccountId) { + const registryId = makeRegistryId(registry); + + await context.ensDb + .insert(ensIndexerSchema.registry) + .values({ + id: registryId, + ...registry, + }) + .onConflictDoNothing(); + + return registryId; +} diff --git a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts index c54058abaf..820ed96ec4 100644 --- a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts +++ b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts @@ -19,6 +19,7 @@ import { getLatestRegistration, insertLatestRegistration, } from "@/lib/ensv2/registration-db-helpers"; +import { ensureRegistry } from "@/lib/ensv2/registry-db-helpers"; import { getThisAccountId } from "@/lib/get-this-account-id"; import { addOnchainEventListener, @@ -32,8 +33,118 @@ import type { EventWithArgs } from "@/lib/ponder-helpers"; const pluginName = PluginName.ENSv2; export default function () { + /** + * In an ENSv2 Registry, a Reservation is just a Registration with no `owner`, so we unify the + * handling of these two pathways here. + */ + async function handleRegistrationOrReservation({ + context, + event, + }: { + context: IndexingEngineContext; + event: EventWithArgs<{ + tokenId: bigint; + labelHash: LabelHash; + label: string; + // NOTE: marking `owner` as optional to handle both LabelRegistered and LabelReserved events + owner?: Address; + expiry: bigint; + sender: Address; + }>; + }) { + const { tokenId, labelHash, label: _label, owner, expiry, sender: registrant } = event.args; + const label = _label as LiteralLabel; + const isReservation = owner === undefined; + + const registry = getThisAccountId(context, event); + const registryId = makeRegistryId(registry); + const canonicalId = getCanonicalId(tokenId); + const domainId = makeENSv2DomainId(registry, canonicalId); + + // Sanity Check: LabelHash must match Label + if (labelHash !== labelhash(label)) { + throw new Error(`Sanity Check: labelHash !== labelhash(label)\n${toJson(event.args)}`); + } + + // Sanity Check: CanonicalId must match LabelHash + if (canonicalId !== getCanonicalId(hexToBigInt(labelHash))) { + throw new Error( + `Sanity Check: canonicalId !== getCanonicalId(hexToBigInt(labelHash))\n${toJson(event.args)}`, + ); + } + + // ensure Registry + // TODO(signals) — move to NewRegistry and add invariant here + await ensureRegistry(context, registry); + + // ensure discovered Label + await ensureLabel(context, label); + + const registration = await getLatestRegistration(context, domainId); + if (isReservation) { + // Invariant: if this is a Reservation, any existing Registration should be fully expired + if (registration && !isRegistrationFullyExpired(registration, event.block.timestamp)) { + throw new Error( + `Invariant(ENSv2Registry:Label[Registered|Reserved]): Existing unexpired Registration found, expected none or expired.\n${toJson(registration)}`, + ); + } + } else { + // Invariant: if this is a Registration, unless it is a Reservation, it should be fully expired + if ( + registration && + registration.type !== "ENSv2RegistryReservation" && + !isRegistrationFullyExpired(registration, event.block.timestamp) + ) { + throw new Error( + `Invariant(ENSv2Registry:Label[Registered|Reserved]): Existing unexpired Registration found, expected none or expired.\n${toJson(registration)}`, + ); + } + } + + // ensure v2Domain + await context.ensDb + .insert(ensIndexerSchema.v2Domain) + .values({ + id: domainId, + tokenId, + registryId, + labelHash, + // NOTE: we intentionally omit setting ownerId here. either + // a) this is a Registration, in which case a TransferSingle event will be emitted afterwards, or + // b) this is a Reservation, in which there is no owner + }) + // if the v2Domain exists, this is a re-register after expiration and tokenId will have changed + .onConflictDoUpdate({ tokenId }); + + // insert Registration + await ensureAccount(context, registrant); + await insertLatestRegistration(context, { + domainId, + type: isReservation ? "ENSv2RegistryReservation" : "ENSv2RegistryRegistration", + registrarChainId: registry.chainId, + registrarAddress: registry.address, + registrantId: interpretAddress(registrant), + start: event.block.timestamp, + expiry, + eventId: await ensureEvent(context, event), + }); + + // push event to domain history + await ensureDomainEvent(context, event, domainId); + } + + addOnchainEventListener( + namespaceContract(pluginName, "ENSv2Registry:LabelRegistered"), + handleRegistrationOrReservation, + ); + addOnchainEventListener( - namespaceContract(pluginName, "ENSv2Registry:NameRegistered"), + namespaceContract(pluginName, "ENSv2Registry:LabelReserved"), + handleRegistrationOrReservation, + ); + + addOnchainEventListener( + namespaceContract(pluginName, "ENSv2Registry:LabelUnregistered"), async ({ context, event, @@ -41,90 +152,37 @@ export default function () { context: IndexingEngineContext; event: EventWithArgs<{ tokenId: bigint; - labelHash: LabelHash; - label: string; - owner: Address; - expiry: bigint; sender: Address; }>; }) => { - const { tokenId, label: _label, expiry, sender: registrant } = event.args; - const label = _label as LiteralLabel; + const { tokenId, sender: unregistrant } = event.args; - const labelHash = labelhash(label); const registry = getThisAccountId(context, event); - const registryId = makeRegistryId(registry); const canonicalId = getCanonicalId(tokenId); const domainId = makeENSv2DomainId(registry, canonicalId); - // Sanity Check: Canonical Id must match emitted label - if (canonicalId !== getCanonicalId(hexToBigInt(labelhash(label)))) { - throw new Error( - `Sanity Check: Domain's Canonical Id !== getCanonicalId(uint256(labelhash(label)))\n${toJson( - { - tokenId, - canonicalId, - label, - labelHash, - hexToBigInt: hexToBigInt(labelhash(label)), - }, - )}`, - ); - } - - // upsert Registry - // TODO(signals) — move to NewRegistry and add invariant here - await context.ensDb - .insert(ensIndexerSchema.registry) - .values({ - id: registryId, - type: "RegistryContract", - ...registry, - }) - .onConflictDoNothing(); - - // TODO(bridged-registries): upon registry creation, write the registry's canonical domain here - - // ensure discovered Label - await ensureLabel(context, label); - const registration = await getLatestRegistration(context, domainId); - const isFullyExpired = - registration && isRegistrationFullyExpired(registration, event.block.timestamp); - // Invariant: If a Registration for this v2Domain exists, it must be fully expired - if (registration && !isFullyExpired) { + // Invariant: There must be an existing Registration + if (!registration) { + throw new Error(`Invariant(ENSv2Registry:LabelUnregistered): Expected registration.`); + } + + // Invariant: The existing Registration must not be expired. + if (isRegistrationFullyExpired(registration, event.block.timestamp)) { throw new Error( - `Invariant(ENSv2Registry:NameRegistered): Existing unexpired ENSv2Registry Registration found in NameRegistered, expected none or expired.\n${toJson(registration)}`, + `Invariant(ENSv2Registry:LabelUnregistered): Expected unexpired registration but got:\n${toJson(registration)}`, ); } - // insert or update v2Domain - // console.log(`NameRegistered: '${label}'\n ↳ ${domainId}`); + // unregistering a label just immediately sets its expiration to event.block.timestamp, which + // effectively removes it from resolution (which interprets expired names as non-existent) await context.ensDb - .insert(ensIndexerSchema.v2Domain) - .values({ - id: domainId, - tokenId, - registryId, - labelHash, - // NOTE: ownerId omitted, Transfer* events are sole source of ownership - }) - // if the v2Domain exists, this is a re-register after expiration and tokenId may have changed - .onConflictDoUpdate({ tokenId }); - - // insert ENSv2Registry Registration - await ensureAccount(context, registrant); - await insertLatestRegistration(context, { - domainId, - type: "ENSv2Registry", - registrarChainId: registry.chainId, - registrarAddress: registry.address, - registrantId: interpretAddress(registrant), - start: event.block.timestamp, - expiry, - eventId: await ensureEvent(context, event), - }); + .update(ensIndexerSchema.registration, { id: registration.id }) + .set({ expiry: event.block.timestamp }); + + // NOTE(shrugs): PermissionedRegistry also increments eacVersionId and tokenVersionId if there was a + // previous owner, but i'm not sure if we need to handle that detail here // push event to domain history await ensureDomainEvent(context, event, domainId); @@ -153,15 +211,15 @@ export default function () { const registration = await getLatestRegistration(context, domainId); - // Invariant: Registration must exist + // Invariant: There must be an existing Registration if (!registration) { - throw new Error(`Invariant(ENSv2Registry:NameRenewed): Registration expected, none found.`); + throw new Error(`Invariant(ENSv2Registry:ExpiryUpdated): Expected registration.`); } - // Invariant: Registration must not be expired + // Invariant: The existing Registration must not be expired. if (isRegistrationFullyExpired(registration, event.block.timestamp)) { throw new Error( - `Invariant(ENSv2Registry:NameRenewed): Registration found but it is expired:\n${toJson(registration)}`, + `Invariant(ENSv2Registry:ExpiryUpdated): Expected unexpired Registration but got:\n${toJson(registration)}`, ); } @@ -276,13 +334,12 @@ export default function () { const registry = getThisAccountId(context, event); const domainId = makeENSv2DomainId(registry, canonicalId); - // TODO(signals): remove this + // TODO(signals): remove this invariant, since we'll only be indexing Registry contracts const registryId = makeRegistryId(registry); const exists = await context.ensDb.find(ensIndexerSchema.registry, { id: registryId }); if (!exists) return; // no-op non-Registry ERC1155 Transfers - // just update the owner - // any _burns are always followed by a _mint, which would set the owner correctly + // update the Domain's ownerId await context.ensDb .update(ensIndexerSchema.v2Domain, { id: domainId }) .set({ ownerId: interpretAddress(owner) }); diff --git a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts index e72267bfda..0d4c54657e 100644 --- a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts +++ b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts @@ -83,7 +83,7 @@ export default function () { } // Invariant: must be ENSv2Registry Registration - if (registration.type !== "ENSv2Registry") { + if (registration.type !== "ENSv2RegistryRegistration") { throw new Error( `Invariant(ETHRegistrar:NameRegistered): Registration found but not ENSv2Registry Registration:\n${toJson(registration)}`, ); @@ -156,7 +156,7 @@ export default function () { } // Invariant: Must be ENSv2Registry Registration - if (registration.type !== "ENSv2Registry") { + if (registration.type !== "ENSv2RegistryRegistration") { throw new Error( `Invariant(ETHRegistrar:NameRenewed): Registration found but not ENSv2Registry Registration:\n${toJson(registration)}`, ); diff --git a/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts b/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts index aba2dc025a..f9ed677395 100644 --- a/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts +++ b/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts @@ -326,7 +326,8 @@ export const registrationType = onchainEnum("RegistrationType", [ "NameWrapper", "BaseRegistrar", "ThreeDNS", - "ENSv2Registry", + "ENSv2RegistryRegistration", + "ENSv2RegistryReservation", ]); export const registration = onchainTable( From 6941fba0bdf7f4477bd27fc3dcf33f7b66f75f38 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 14:10:54 -0500 Subject: [PATCH 07/29] save unregistrant --- .../src/graphql-api/schema/registration.ts | 22 +++++++++++++++++++ .../ensv2/handlers/ensv2/ENSv2Registry.ts | 8 ++++--- .../src/ensindexer-abstract/ensv2.schema.ts | 7 ++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/apps/ensapi/src/graphql-api/schema/registration.ts b/apps/ensapi/src/graphql-api/schema/registration.ts index b46bcb276e..fb192e2ece 100644 --- a/apps/ensapi/src/graphql-api/schema/registration.ts +++ b/apps/ensapi/src/graphql-api/schema/registration.ts @@ -14,6 +14,7 @@ import { builder } from "@/graphql-api/builder"; import { orderPaginationBy, paginateByInt } from "@/graphql-api/lib/connection-helpers"; import { getModelId } from "@/graphql-api/lib/get-model-id"; import { lazyConnection } from "@/graphql-api/lib/lazy-connection"; +import { AccountRef } from "@/graphql-api/schema/account"; import { AccountIdRef } from "@/graphql-api/schema/account-id"; import { INDEX_PAGINATED_CONNECTION_ARGS } from "@/graphql-api/schema/constants"; import { DomainInterfaceRef } from "@/graphql-api/schema/domain"; @@ -42,6 +43,7 @@ export type RegistrationInterface = Pick< | "registrarChainId" | "registrarAddress" | "registrantId" + | "unregistrantId" | "referrer" >; export type NameWrapperRegistration = RequiredAndNotNull; @@ -131,6 +133,26 @@ RegistrationInterfaceRef.implement({ resolve: (parent) => parent.referrer, }), + /////////////////////////// + // Registration.registrant + /////////////////////////// + registrant: t.field({ + description: "The Registrant of a Registration, if exists.", + type: AccountRef, + nullable: true, + resolve: (parent) => parent.registrantId, + }), + + ///////////////////////////// + // Registration.unregistrant + ///////////////////////////// + unregistrant: t.field({ + description: "The Unregistrant of a Registration, if exists.", + type: AccountRef, + nullable: true, + resolve: (parent) => parent.unregistrantId, + }), + ///////////////////////// // Registration.renewals ///////////////////////// diff --git a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts index 820ed96ec4..27566bf218 100644 --- a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts +++ b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts @@ -177,9 +177,11 @@ export default function () { // unregistering a label just immediately sets its expiration to event.block.timestamp, which // effectively removes it from resolution (which interprets expired names as non-existent) - await context.ensDb - .update(ensIndexerSchema.registration, { id: registration.id }) - .set({ expiry: event.block.timestamp }); + await ensureAccount(context, unregistrant); + await context.ensDb.update(ensIndexerSchema.registration, { id: registration.id }).set({ + expiry: event.block.timestamp, + unregistrantId: interpretAddress(unregistrant), + }); // NOTE(shrugs): PermissionedRegistry also increments eacVersionId and tokenVersionId if there was a // previous owner, but i'm not sure if we need to handle that detail here diff --git a/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts b/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts index f9ed677395..25f81e7e32 100644 --- a/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts +++ b/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts @@ -353,10 +353,13 @@ export const registration = onchainTable( registrarChainId: t.integer().notNull().$type(), registrarAddress: t.hex().notNull().$type
(), - // references registrant + // may reference a registrant registrantId: t.hex().$type
(), - // may have a referrer + // may reference an unregistrant + unregistrantId: t.hex().$type
(), + + // may have referrer data referrer: t.hex().$type(), // may have fuses (NameWrapper, Wrapped BaseRegistrar) From 3bf16a5a335f7d68c9cb652863b7403af60a6f64 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 14:12:30 -0500 Subject: [PATCH 08/29] use distinct ensindexer schema names --- docker-compose.yml | 4 ++-- packages/integration-test-env/src/orchestrator.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 86750baf68..cd3182347f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: # Override environment variables to point to docker instances DATABASE_URL: postgresql://postgres:password@postgres:5432/postgres - DATABASE_SCHEMA: a_unique_schema_name + DATABASE_SCHEMA: docker_compose_ensindexer_schema ENSRAINBOW_URL: http://ensrainbow:3223 env_file: # NOTE: must define apps/ensindexer/.env.local (see apps/ensindexer/.env.local.example) @@ -35,7 +35,7 @@ services: environment: # Override environment variables to point to docker instances DATABASE_URL: postgresql://postgres:password@postgres:5432/postgres - ENSINDEXER_SCHEMA_NAME: a_unique_schema_name + ENSINDEXER_SCHEMA_NAME: docker_compose_ensindexer_schema env_file: # NOTE: must define apps/ensapi/.env.local (see apps/ensapi/.env.local.example) # Copy .env.local.example to .env.local and configure all required values diff --git a/packages/integration-test-env/src/orchestrator.ts b/packages/integration-test-env/src/orchestrator.ts index 7822f1e40c..5c9860909e 100644 --- a/packages/integration-test-env/src/orchestrator.ts +++ b/packages/integration-test-env/src/orchestrator.ts @@ -53,7 +53,7 @@ const ENSAPI_PORT = 4334; // Shared config const ENSRAINBOW_URL = `http://localhost:${ENSRAINBOW_PORT}`; -const ENSINDEXER_SCHEMA_NAME = "ensindexer_0"; +const ENSINDEXER_SCHEMA_NAME = "integration_test_ensindexer_schema"; // Track resources for cleanup const subprocesses: ResultPromise[] = []; From 8784e4616744f8f466c24f94ce255c281feb39fc Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 14:13:09 -0500 Subject: [PATCH 09/29] fix orchestrator --- packages/integration-test-env/src/orchestrator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/integration-test-env/src/orchestrator.ts b/packages/integration-test-env/src/orchestrator.ts index 5c9860909e..753b6c73fd 100644 --- a/packages/integration-test-env/src/orchestrator.ts +++ b/packages/integration-test-env/src/orchestrator.ts @@ -239,12 +239,12 @@ async function main() { // Phase 1: Start Postgres + Devnet via docker-compose log("Starting Postgres and devnet..."); composeEnvironment = await new DockerComposeEnvironment(MONOREPO_ROOT, "docker-compose.yml") - .withWaitStrategy("devnet-1", Wait.forHealthCheck()) - .withWaitStrategy("postgres-1", Wait.forListeningPorts()) + .withWaitStrategy("devnet", Wait.forHealthCheck()) + .withWaitStrategy("postgres", Wait.forListeningPorts()) .withStartupTimeout(120_000) .up(["postgres", "devnet"]); - const postgresContainer = composeEnvironment.getContainer("postgres-1"); + const postgresContainer = composeEnvironment.getContainer("postgres"); const postgresPort = postgresContainer.getMappedPort(5432); const DATABASE_URL = `postgresql://postgres:password@localhost:${postgresPort}/postgres`; log(`Postgres is ready (port ${postgresPort})`); From 35eace6c23d746b3a8850eee32f2b40feab30882 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 14:15:22 -0500 Subject: [PATCH 10/29] fix: remove unnecessary helper --- .../src/lib/ensv2/registry-db-helpers.ts | 17 ----------------- .../ensv2/handlers/ensv2/ENSv2Registry.ts | 6 ++++-- 2 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts diff --git a/apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts b/apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts deleted file mode 100644 index 3c348a8793..0000000000 --- a/apps/ensindexer/src/lib/ensv2/registry-db-helpers.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { type AccountId, makeRegistryId } from "@ensnode/ensnode-sdk"; - -import { ensIndexerSchema, type IndexingEngineContext } from "@/lib/indexing-engines/ponder"; - -export async function ensureRegistry(context: IndexingEngineContext, registry: AccountId) { - const registryId = makeRegistryId(registry); - - await context.ensDb - .insert(ensIndexerSchema.registry) - .values({ - id: registryId, - ...registry, - }) - .onConflictDoNothing(); - - return registryId; -} diff --git a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts index 27566bf218..5e64789640 100644 --- a/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts +++ b/apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts @@ -19,7 +19,6 @@ import { getLatestRegistration, insertLatestRegistration, } from "@/lib/ensv2/registration-db-helpers"; -import { ensureRegistry } from "@/lib/ensv2/registry-db-helpers"; import { getThisAccountId } from "@/lib/get-this-account-id"; import { addOnchainEventListener, @@ -75,7 +74,10 @@ export default function () { // ensure Registry // TODO(signals) — move to NewRegistry and add invariant here - await ensureRegistry(context, registry); + await context.ensDb + .insert(ensIndexerSchema.registry) + .values({ id: registryId, ...registry }) + .onConflictDoNothing(); // ensure discovered Label await ensureLabel(context, label); From 3107c80c9ab45f8d2f80ebc15282b7e5285d4487 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 14:20:55 -0500 Subject: [PATCH 11/29] fix: container name devnet --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index cd3182347f..dfc2e3a10c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -104,6 +104,7 @@ services: - postgres_data:/var/lib/postgresql/data devnet: + container_name: devnet image: ghcr.io/ensdomains/contracts-v2:main-e8696c6 command: ./script/runDevnet.ts --testNames pull_policy: always From d11d0b9ed1151312a55213462fe685ed4248e26e Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 14:23:11 -0500 Subject: [PATCH 12/29] nit: add relation --- packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts b/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts index 25f81e7e32..bb67d283f4 100644 --- a/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts +++ b/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts @@ -407,6 +407,13 @@ export const registration_relations = relations(registration, ({ one, many }) => relationName: "registrant", }), + // has one unregistrant + unregistrant: one(account, { + fields: [registration.unregistrantId], + references: [account.id], + relationName: "unregistrant", + }), + // has many renewals renewals: many(renewal), From 19a467ef723c6b57e7e6cb8e1bc17caa6e800d97 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 17:17:47 -0500 Subject: [PATCH 13/29] add manual instructions --- packages/integration-test-env/README.md | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/packages/integration-test-env/README.md b/packages/integration-test-env/README.md index e0d3fc1c33..34a7a63601 100644 --- a/packages/integration-test-env/README.md +++ b/packages/integration-test-env/README.md @@ -25,12 +25,101 @@ The orchestrator runs a 6-phase pipeline: ## Usage +### Automated + ```sh pnpm start ``` Works both in CI and locally — just make sure the required ports are available (8545, 8000, 3223, 42069, 4334). +### Manual (local development) + +When developing, it's useful to run each service individually so you can restart or iterate on a single piece without tearing down the whole stack. + +#### 1. Start the devnet + +```sh +docker compose up devnet +``` + +Runs the ENS contracts-v2 devnet on port 8545. + +#### 2. Start Postgres + +You may have Postgres running any which way you want, for example with brew services: + +```sh +brew services start postgresql@17 +``` + +or with the local docker compose: +```sh +docker compose up postgres +``` + +#### 3. Start ENSRainbow + +Run via docker compose: + +```sh +docker compose up ensrainbow +``` + +Or run it on the host machine from the repo root: + +```sh +cd apps/ensrainbow && pnpm dev +``` + +with environment variables: + +```env +LOG_LEVEL=error +DB_SCHEMA_VERSION=3 +LABEL_SET_ID=ens-test-env +LABEL_SET_VERSION=0 +``` + +#### 4. Start ENSIndexer + +```sh +cd apps/ensindexer && pnpm dev +``` + +with environment variables: + +```env +DATABASE_SCHEMA=public +NAMESPACE=ens-test-env +PLUGINS=ensv2,protocol-acceleration +``` + +`DATABASE_SCHEMA` can be any valid Postgres schema name — just make sure ENSApi uses the same value. + +#### 5. Start ENSApi + +```sh +cd apps/ensapi && pnpm dev +``` + +with environment variables: + +```env +DATABASE_URL=postgresql://ensnode:ensnode@localhost:5432/ensnode +ENSINDEXER_SCHEMA_NAME=public +``` + +`ENSINDEXER_SCHEMA_NAME` must match the `DATABASE_SCHEMA` used by ENSIndexer above. + +#### 6. Run Integration Tests + +Finally, you can run vitest on the integration tests using: + +```sh +pnpm test:integration +``` + ## License Licensed under the MIT License. See [LICENSE](./LICENSE). From c2f00eb939b0e9c57747bff356e98650d0ad12cc Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 18:39:11 -0500 Subject: [PATCH 14/29] feat: initial enssdk client implementation with omnigraph gql.tada --- .changeset/config.json | 4 + .github/workflows/release.yml | 4 + .github/workflows/test_ci.yml | 24 ++++ .../src/graphql-api/lib/generate-schema.ts | 33 ++++++ .../api/graphql/ensnode-graphql-api.ts | 2 +- apps/ensapi/src/index.ts | 6 +- packages/enscli/LICENSE | 21 ++++ packages/enscli/package.json | 3 +- packages/enskit/LICENSE | 21 ++++ packages/enskit/package.json | 2 +- packages/enssdk/LICENSE | 21 ++++ packages/enssdk/package.json | 55 +++++++++- packages/enssdk/src/core/core.test.ts | 66 +++++++++++ packages/enssdk/src/core/index.ts | 39 +++++++ packages/enssdk/src/omnigraph/graphql.ts | 22 ++++ packages/enssdk/src/omnigraph/index.ts | 50 +++++++++ .../enssdk/src/omnigraph/omnigraph.test.ts | 79 ++++++++++++++ packages/enssdk/tsconfig.json | 16 +++ packages/enssdk/tsup.config.ts | 18 +++ packages/enssdk/vitest.config.ts | 15 +++ packages/ensskills/LICENSE | 21 ++++ packages/ensskills/package.json | 2 +- pnpm-lock.yaml | 103 ++++++++++++++++++ 23 files changed, 617 insertions(+), 10 deletions(-) create mode 100644 apps/ensapi/src/graphql-api/lib/generate-schema.ts create mode 100644 packages/enscli/LICENSE create mode 100644 packages/enskit/LICENSE create mode 100644 packages/enssdk/LICENSE create mode 100644 packages/enssdk/src/core/core.test.ts create mode 100644 packages/enssdk/src/core/index.ts create mode 100644 packages/enssdk/src/omnigraph/graphql.ts create mode 100644 packages/enssdk/src/omnigraph/index.ts create mode 100644 packages/enssdk/src/omnigraph/omnigraph.test.ts create mode 100644 packages/enssdk/tsconfig.json create mode 100644 packages/enssdk/tsup.config.ts create mode 100644 packages/enssdk/vitest.config.ts create mode 100644 packages/ensskills/LICENSE diff --git a/.changeset/config.json b/.changeset/config.json index 81f9844bfb..426bc1dfde 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -11,6 +11,10 @@ "ensrainbow", "ensapi", "fallback-ensapi", + "enssdk", + "enscli", + "enskit", + "ensskills", "@ensnode/datasources", "@ensnode/ensrainbow-sdk", "@ensnode/ensdb-sdk", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9964e9c57..ab17fcf67b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -105,6 +105,10 @@ jobs: or .name == "@ensnode/namehash-ui" or .name == "@ensnode/ponder-sdk" or .name == "@ensnode/ponder-subgraph" + or .name == "enssdk" + or .name == "enskit" + or .name == "enscli" + or .name == "ensskills" )) - name: Filter Published Packages For Lambdas diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 57c3eef201..9f96ede44e 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -89,6 +89,30 @@ jobs: - name: Validate OpenAPI spec with Mintlify run: pnpm dlx mint@^4.1.0 openapi-check docs/docs.ensnode.io/ensapi-openapi.json + graphql-schema-check: + name: "GraphQL Schema Check" + runs-on: blacksmith-4vcpu-ubuntu-2204 + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/setup_node_environment + + - name: Generate gql.tada output and turbo cache + run: pnpm --filter enssdk generate:schema + + - name: Verify generated files are committed + run: | + if ! git diff --quiet packages/enssdk/src/omnigraph/generated/; then + echo "Error: gql.tada generated files are out of sync" + echo "" + echo "The following generated files differ from what is committed:" + git diff --name-status packages/enssdk/src/omnigraph/generated/ + echo "" + echo "To fix, run: pnpm --filter enssdk generate:schema" + echo "Then commit the updated generated files." + exit 1 + fi + echo "GraphQL schema generated files are in sync" + integrity-check: name: "Integrity Check" runs-on: blacksmith-4vcpu-ubuntu-2204 diff --git a/apps/ensapi/src/graphql-api/lib/generate-schema.ts b/apps/ensapi/src/graphql-api/lib/generate-schema.ts new file mode 100644 index 0000000000..5248389ee5 --- /dev/null +++ b/apps/ensapi/src/graphql-api/lib/generate-schema.ts @@ -0,0 +1,33 @@ +import config from "@/config"; + +import { writeFileSync } from "node:fs"; +import { resolve } from "node:path"; + +import { lexicographicSortSchema, printSchema } from "graphql"; + +import { DatasourceNames, maybeGetDatasource } from "@ensnode/datasources"; + +import { makeLogger } from "@/lib/logger"; + +const logger = makeLogger("generate-schema"); + +const MONOREPO_ROOT = resolve(import.meta.dirname, "../../../../../"); +const OUTPUT_PATH = resolve( + MONOREPO_ROOT, + "packages/enssdk/src/omnigraph/generated/schema.graphql", +); + +export async function writeGeneratedSchema() { + const ENSv2Root = maybeGetDatasource(config.namespace, DatasourceNames.ENSv2Root); + if (!ENSv2Root) return; + + const { schema } = await import("@/graphql-api/schema"); + const schemaAsString = printSchema(lexicographicSortSchema(schema)); + + try { + writeFileSync(OUTPUT_PATH, schemaAsString); + logger.info(`Wrote SDL to ${OUTPUT_PATH}`); + } catch (error) { + logger.error(error, `Unable to write SDL to ${OUTPUT_PATH}`); + } +} diff --git a/apps/ensapi/src/handlers/api/graphql/ensnode-graphql-api.ts b/apps/ensapi/src/handlers/api/graphql/ensnode-graphql-api.ts index 04efec0aa1..920e95e609 100644 --- a/apps/ensapi/src/handlers/api/graphql/ensnode-graphql-api.ts +++ b/apps/ensapi/src/handlers/api/graphql/ensnode-graphql-api.ts @@ -6,7 +6,7 @@ import { createApp } from "@/lib/hono-factory"; const app = createApp(); -// 503 if ensv2 plugin not available +// 503 if prerequisites not met app.use(async (c, next) => { const prerequisite = hasGraphqlApiConfigSupport(config.ensIndexerPublicConfig); if (!prerequisite.supported) { diff --git a/apps/ensapi/src/index.ts b/apps/ensapi/src/index.ts index 456716a369..83829ea889 100644 --- a/apps/ensapi/src/index.ts +++ b/apps/ensapi/src/index.ts @@ -7,6 +7,7 @@ import { getReferralLeaderboardEditionsCaches } from "@/cache/referral-leaderboa import { referralProgramEditionConfigSetCache } from "@/cache/referral-program-edition-set.cache"; import { referrerLeaderboardCache } from "@/cache/referrer-leaderboard.cache"; import { redactEnsApiConfig } from "@/config/redact"; +import { writeGeneratedSchema } from "@/graphql-api/lib/generate-schema"; import { sdk } from "@/lib/instrumentation"; import logger from "@/lib/logger"; @@ -26,12 +27,15 @@ const server = serve( async (info) => { logger.info({ config: redactEnsApiConfig(config) }, `ENSApi listening on port ${info.port}`); + // Write the generated graphql schema + await writeGeneratedSchema(); + // Trigger proactive initialization of the indexing status cache at startup. // SWRCache with proactivelyInitialize: true starts fetching immediately upon // construction, but construction is deferred via the lazy proxy until first // access — so we access it explicitly here rather than waiting for the first // user request. - indexingStatusCache.read(); + await indexingStatusCache.read(); }, ); diff --git a/packages/enscli/LICENSE b/packages/enscli/LICENSE new file mode 100644 index 0000000000..24d66814d7 --- /dev/null +++ b/packages/enscli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 NameHash + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/enscli/package.json b/packages/enscli/package.json index 0e1365bd6e..d439590f65 100644 --- a/packages/enscli/package.json +++ b/packages/enscli/package.json @@ -1,6 +1,7 @@ { + "private": true, "name": "enscli", - "version": "0.0.1", + "version": "1.9.0", "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", "repository": { "type": "git", diff --git a/packages/enskit/LICENSE b/packages/enskit/LICENSE new file mode 100644 index 0000000000..24d66814d7 --- /dev/null +++ b/packages/enskit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 NameHash + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/enskit/package.json b/packages/enskit/package.json index 0b9431558a..5e46714b5d 100644 --- a/packages/enskit/package.json +++ b/packages/enskit/package.json @@ -1,6 +1,6 @@ { "name": "enskit", - "version": "0.0.1", + "version": "1.9.0", "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", "repository": { "type": "git", diff --git a/packages/enssdk/LICENSE b/packages/enssdk/LICENSE new file mode 100644 index 0000000000..24d66814d7 --- /dev/null +++ b/packages/enssdk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 NameHash + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/enssdk/package.json b/packages/enssdk/package.json index b953a7db79..e4c00a462b 100644 --- a/packages/enssdk/package.json +++ b/packages/enssdk/package.json @@ -1,12 +1,57 @@ { "name": "enssdk", - "version": "0.0.1", - "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", + "version": "1.9.0", + "type": "module", + "description": "The foundational ENS client library", + "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/namehash/ensnode.git", + "url": "git+https://github.com/namehash/ensnode.git", "directory": "packages/enssdk" }, - "license": "MIT", - "homepage": "https://ensnode.io" + "homepage": "https://ensnode.io", + "keywords": [ + "ENS", + "ENSNode", + "Omnigraph" + ], + "files": [ + "dist" + ], + "exports": { + "./core": "./src/core/index.ts", + "./omnigraph": "./src/omnigraph/index.ts" + }, + "sideEffects": false, + "publishConfig": { + "access": "public", + "exports": { + "./core": { + "types": "./dist/core/index.d.ts", + "default": "./dist/core/index.js" + }, + "./omnigraph": { + "types": "./dist/omnigraph/index.d.ts", + "default": "./dist/omnigraph/index.js" + } + } + }, + "scripts": { + "prepublish": "tsup", + "lint": "biome check --write .", + "lint:ci": "biome ci", + "test": "vitest", + "typecheck": "tsgo --noEmit", + "generate:schema": "gql.tada generate-output && gql.tada turbo" + }, + "dependencies": { + "gql.tada": "^1.8.10", + "graphql": "^16.11.0" + }, + "devDependencies": { + "@ensnode/shared-configs": "workspace:*", + "tsup": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } } diff --git a/packages/enssdk/src/core/core.test.ts b/packages/enssdk/src/core/core.test.ts new file mode 100644 index 0000000000..7b5ba9e061 --- /dev/null +++ b/packages/enssdk/src/core/core.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it, vi } from "vitest"; + +import { createENSSDKClient } from "./index"; + +describe("createENSSDKClient", () => { + it("creates a client with frozen config", () => { + const client = createENSSDKClient({ url: "https://example.com" }); + + expect(client.config.url).toBe("https://example.com"); + expect(client.config.fetch).toBeUndefined(); + expect(Object.isFrozen(client.config)).toBe(true); + }); + + it("preserves custom fetch in config", () => { + const customFetch = vi.fn(); + const client = createENSSDKClient({ + url: "https://example.com", + fetch: customFetch as unknown as typeof globalThis.fetch, + }); + + expect(client.config.fetch).toBe(customFetch); + }); +}); + +describe("extend", () => { + it("adds module properties to the client", () => { + const client = createENSSDKClient({ url: "https://example.com" }).extend( + () => ({ + myModule: { greet: () => "hello" }, + }), + ); + + expect(client.myModule.greet()).toBe("hello"); + expect(client.config.url).toBe("https://example.com"); + }); + + it("passes the base client to the decorator function", () => { + const client = createENSSDKClient({ url: "https://example.com" }).extend( + (base) => ({ + meta: { getUrl: () => base.config.url }, + }), + ); + + expect(client.meta.getUrl()).toBe("https://example.com"); + }); + + it("supports chaining multiple extend calls", () => { + const client = createENSSDKClient({ url: "https://example.com" }) + .extend(() => ({ a: { value: 1 } })) + .extend(() => ({ b: { value: 2 } })); + + expect(client.a.value).toBe(1); + expect(client.b.value).toBe(2); + expect(client.config.url).toBe("https://example.com"); + }); + + it("later extensions can see earlier extensions via the base client", () => { + const client = createENSSDKClient({ url: "https://example.com" }) + .extend(() => ({ a: { value: 42 } })) + .extend((base) => ({ + b: { doubled: () => base.a.value * 2 }, + })); + + expect(client.b.doubled()).toBe(84); + }); +}); diff --git a/packages/enssdk/src/core/index.ts b/packages/enssdk/src/core/index.ts new file mode 100644 index 0000000000..95d4e90149 --- /dev/null +++ b/packages/enssdk/src/core/index.ts @@ -0,0 +1,39 @@ +export interface ENSSDKClientConfig { + /** + * ENSNode instance URL (e.g. "https://api.alpha.ensnode.io") + */ + url: string; + + /** + * Optional fetch implementation (for Node/edge runtimes) + */ + fetch?: typeof globalThis.fetch; +} + +export type ENSSDKClient = TExtended & { + readonly config: Readonly; + extend( + fn: (client: ENSSDKClient) => T, + ): ENSSDKClient; +}; + +export function createENSSDKClient(config: ENSSDKClientConfig): ENSSDKClient { + const frozenConfig = Object.freeze({ ...config }); + + function makeClient(base: Record): ENSSDKClient> { + const client = { + ...base, + config: frozenConfig, + extend(fn: (client: any) => object) { + const extension = fn(client); + return makeClient({ + ...base, + ...(extension as Record), + }); + }, + }; + return client as ENSSDKClient>; + } + + return makeClient({}) as ENSSDKClient; +} diff --git a/packages/enssdk/src/omnigraph/graphql.ts b/packages/enssdk/src/omnigraph/graphql.ts new file mode 100644 index 0000000000..7a6f10ba73 --- /dev/null +++ b/packages/enssdk/src/omnigraph/graphql.ts @@ -0,0 +1,22 @@ +import { initGraphQLTada } from "gql.tada"; + +import type { introspection } from "./generated/graphql-env"; + +// Semantic scalar types — these will eventually be imported from enssdk's +// own type definitions. For now, defined inline. +type Name = string; +type UnixTimestamp = number; + +export const graphql = initGraphQLTada<{ + introspection: introspection; + scalars: { + Name: Name; + BigInt: bigint; + Bytes: `0x${string}`; + ID: string; + UnixTimestamp: UnixTimestamp; + }; +}>(); + +export type { FragmentOf, ResultOf, VariablesOf } from "gql.tada"; +export { readFragment } from "gql.tada"; diff --git a/packages/enssdk/src/omnigraph/index.ts b/packages/enssdk/src/omnigraph/index.ts new file mode 100644 index 0000000000..6217b024cd --- /dev/null +++ b/packages/enssdk/src/omnigraph/index.ts @@ -0,0 +1,50 @@ +import type { TadaDocumentNode } from "gql.tada"; +import type { DocumentNode } from "graphql"; +import { print } from "graphql"; + +import type { ENSSDKClient } from "../core/index"; + +export type { FragmentOf, ResultOf, VariablesOf } from "./graphql"; +export { graphql, readFragment } from "./graphql"; + +type GraphQLDocument = string | DocumentNode | TadaDocumentNode; + +type QueryOptions = { + query: GraphQLDocument; + variables?: V; + signal?: AbortSignal; +}; + +type QueryResult = { + data?: R; + errors?: Array<{ message: string; path?: (string | number)[] }>; +}; + +export interface OmnigraphModule { + omnigraph: { + query(options: QueryOptions): Promise>; + }; +} + +export function omnigraph(client: ENSSDKClient): OmnigraphModule { + const { config } = client; + const _fetch = config.fetch ?? globalThis.fetch; + + return { + omnigraph: { + async query(opts: QueryOptions): Promise> { + const response = await _fetch(`${config.url}/api/omnigraph`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query: typeof opts.query === "string" ? opts.query : print(opts.query), + variables: opts.variables, + }), + signal: opts.signal, + }); + + return response.json() as Promise>; + }, + }, + }; +} diff --git a/packages/enssdk/src/omnigraph/omnigraph.test.ts b/packages/enssdk/src/omnigraph/omnigraph.test.ts new file mode 100644 index 0000000000..c76bba5af2 --- /dev/null +++ b/packages/enssdk/src/omnigraph/omnigraph.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it, vi } from "vitest"; + +import { createENSSDKClient } from "../core/index"; +import { omnigraph } from "./index"; + +describe("omnigraph module", () => { + it("attaches omnigraph namespace to client", () => { + const client = createENSSDKClient({ url: "https://example.com" }).extend(omnigraph); + + expect(client.omnigraph).toBeDefined(); + expect(typeof client.omnigraph.query).toBe("function"); + }); + + it("sends a POST request with string query", async () => { + const mockResponse = { data: { domain: { name: "nick.eth" } } }; + const mockFetch = vi.fn().mockResolvedValue({ + json: () => Promise.resolve(mockResponse), + }); + + const client = createENSSDKClient({ + url: "https://example.com", + fetch: mockFetch as unknown as typeof globalThis.fetch, + }).extend(omnigraph); + + const result = await client.omnigraph.query({ + query: 'query { domain(by: { name: "nick.eth" }) { name } }', + }); + + expect(mockFetch).toHaveBeenCalledWith("https://example.com/api/omnigraph", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query: 'query { domain(by: { name: "nick.eth" }) { name } }', + variables: undefined, + }), + signal: undefined, + }); + + expect(result).toEqual(mockResponse); + }); + + it("sends variables when provided", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + json: () => Promise.resolve({ data: null }), + }); + + const client = createENSSDKClient({ + url: "https://example.com", + fetch: mockFetch as unknown as typeof globalThis.fetch, + }).extend(omnigraph); + + await client.omnigraph.query({ + query: "query($name: String!) { domain(by: { name: $name }) { name } }", + variables: { name: "nick.eth" }, + }); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + expect(body.variables).toEqual({ name: "nick.eth" }); + }); + + it("passes signal for abort support", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + json: () => Promise.resolve({ data: null }), + }); + const controller = new AbortController(); + + const client = createENSSDKClient({ + url: "https://example.com", + fetch: mockFetch as unknown as typeof globalThis.fetch, + }).extend(omnigraph); + + await client.omnigraph.query({ + query: "query { domains { name } }", + signal: controller.signal, + }); + + expect(mockFetch.mock.calls[0][1].signal).toBe(controller.signal); + }); +}); diff --git a/packages/enssdk/tsconfig.json b/packages/enssdk/tsconfig.json new file mode 100644 index 0000000000..983afbfe85 --- /dev/null +++ b/packages/enssdk/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@ensnode/shared-configs/tsconfig.lib.json", + "compilerOptions": { + "rootDir": ".", + "plugins": [ + { + "name": "gql.tada/ts-plugin", + "schema": "./src/omnigraph/generated/schema.graphql", + "tadaOutputLocation": "./src/omnigraph/generated/graphql-env.d.ts", + "tadaTurboLocation": "./src/omnigraph/generated/graphql-cache.d.ts" + } + ] + }, + "include": ["src/**/*"], + "exclude": ["dist"] +} diff --git a/packages/enssdk/tsup.config.ts b/packages/enssdk/tsup.config.ts new file mode 100644 index 0000000000..4c0916ed86 --- /dev/null +++ b/packages/enssdk/tsup.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: { + "core/index": "src/core/index.ts", + "omnigraph/index": "src/omnigraph/index.ts", + }, + platform: "neutral", + format: ["esm"], + target: "es2022", + bundle: true, + splitting: false, + sourcemap: true, + dts: true, + clean: true, + external: ["gql.tada", "graphql"], + outDir: "./dist", +}); diff --git a/packages/enssdk/vitest.config.ts b/packages/enssdk/vitest.config.ts new file mode 100644 index 0000000000..8e3d64e885 --- /dev/null +++ b/packages/enssdk/vitest.config.ts @@ -0,0 +1,15 @@ +import { resolve } from "node:path"; + +import { configDefaults, defineProject } from "vitest/config"; + +export default defineProject({ + resolve: { + alias: { + "@": resolve(__dirname, "./src"), + }, + }, + test: { + environment: "node", + exclude: [...configDefaults.exclude, "**/*.integration.test.ts"], + }, +}); diff --git a/packages/ensskills/LICENSE b/packages/ensskills/LICENSE new file mode 100644 index 0000000000..24d66814d7 --- /dev/null +++ b/packages/ensskills/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 NameHash + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/ensskills/package.json b/packages/ensskills/package.json index efe6f1e583..76a04a0f69 100644 --- a/packages/ensskills/package.json +++ b/packages/ensskills/package.json @@ -1,6 +1,6 @@ { "name": "ensskills", - "version": "0.0.1", + "version": "1.9.0", "description": "Reserved for the ENSNode project by NameHash Labs. See https://ensnode.io", "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f693c35fe5..39884f65b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -842,6 +842,8 @@ importers: specifier: 'catalog:' version: 4.0.5(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.3) + packages/enscli: {} + packages/ensdb-sdk: devDependencies: '@ensnode/ensnode-sdk': @@ -872,6 +874,8 @@ importers: specifier: 'catalog:' version: 4.0.5(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.3) + packages/enskit: {} + packages/ensnode-react: dependencies: '@ensnode/ensnode-sdk': @@ -968,6 +972,30 @@ importers: specifier: 'catalog:' version: 4.0.5(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.3) + packages/enssdk: + dependencies: + gql.tada: + specifier: ^1.8.10 + version: 1.9.1(graphql@16.11.0)(typescript@5.9.3) + graphql: + specifier: ^16.11.0 + version: 16.11.0 + devDependencies: + '@ensnode/shared-configs': + specifier: workspace:* + version: link:../shared-configs + tsup: + specifier: 'catalog:' + version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.5(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.6))(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.3) + + packages/ensskills: {} + packages/integration-test-env: dependencies: '@ensnode/datasources': @@ -1168,6 +1196,20 @@ importers: packages: + '@0no-co/graphql.web@1.2.0': + resolution: {integrity: sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + + '@0no-co/graphqlsp@1.15.2': + resolution: {integrity: sha512-Ys031WnS3sTQQBtRTkQsYnw372OlW72ais4sp0oh2UMPRNyxxnq85zRfU4PIdoy9kWriysPT5BYAkgIxhbonFA==} + peerDependencies: + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + typescript: ^5.0.0 + '@adraffy/ens-normalize@1.11.1': resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} @@ -2279,6 +2321,26 @@ packages: '@formkit/auto-animate@0.9.0': resolution: {integrity: sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==} + '@gql.tada/cli-utils@1.7.2': + resolution: {integrity: sha512-Qbc7hbLvCz6IliIJpJuKJa9p05b2Jona7ov7+qofCsMRxHRZE1kpAmZMvL8JCI4c0IagpIlWNaMizXEQUe8XjQ==} + peerDependencies: + '@0no-co/graphqlsp': ^1.12.13 + '@gql.tada/svelte-support': 1.0.1 + '@gql.tada/vue-support': 1.0.1 + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + typescript: ^5.0.0 + peerDependenciesMeta: + '@gql.tada/svelte-support': + optional: true + '@gql.tada/vue-support': + optional: true + + '@gql.tada/internal@1.0.8': + resolution: {integrity: sha512-XYdxJhtHC5WtZfdDqtKjcQ4d7R1s0d1rnlSs3OcBEUbYiPoJJfZU7tWsVXuv047Z6msvmr4ompJ7eLSK5Km57g==} + peerDependencies: + graphql: ^15.5.0 || ^16.0.0 || ^17.0.0 + typescript: ^5.0.0 + '@graphiql/plugin-doc-explorer@0.4.1': resolution: {integrity: sha512-+ram1dDDGMqJn/f9n5I8E6grTvxcM9JZYt/HhtYLuCvkN8kERI6/E3zBHBshhIUnQZoXioZ03fAzXg7JOn0Kyg==} peerDependencies: @@ -6250,6 +6312,12 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + gql.tada@1.9.1: + resolution: {integrity: sha512-Ijtwgw08aE7l06wK5oj5Msgpk9SUe5FSVcuxU5dHyefdM7fDqLQpA76yHBoq8lPB3MNSir8tznodDknHkm2Z/w==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -9403,6 +9471,16 @@ packages: snapshots: + '@0no-co/graphql.web@1.2.0(graphql@16.11.0)': + optionalDependencies: + graphql: 16.11.0 + + '@0no-co/graphqlsp@1.15.2(graphql@16.11.0)(typescript@5.9.3)': + dependencies: + '@gql.tada/internal': 1.0.8(graphql@16.11.0)(typescript@5.9.3) + graphql: 16.11.0 + typescript: 5.9.3 + '@adraffy/ens-normalize@1.11.1': {} '@alloc/quick-lru@5.2.0': {} @@ -10804,6 +10882,19 @@ snapshots: '@formkit/auto-animate@0.9.0': {} + '@gql.tada/cli-utils@1.7.2(@0no-co/graphqlsp@1.15.2(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(typescript@5.9.3)': + dependencies: + '@0no-co/graphqlsp': 1.15.2(graphql@16.11.0)(typescript@5.9.3) + '@gql.tada/internal': 1.0.8(graphql@16.11.0)(typescript@5.9.3) + graphql: 16.11.0 + typescript: 5.9.3 + + '@gql.tada/internal@1.0.8(graphql@16.11.0)(typescript@5.9.3)': + dependencies: + '@0no-co/graphql.web': 1.2.0(graphql@16.11.0) + graphql: 16.11.0 + typescript: 5.9.3 + '@graphiql/plugin-doc-explorer@0.4.1(@graphiql/react@0.37.1(@types/node@24.10.9)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(graphql@16.11.0)(immer@9.0.21)(react-compiler-runtime@19.1.0-rc.1(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)))(@types/react@19.2.7)(graphql@16.11.0)(immer@9.0.21)(react-compiler-runtime@19.1.0-rc.1(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1))': dependencies: '@graphiql/react': 0.37.1(@types/node@24.10.9)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(graphql@16.11.0)(immer@9.0.21)(react-compiler-runtime@19.1.0-rc.1(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) @@ -15273,6 +15364,18 @@ snapshots: gopd@1.2.0: {} + gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3): + dependencies: + '@0no-co/graphql.web': 1.2.0(graphql@16.11.0) + '@0no-co/graphqlsp': 1.15.2(graphql@16.11.0)(typescript@5.9.3) + '@gql.tada/cli-utils': 1.7.2(@0no-co/graphqlsp@1.15.2(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(typescript@5.9.3) + '@gql.tada/internal': 1.0.8(graphql@16.11.0)(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - '@gql.tada/svelte-support' + - '@gql.tada/vue-support' + - graphql + graceful-fs@4.2.11: {} graphiql-explorer@0.9.0(graphql@16.11.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): From 197e269f304b85f791e880ef236bd55ff8101310 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 18:49:29 -0500 Subject: [PATCH 15/29] feat: add gql.tada codegen pipeline, generated schema artifacts, and CI sync check Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 1 + .../src/graphql-api/lib/generate-schema.ts | 4 +- .../omnigraph/generated/graphql-cache.d.ts | 9 + .../src/omnigraph/generated/graphql-env.d.ts | 133 ++ .../src/omnigraph/generated/schema.graphql | 1162 +++++++++++++++++ packages/enssdk/src/omnigraph/index.ts | 19 +- .../enssdk/src/omnigraph/omnigraph.test.ts | 22 +- 7 files changed, 1331 insertions(+), 19 deletions(-) create mode 100644 packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts create mode 100644 packages/enssdk/src/omnigraph/generated/graphql-env.d.ts create mode 100644 packages/enssdk/src/omnigraph/generated/schema.graphql diff --git a/.gitignore b/.gitignore index e7b333e666..eae700ab06 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ dist # Ponder generated +!packages/enssdk/src/omnigraph/generated/ .ponder #jetbrains elements diff --git a/apps/ensapi/src/graphql-api/lib/generate-schema.ts b/apps/ensapi/src/graphql-api/lib/generate-schema.ts index 5248389ee5..a704255620 100644 --- a/apps/ensapi/src/graphql-api/lib/generate-schema.ts +++ b/apps/ensapi/src/graphql-api/lib/generate-schema.ts @@ -1,6 +1,6 @@ import config from "@/config"; -import { writeFileSync } from "node:fs"; +import { writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { lexicographicSortSchema, printSchema } from "graphql"; @@ -25,7 +25,7 @@ export async function writeGeneratedSchema() { const schemaAsString = printSchema(lexicographicSortSchema(schema)); try { - writeFileSync(OUTPUT_PATH, schemaAsString); + await writeFile(OUTPUT_PATH, schemaAsString); logger.info(`Wrote SDL to ${OUTPUT_PATH}`); } catch (error) { logger.error(error, `Unable to write SDL to ${OUTPUT_PATH}`); diff --git a/packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts b/packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts new file mode 100644 index 0000000000..e664361ffe --- /dev/null +++ b/packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +/* prettier-ignore */ +import type { TadaDocumentNode, $tada } from 'gql.tada'; + +declare module 'gql.tada' { + interface setupCache { + + } +} diff --git a/packages/enssdk/src/omnigraph/generated/graphql-env.d.ts b/packages/enssdk/src/omnigraph/generated/graphql-env.d.ts new file mode 100644 index 0000000000..68dc4c8e32 --- /dev/null +++ b/packages/enssdk/src/omnigraph/generated/graphql-env.d.ts @@ -0,0 +1,133 @@ +/* eslint-disable */ +/* prettier-ignore */ + +export type introspection_types = { + 'Account': { kind: 'OBJECT'; name: 'Account'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Address'; ofType: null; }; } }; 'domains': { name: 'domains'; type: { kind: 'OBJECT'; name: 'AccountDomainsConnection'; ofType: null; } }; 'events': { name: 'events'; type: { kind: 'OBJECT'; name: 'AccountEventsConnection'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Address'; ofType: null; }; } }; 'permissions': { name: 'permissions'; type: { kind: 'OBJECT'; name: 'AccountPermissionsConnection'; ofType: null; } }; 'registryPermissions': { name: 'registryPermissions'; type: { kind: 'OBJECT'; name: 'AccountRegistryPermissionsConnection'; ofType: null; } }; 'resolverPermissions': { name: 'resolverPermissions'; type: { kind: 'OBJECT'; name: 'AccountResolverPermissionsConnection'; ofType: null; } }; }; }; + 'AccountDomainsConnection': { kind: 'OBJECT'; name: 'AccountDomainsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountDomainsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'AccountDomainsConnectionEdge': { kind: 'OBJECT'; name: 'AccountDomainsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; } }; }; }; + 'AccountDomainsWhereInput': { kind: 'INPUT_OBJECT'; name: 'AccountDomainsWhereInput'; isOneOf: false; inputFields: [{ name: 'canonical'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: "false" }, { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; + 'AccountEventsConnection': { kind: 'OBJECT'; name: 'AccountEventsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountEventsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'AccountEventsConnectionEdge': { kind: 'OBJECT'; name: 'AccountEventsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'Event'; ofType: null; } }; }; }; + 'AccountEventsWhereInput': { kind: 'INPUT_OBJECT'; name: 'AccountEventsWhereInput'; isOneOf: false; inputFields: [{ name: 'selector_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Hex'; ofType: null; }; }; }; defaultValue: null }, { name: 'timestamp_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'timestamp_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }]; }; + 'AccountId': { kind: 'OBJECT'; name: 'AccountId'; fields: { 'address': { name: 'address'; type: { kind: 'SCALAR'; name: 'Address'; ofType: null; } }; 'chainId': { name: 'chainId'; type: { kind: 'SCALAR'; name: 'ChainId'; ofType: null; } }; }; }; + 'AccountIdInput': { kind: 'INPUT_OBJECT'; name: 'AccountIdInput'; isOneOf: false; inputFields: [{ name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Address'; ofType: null; }; }; defaultValue: null }, { name: 'chainId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ChainId'; ofType: null; }; }; defaultValue: null }]; }; + 'AccountPermissionsConnection': { kind: 'OBJECT'; name: 'AccountPermissionsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountPermissionsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'AccountPermissionsConnectionEdge': { kind: 'OBJECT'; name: 'AccountPermissionsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'PermissionsUser'; ofType: null; } }; }; }; + 'AccountRegistryPermissionsConnection': { kind: 'OBJECT'; name: 'AccountRegistryPermissionsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountRegistryPermissionsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'AccountRegistryPermissionsConnectionEdge': { kind: 'OBJECT'; name: 'AccountRegistryPermissionsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'RegistryPermissionsUser'; ofType: null; } }; }; }; + 'AccountResolverPermissionsConnection': { kind: 'OBJECT'; name: 'AccountResolverPermissionsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountResolverPermissionsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'AccountResolverPermissionsConnectionEdge': { kind: 'OBJECT'; name: 'AccountResolverPermissionsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'ResolverPermissionsUser'; ofType: null; } }; }; }; + 'Address': unknown; + 'BaseRegistrarRegistration': { kind: 'OBJECT'; name: 'BaseRegistrarRegistration'; fields: { 'baseCost': { name: 'baseCost'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'domain': { name: 'domain'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'expired': { name: 'expired'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'expiry': { name: 'expiry'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RegistrationId'; ofType: null; }; } }; 'isInGracePeriod': { name: 'isInGracePeriod'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'premium': { name: 'premium'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'referrer': { name: 'referrer'; type: { kind: 'SCALAR'; name: 'Hex'; ofType: null; } }; 'registrant': { name: 'registrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'registrar': { name: 'registrar'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'renewals': { name: 'renewals'; type: { kind: 'OBJECT'; name: 'RegistrationRenewalsConnection'; ofType: null; } }; 'start': { name: 'start'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'unregistrant': { name: 'unregistrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'wrapped': { name: 'wrapped'; type: { kind: 'OBJECT'; name: 'WrappedBaseRegistrarRegistration'; ofType: null; } }; }; }; + 'BigInt': unknown; + 'Boolean': unknown; + 'ChainId': unknown; + 'CoinType': unknown; + 'Domain': { kind: 'INTERFACE'; name: 'Domain'; fields: { 'events': { name: 'events'; type: { kind: 'OBJECT'; name: 'DomainEventsConnection'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DomainId'; ofType: null; }; } }; 'label': { name: 'label'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Label'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'Name'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'path': { name: 'path'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; }; } }; 'registration': { name: 'registration'; type: { kind: 'INTERFACE'; name: 'Registration'; ofType: null; } }; 'registrations': { name: 'registrations'; type: { kind: 'OBJECT'; name: 'DomainRegistrationsConnection'; ofType: null; } }; 'resolver': { name: 'resolver'; type: { kind: 'OBJECT'; name: 'Resolver'; ofType: null; } }; 'subdomains': { name: 'subdomains'; type: { kind: 'OBJECT'; name: 'DomainSubdomainsConnection'; ofType: null; } }; }; possibleTypes: 'ENSv1Domain' | 'ENSv2Domain'; }; + 'DomainEventsConnection': { kind: 'OBJECT'; name: 'DomainEventsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'DomainEventsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'DomainEventsConnectionEdge': { kind: 'OBJECT'; name: 'DomainEventsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'Event'; ofType: null; } }; }; }; + 'DomainId': unknown; + 'DomainIdInput': { kind: 'INPUT_OBJECT'; name: 'DomainIdInput'; isOneOf: true; inputFields: [{ name: 'id'; type: { kind: 'SCALAR'; name: 'DomainId'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'SCALAR'; name: 'Name'; ofType: null; }; defaultValue: null }]; }; + 'DomainPermissionsWhereInput': { kind: 'INPUT_OBJECT'; name: 'DomainPermissionsWhereInput'; isOneOf: false; inputFields: [{ name: 'user'; type: { kind: 'SCALAR'; name: 'Address'; ofType: null; }; defaultValue: null }]; }; + 'DomainRegistrationsConnection': { kind: 'OBJECT'; name: 'DomainRegistrationsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'DomainRegistrationsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'DomainRegistrationsConnectionEdge': { kind: 'OBJECT'; name: 'DomainRegistrationsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'INTERFACE'; name: 'Registration'; ofType: null; } }; }; }; + 'DomainSubdomainsConnection': { kind: 'OBJECT'; name: 'DomainSubdomainsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'DomainSubdomainsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'DomainSubdomainsConnectionEdge': { kind: 'OBJECT'; name: 'DomainSubdomainsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; } }; }; }; + 'DomainsOrderBy': { name: 'DomainsOrderBy'; enumValues: 'NAME' | 'REGISTRATION_EXPIRY' | 'REGISTRATION_TIMESTAMP'; }; + 'DomainsOrderInput': { kind: 'INPUT_OBJECT'; name: 'DomainsOrderInput'; isOneOf: false; inputFields: [{ name: 'by'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'DomainsOrderBy'; ofType: null; }; }; defaultValue: null }, { name: 'dir'; type: { kind: 'ENUM'; name: 'OrderDirection'; ofType: null; }; defaultValue: "ASC" }]; }; + 'DomainsWhereInput': { kind: 'INPUT_OBJECT'; name: 'DomainsWhereInput'; isOneOf: false; inputFields: [{ name: 'canonical'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: "false" }, { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; + 'ENSv1Domain': { kind: 'OBJECT'; name: 'ENSv1Domain'; fields: { 'events': { name: 'events'; type: { kind: 'OBJECT'; name: 'DomainEventsConnection'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DomainId'; ofType: null; }; } }; 'label': { name: 'label'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Label'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'Name'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'parent': { name: 'parent'; type: { kind: 'OBJECT'; name: 'ENSv1Domain'; ofType: null; } }; 'path': { name: 'path'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; }; } }; 'registration': { name: 'registration'; type: { kind: 'INTERFACE'; name: 'Registration'; ofType: null; } }; 'registrations': { name: 'registrations'; type: { kind: 'OBJECT'; name: 'DomainRegistrationsConnection'; ofType: null; } }; 'resolver': { name: 'resolver'; type: { kind: 'OBJECT'; name: 'Resolver'; ofType: null; } }; 'rootRegistryOwner': { name: 'rootRegistryOwner'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'subdomains': { name: 'subdomains'; type: { kind: 'OBJECT'; name: 'DomainSubdomainsConnection'; ofType: null; } }; }; }; + 'ENSv2Domain': { kind: 'OBJECT'; name: 'ENSv2Domain'; fields: { 'canonicalId': { name: 'canonicalId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'events': { name: 'events'; type: { kind: 'OBJECT'; name: 'DomainEventsConnection'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DomainId'; ofType: null; }; } }; 'label': { name: 'label'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Label'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'Name'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'path': { name: 'path'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; }; } }; 'permissions': { name: 'permissions'; type: { kind: 'OBJECT'; name: 'ENSv2DomainPermissionsConnection'; ofType: null; } }; 'registration': { name: 'registration'; type: { kind: 'INTERFACE'; name: 'Registration'; ofType: null; } }; 'registrations': { name: 'registrations'; type: { kind: 'OBJECT'; name: 'DomainRegistrationsConnection'; ofType: null; } }; 'registry': { name: 'registry'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Registry'; ofType: null; }; } }; 'resolver': { name: 'resolver'; type: { kind: 'OBJECT'; name: 'Resolver'; ofType: null; } }; 'subdomains': { name: 'subdomains'; type: { kind: 'OBJECT'; name: 'DomainSubdomainsConnection'; ofType: null; } }; 'subregistry': { name: 'subregistry'; type: { kind: 'OBJECT'; name: 'Registry'; ofType: null; } }; 'tokenId': { name: 'tokenId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; }; }; + 'ENSv2DomainPermissionsConnection': { kind: 'OBJECT'; name: 'ENSv2DomainPermissionsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ENSv2DomainPermissionsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'ENSv2DomainPermissionsConnectionEdge': { kind: 'OBJECT'; name: 'ENSv2DomainPermissionsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'PermissionsUser'; ofType: null; } }; }; }; + 'ENSv2RegistryRegistration': { kind: 'OBJECT'; name: 'ENSv2RegistryRegistration'; fields: { 'domain': { name: 'domain'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'expired': { name: 'expired'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'expiry': { name: 'expiry'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RegistrationId'; ofType: null; }; } }; 'referrer': { name: 'referrer'; type: { kind: 'SCALAR'; name: 'Hex'; ofType: null; } }; 'registrant': { name: 'registrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'registrar': { name: 'registrar'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'renewals': { name: 'renewals'; type: { kind: 'OBJECT'; name: 'RegistrationRenewalsConnection'; ofType: null; } }; 'start': { name: 'start'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'unregistrant': { name: 'unregistrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; }; }; + 'ENSv2RegistryReservation': { kind: 'OBJECT'; name: 'ENSv2RegistryReservation'; fields: { 'domain': { name: 'domain'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'expired': { name: 'expired'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'expiry': { name: 'expiry'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RegistrationId'; ofType: null; }; } }; 'referrer': { name: 'referrer'; type: { kind: 'SCALAR'; name: 'Hex'; ofType: null; } }; 'registrant': { name: 'registrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'registrar': { name: 'registrar'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'renewals': { name: 'renewals'; type: { kind: 'OBJECT'; name: 'RegistrationRenewalsConnection'; ofType: null; } }; 'start': { name: 'start'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'unregistrant': { name: 'unregistrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; }; }; + 'Event': { kind: 'OBJECT'; name: 'Event'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Address'; ofType: null; }; } }; 'blockHash': { name: 'blockHash'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Hex'; ofType: null; }; } }; 'blockNumber': { name: 'blockNumber'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'chainId': { name: 'chainId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ChainId'; ofType: null; }; } }; 'data': { name: 'data'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Hex'; ofType: null; }; } }; 'from': { name: 'from'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Address'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'logIndex': { name: 'logIndex'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'timestamp': { name: 'timestamp'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'to': { name: 'to'; type: { kind: 'SCALAR'; name: 'Address'; ofType: null; } }; 'topics': { name: 'topics'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Hex'; ofType: null; }; }; }; } }; 'transactionHash': { name: 'transactionHash'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Hex'; ofType: null; }; } }; 'transactionIndex': { name: 'transactionIndex'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'EventsWhereInput': { kind: 'INPUT_OBJECT'; name: 'EventsWhereInput'; isOneOf: false; inputFields: [{ name: 'from'; type: { kind: 'SCALAR'; name: 'Address'; ofType: null; }; defaultValue: null }, { name: 'selector_in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Hex'; ofType: null; }; }; }; defaultValue: null }, { name: 'timestamp_gte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }, { name: 'timestamp_lte'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; defaultValue: null }]; }; + 'Hex': unknown; + 'ID': unknown; + 'Int': unknown; + 'Label': { kind: 'OBJECT'; name: 'Label'; fields: { 'hash': { name: 'hash'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Hex'; ofType: null; }; } }; 'interpreted': { name: 'interpreted'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; + 'Name': unknown; + 'NameOrNodeInput': { kind: 'INPUT_OBJECT'; name: 'NameOrNodeInput'; isOneOf: true; inputFields: [{ name: 'name'; type: { kind: 'SCALAR'; name: 'Name'; ofType: null; }; defaultValue: null }, { name: 'node'; type: { kind: 'SCALAR'; name: 'Node'; ofType: null; }; defaultValue: null }]; }; + 'NameWrapperRegistration': { kind: 'OBJECT'; name: 'NameWrapperRegistration'; fields: { 'domain': { name: 'domain'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'expired': { name: 'expired'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'expiry': { name: 'expiry'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'fuses': { name: 'fuses'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RegistrationId'; ofType: null; }; } }; 'referrer': { name: 'referrer'; type: { kind: 'SCALAR'; name: 'Hex'; ofType: null; } }; 'registrant': { name: 'registrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'registrar': { name: 'registrar'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'renewals': { name: 'renewals'; type: { kind: 'OBJECT'; name: 'RegistrationRenewalsConnection'; ofType: null; } }; 'start': { name: 'start'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'unregistrant': { name: 'unregistrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; }; }; + 'Node': unknown; + 'OrderDirection': { name: 'OrderDirection'; enumValues: 'ASC' | 'DESC'; }; + 'PageInfo': { kind: 'OBJECT'; name: 'PageInfo'; fields: { 'endCursor': { name: 'endCursor'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'hasNextPage': { name: 'hasNextPage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'hasPreviousPage': { name: 'hasPreviousPage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'startCursor': { name: 'startCursor'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'Permissions': { kind: 'OBJECT'; name: 'Permissions'; fields: { 'contract': { name: 'contract'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'events': { name: 'events'; type: { kind: 'OBJECT'; name: 'PermissionsEventsConnection'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'PermissionsId'; ofType: null; }; } }; 'resources': { name: 'resources'; type: { kind: 'OBJECT'; name: 'PermissionsResourcesConnection'; ofType: null; } }; 'root': { name: 'root'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PermissionsResource'; ofType: null; }; } }; }; }; + 'PermissionsEventsConnection': { kind: 'OBJECT'; name: 'PermissionsEventsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'PermissionsEventsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'PermissionsEventsConnectionEdge': { kind: 'OBJECT'; name: 'PermissionsEventsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'Event'; ofType: null; } }; }; }; + 'PermissionsId': unknown; + 'PermissionsResource': { kind: 'OBJECT'; name: 'PermissionsResource'; fields: { 'contract': { name: 'contract'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'PermissionsResourceId'; ofType: null; }; } }; 'permissions': { name: 'permissions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Permissions'; ofType: null; }; } }; 'resource': { name: 'resource'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'users': { name: 'users'; type: { kind: 'OBJECT'; name: 'PermissionsResourceUsersConnection'; ofType: null; } }; }; }; + 'PermissionsResourceId': unknown; + 'PermissionsResourceUsersConnection': { kind: 'OBJECT'; name: 'PermissionsResourceUsersConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'PermissionsResourceUsersConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'PermissionsResourceUsersConnectionEdge': { kind: 'OBJECT'; name: 'PermissionsResourceUsersConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'PermissionsUser'; ofType: null; } }; }; }; + 'PermissionsResourcesConnection': { kind: 'OBJECT'; name: 'PermissionsResourcesConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'PermissionsResourcesConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'PermissionsResourcesConnectionEdge': { kind: 'OBJECT'; name: 'PermissionsResourcesConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'PermissionsResource'; ofType: null; } }; }; }; + 'PermissionsUser': { kind: 'OBJECT'; name: 'PermissionsUser'; fields: { 'contract': { name: 'contract'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'PermissionsUserId'; ofType: null; }; } }; 'resource': { name: 'resource'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'roles': { name: 'roles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Account'; ofType: null; }; } }; }; }; + 'PermissionsUserId': unknown; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'account': { name: 'account'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'domain': { name: 'domain'; type: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; } }; 'domains': { name: 'domains'; type: { kind: 'OBJECT'; name: 'QueryDomainsConnection'; ofType: null; } }; 'permissions': { name: 'permissions'; type: { kind: 'OBJECT'; name: 'Permissions'; ofType: null; } }; 'registrations': { name: 'registrations'; type: { kind: 'OBJECT'; name: 'QueryRegistrationsConnection'; ofType: null; } }; 'registry': { name: 'registry'; type: { kind: 'OBJECT'; name: 'Registry'; ofType: null; } }; 'resolver': { name: 'resolver'; type: { kind: 'OBJECT'; name: 'Resolver'; ofType: null; } }; 'resolvers': { name: 'resolvers'; type: { kind: 'OBJECT'; name: 'QueryResolversConnection'; ofType: null; } }; 'root': { name: 'root'; type: { kind: 'OBJECT'; name: 'Registry'; ofType: null; } }; 'v1Domains': { name: 'v1Domains'; type: { kind: 'OBJECT'; name: 'QueryV1DomainsConnection'; ofType: null; } }; 'v2Domains': { name: 'v2Domains'; type: { kind: 'OBJECT'; name: 'QueryV2DomainsConnection'; ofType: null; } }; }; }; + 'QueryDomainsConnection': { kind: 'OBJECT'; name: 'QueryDomainsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'QueryDomainsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'QueryDomainsConnectionEdge': { kind: 'OBJECT'; name: 'QueryDomainsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; } }; }; }; + 'QueryRegistrationsConnection': { kind: 'OBJECT'; name: 'QueryRegistrationsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'QueryRegistrationsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'QueryRegistrationsConnectionEdge': { kind: 'OBJECT'; name: 'QueryRegistrationsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'INTERFACE'; name: 'Registration'; ofType: null; } }; }; }; + 'QueryResolversConnection': { kind: 'OBJECT'; name: 'QueryResolversConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'QueryResolversConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'QueryResolversConnectionEdge': { kind: 'OBJECT'; name: 'QueryResolversConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'Resolver'; ofType: null; } }; }; }; + 'QueryV1DomainsConnection': { kind: 'OBJECT'; name: 'QueryV1DomainsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'QueryV1DomainsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'QueryV1DomainsConnectionEdge': { kind: 'OBJECT'; name: 'QueryV1DomainsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'ENSv1Domain'; ofType: null; } }; }; }; + 'QueryV2DomainsConnection': { kind: 'OBJECT'; name: 'QueryV2DomainsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'QueryV2DomainsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'QueryV2DomainsConnectionEdge': { kind: 'OBJECT'; name: 'QueryV2DomainsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'ENSv2Domain'; ofType: null; } }; }; }; + 'Registration': { kind: 'INTERFACE'; name: 'Registration'; fields: { 'domain': { name: 'domain'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'expired': { name: 'expired'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'expiry': { name: 'expiry'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RegistrationId'; ofType: null; }; } }; 'referrer': { name: 'referrer'; type: { kind: 'SCALAR'; name: 'Hex'; ofType: null; } }; 'registrant': { name: 'registrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'registrar': { name: 'registrar'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'renewals': { name: 'renewals'; type: { kind: 'OBJECT'; name: 'RegistrationRenewalsConnection'; ofType: null; } }; 'start': { name: 'start'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'unregistrant': { name: 'unregistrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; }; possibleTypes: 'BaseRegistrarRegistration' | 'ENSv2RegistryRegistration' | 'ENSv2RegistryReservation' | 'NameWrapperRegistration' | 'ThreeDNSRegistration'; }; + 'RegistrationId': unknown; + 'RegistrationRenewalsConnection': { kind: 'OBJECT'; name: 'RegistrationRenewalsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'RegistrationRenewalsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'RegistrationRenewalsConnectionEdge': { kind: 'OBJECT'; name: 'RegistrationRenewalsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'Renewal'; ofType: null; } }; }; }; + 'Registry': { kind: 'OBJECT'; name: 'Registry'; fields: { 'contract': { name: 'contract'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'domains': { name: 'domains'; type: { kind: 'OBJECT'; name: 'RegistryDomainsConnection'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RegistryId'; ofType: null; }; } }; 'parents': { name: 'parents'; type: { kind: 'OBJECT'; name: 'RegistryParentsConnection'; ofType: null; } }; 'permissions': { name: 'permissions'; type: { kind: 'OBJECT'; name: 'Permissions'; ofType: null; } }; }; }; + 'RegistryDomainsConnection': { kind: 'OBJECT'; name: 'RegistryDomainsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'RegistryDomainsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'RegistryDomainsConnectionEdge': { kind: 'OBJECT'; name: 'RegistryDomainsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; } }; }; }; + 'RegistryDomainsWhereInput': { kind: 'INPUT_OBJECT'; name: 'RegistryDomainsWhereInput'; isOneOf: false; inputFields: [{ name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; + 'RegistryId': unknown; + 'RegistryIdInput': { kind: 'INPUT_OBJECT'; name: 'RegistryIdInput'; isOneOf: true; inputFields: [{ name: 'contract'; type: { kind: 'INPUT_OBJECT'; name: 'AccountIdInput'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'RegistryId'; ofType: null; }; defaultValue: null }]; }; + 'RegistryParentsConnection': { kind: 'OBJECT'; name: 'RegistryParentsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'RegistryParentsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'RegistryParentsConnectionEdge': { kind: 'OBJECT'; name: 'RegistryParentsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'ENSv2Domain'; ofType: null; } }; }; }; + 'RegistryPermissionsUser': { kind: 'OBJECT'; name: 'RegistryPermissionsUser'; fields: { 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'PermissionsUserId'; ofType: null; }; } }; 'registry': { name: 'registry'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Registry'; ofType: null; }; } }; 'resource': { name: 'resource'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'roles': { name: 'roles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Account'; ofType: null; }; } }; }; }; + 'Renewal': { kind: 'OBJECT'; name: 'Renewal'; fields: { 'base': { name: 'base'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'duration': { name: 'duration'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RenewalId'; ofType: null; }; } }; 'premium': { name: 'premium'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'referrer': { name: 'referrer'; type: { kind: 'SCALAR'; name: 'Hex'; ofType: null; } }; }; }; + 'RenewalId': unknown; + 'Resolver': { kind: 'OBJECT'; name: 'Resolver'; fields: { 'bridged': { name: 'bridged'; type: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; } }; 'contract': { name: 'contract'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'events': { name: 'events'; type: { kind: 'OBJECT'; name: 'ResolverEventsConnection'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ResolverId'; ofType: null; }; } }; 'permissions': { name: 'permissions'; type: { kind: 'OBJECT'; name: 'Permissions'; ofType: null; } }; 'records': { name: 'records'; type: { kind: 'OBJECT'; name: 'ResolverRecordsConnection'; ofType: null; } }; 'records_': { name: 'records_'; type: { kind: 'OBJECT'; name: 'ResolverRecords'; ofType: null; } }; }; }; + 'ResolverEventsConnection': { kind: 'OBJECT'; name: 'ResolverEventsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ResolverEventsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'ResolverEventsConnectionEdge': { kind: 'OBJECT'; name: 'ResolverEventsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'Event'; ofType: null; } }; }; }; + 'ResolverId': unknown; + 'ResolverIdInput': { kind: 'INPUT_OBJECT'; name: 'ResolverIdInput'; isOneOf: true; inputFields: [{ name: 'contract'; type: { kind: 'INPUT_OBJECT'; name: 'AccountIdInput'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'SCALAR'; name: 'ResolverId'; ofType: null; }; defaultValue: null }]; }; + 'ResolverPermissionsUser': { kind: 'OBJECT'; name: 'ResolverPermissionsUser'; fields: { 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'PermissionsUserId'; ofType: null; }; } }; 'resolver': { name: 'resolver'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Resolver'; ofType: null; }; } }; 'resource': { name: 'resource'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'roles': { name: 'roles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'user': { name: 'user'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Account'; ofType: null; }; } }; }; }; + 'ResolverRecords': { kind: 'OBJECT'; name: 'ResolverRecords'; fields: { 'coinTypes': { name: 'coinTypes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'CoinType'; ofType: null; }; }; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ResolverRecordsId'; ofType: null; }; } }; 'keys': { name: 'keys'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Node'; ofType: null; }; } }; }; }; + 'ResolverRecordsConnection': { kind: 'OBJECT'; name: 'ResolverRecordsConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'ResolverRecordsConnectionEdge'; ofType: null; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'totalCount': { name: 'totalCount'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'ResolverRecordsConnectionEdge': { kind: 'OBJECT'; name: 'ResolverRecordsConnectionEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'ResolverRecords'; ofType: null; } }; }; }; + 'ResolverRecordsId': unknown; + 'String': unknown; + 'SubdomainsWhereInput': { kind: 'INPUT_OBJECT'; name: 'SubdomainsWhereInput'; isOneOf: false; inputFields: [{ name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; + 'ThreeDNSRegistration': { kind: 'OBJECT'; name: 'ThreeDNSRegistration'; fields: { 'domain': { name: 'domain'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Domain'; ofType: null; }; } }; 'event': { name: 'event'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'expired': { name: 'expired'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'expiry': { name: 'expiry'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'RegistrationId'; ofType: null; }; } }; 'referrer': { name: 'referrer'; type: { kind: 'SCALAR'; name: 'Hex'; ofType: null; } }; 'registrant': { name: 'registrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; 'registrar': { name: 'registrar'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AccountId'; ofType: null; }; } }; 'renewals': { name: 'renewals'; type: { kind: 'OBJECT'; name: 'RegistrationRenewalsConnection'; ofType: null; } }; 'start': { name: 'start'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'unregistrant': { name: 'unregistrant'; type: { kind: 'OBJECT'; name: 'Account'; ofType: null; } }; }; }; + 'WrappedBaseRegistrarRegistration': { kind: 'OBJECT'; name: 'WrappedBaseRegistrarRegistration'; fields: { 'fuses': { name: 'fuses'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'tokenId': { name: 'tokenId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; }; }; +}; + +/** An IntrospectionQuery representation of your schema. + * + * @remarks + * This is an introspection of your schema saved as a file by GraphQLSP. + * It will automatically be used by `gql.tada` to infer the types of your GraphQL documents. + * If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to + * instead save to a .ts instead of a .d.ts file. + */ +export type introspection = { + name: never; + query: 'Query'; + mutation: never; + subscription: never; + types: introspection_types; +}; + +import * as gqlTada from 'gql.tada'; + +declare module 'gql.tada' { + interface setupSchema { + introspection: introspection + } +} \ No newline at end of file diff --git a/packages/enssdk/src/omnigraph/generated/schema.graphql b/packages/enssdk/src/omnigraph/generated/schema.graphql new file mode 100644 index 0000000000..c80de6e7c1 --- /dev/null +++ b/packages/enssdk/src/omnigraph/generated/schema.graphql @@ -0,0 +1,1162 @@ +"""Represents an individual Account, keyed by its Address.""" +type Account { + """An EVM Address that uniquely identifies this Account on-chain.""" + address: Address! + + """The Domains that are owned by the Account.""" + domains(after: String, before: String, first: Int, last: Int, order: DomainsOrderInput, where: AccountDomainsWhereInput): AccountDomainsConnection + + """ + All Events for which this Account is the sender (i.e. `Transaction.from`). + """ + events(after: String, before: String, first: Int, last: Int, where: AccountEventsWhereInput): AccountEventsConnection + + """A unique reference to this Account.""" + id: Address! + + """ + The Permissions granted to this Account, optionally filtered to Permissions in a specific contract. + """ + permissions(after: String, before: String, first: Int, in: AccountIdInput, last: Int): AccountPermissionsConnection + + """The Permissions on Registries granted to this Account.""" + registryPermissions(after: String, before: String, first: Int, last: Int): AccountRegistryPermissionsConnection + + """The Permissions on Resolvers granted to this Account.""" + resolverPermissions(after: String, before: String, first: Int, last: Int): AccountResolverPermissionsConnection +} + +type AccountDomainsConnection { + edges: [AccountDomainsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type AccountDomainsConnectionEdge { + cursor: String! + node: Domain +} + +"""Filter for Account.domains query.""" +input AccountDomainsWhereInput { + """ + Optional, defaults to false. If true, filters the set of Domains by those that are Canonical (i.e. reachable by ENS Forward Resolution). + """ + canonical: Boolean = false + + """ + A partial Interpreted Name by which to search the set of Domains. ex: 'example', 'example.', 'example.et'. + """ + name: String +} + +type AccountEventsConnection { + edges: [AccountEventsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type AccountEventsConnectionEdge { + cursor: String! + node: Event +} + +""" +Filter conditions for Account.events (where `from` is implied by the Account). +""" +input AccountEventsWhereInput { + """ + Filter to events whose selector (event signature) is one of the provided values. + """ + selector_in: [Hex!] + + """Filter to events at or after this UnixTimestamp.""" + timestamp_gte: BigInt + + """Filter to events at or before this UnixTimestamp.""" + timestamp_lte: BigInt +} + +"""A CAIP-10 Account ID including chainId and address.""" +type AccountId { + address: Address + chainId: ChainId +} + +"""A CAIP-10 Account ID including chainId and address.""" +input AccountIdInput { + address: Address! + chainId: ChainId! +} + +type AccountPermissionsConnection { + edges: [AccountPermissionsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type AccountPermissionsConnectionEdge { + cursor: String! + node: PermissionsUser +} + +type AccountRegistryPermissionsConnection { + edges: [AccountRegistryPermissionsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type AccountRegistryPermissionsConnectionEdge { + cursor: String! + node: RegistryPermissionsUser +} + +type AccountResolverPermissionsConnection { + edges: [AccountResolverPermissionsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type AccountResolverPermissionsConnectionEdge { + cursor: String! + node: ResolverPermissionsUser +} + +"""Address represents a lowercase (unchecksummed) viem#Address.""" +scalar Address + +""" +A BaseRegistrarRegistration represents a Registration within an ENSv1 BaseRegistrar contract, including those deployed by Basenames and Lineanames. +""" +type BaseRegistrarRegistration implements Registration { + """The `baseCost` for registering this Domain, in wei.""" + baseCost: BigInt + + """The Domain for which this Registration exists.""" + domain: Domain! + + """The Event for which this Registration was created.""" + event: Event! + + """ + Indicates whether this Registration is expired. If the Registration is for an ENSv1Domain, a Registration is only considered `expired` after the Grace Period has elapsed. + """ + expired: Boolean! + + """A UnixTimestamp indicating the Registration's expiry, if exists.""" + expiry: BigInt + + """A unique reference to this Registration.""" + id: RegistrationId! + + """ + Whether this Registration is in the Grace Period (90 days) and can be renewed by the current owner. + """ + isInGracePeriod: Boolean! + + """The `premium` for registering this Domain, in wei.""" + premium: BigInt + + """The extra `referrer` data provided with a Registration, if exists.""" + referrer: Hex + + """The Registrant of a Registration, if exists.""" + registrant: Account + + """The Registrar contract under which this Registration is managed.""" + registrar: AccountId! + + """ + Renewals that have occurred within this Registration's lifespan to extend its expiration. + """ + renewals(after: String, before: String, first: Int, last: Int): RegistrationRenewalsConnection + + """A UnixTimestamp indicating when this Registration was created.""" + start: BigInt! + + """The Unregistrant of a Registration, if exists.""" + unregistrant: Account + + """ + Additional metadata if this BaseRegistrarRegistration is wrapped by the NameWrapper (i.e. in the case of wrapped .eth names). + """ + wrapped: WrappedBaseRegistrarRegistration +} + +"""BigInt represents non-fractional signed whole numeric values.""" +scalar BigInt + +"""ChainId represents a @ensnode/ensnode-sdk#ChainId.""" +scalar ChainId + +"""CoinType represents a @ensnode/ensnode-sdk#CoinType.""" +scalar CoinType + +""" +A Domain represents an individual Label within the ENS namegraph. It may or may not be Canonical. It may be an ENSv1Domain or an ENSv2Domain. +""" +interface Domain { + """All Events associated with this Domain.""" + events(after: String, before: String, first: Int, last: Int, where: EventsWhereInput): DomainEventsConnection + + """A unique reference to this Domain.""" + id: DomainId! + + """The Label this Domain represents in the ENS Namegraph""" + label: Label! + + """ + The Canonical Name for this Domain. If the Domain is not Canonical, then `name` will be null. + """ + name: Name + + """The owner of this Domain.""" + owner: Account + + """ + The Canonical Path from the ENS Root to this Domain. `path` is null if the Domain is not Canonical. + """ + path: [Domain!] + + """The latest Registration for this Domain, if exists.""" + registration: Registration + + """All Registrations for a Domain, including the latest Registration.""" + registrations(after: String, before: String, first: Int, last: Int): DomainRegistrationsConnection + + """ + The Resolver that this Domain has assigned, if any. NOTE that this is the Domain's _assigned_ Resolver, _not_ its _effective_ Resolver, which can only be determined by following ENS Forward Resolution and ENSIP-10. + """ + resolver: Resolver + + """ + All Domains that are direct descendents of this Domain in the namegraph. + """ + subdomains(after: String, before: String, first: Int, last: Int, order: DomainsOrderInput, where: SubdomainsWhereInput): DomainSubdomainsConnection +} + +type DomainEventsConnection { + edges: [DomainEventsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type DomainEventsConnectionEdge { + cursor: String! + node: Event +} + +"""DomainId represents a @ensnode/ensnode-sdk#DomainId.""" +scalar DomainId + +"""Reference a specific Domain.""" +input DomainIdInput @oneOf { + id: DomainId + name: Name +} + +"""Filter Permissions over this Domain by a specific User address.""" +input DomainPermissionsWhereInput { + user: Address +} + +type DomainRegistrationsConnection { + edges: [DomainRegistrationsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type DomainRegistrationsConnectionEdge { + cursor: String! + node: Registration +} + +type DomainSubdomainsConnection { + edges: [DomainSubdomainsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type DomainSubdomainsConnectionEdge { + cursor: String! + node: Domain +} + +"""Fields by which domains can be ordered""" +enum DomainsOrderBy { + NAME + REGISTRATION_EXPIRY + REGISTRATION_TIMESTAMP +} + +""" +Ordering options for domains query. If no order is provided, the default is ASC. +""" +input DomainsOrderInput { + by: DomainsOrderBy! + dir: OrderDirection = ASC +} + +"""Filter for the top-level domains query.""" +input DomainsWhereInput { + """ + Optional, defaults to false. If true, filters the set of Domains by those that are Canonical (i.e. reachable by ENS Forward Resolution). If false, the set of Domains is not filtered, and may include ENSv2 Domains not reachable by ENS Forward Resolution. + """ + canonical: Boolean = false + + """ + A partial Interpreted Name by which to search the set of Domains. ex: 'example', 'example.', 'example.et'. + """ + name: String! +} + +"""An ENSv1Domain represents an ENSv1 Domain.""" +type ENSv1Domain implements Domain { + """All Events associated with this Domain.""" + events(after: String, before: String, first: Int, last: Int, where: EventsWhereInput): DomainEventsConnection + + """A unique reference to this Domain.""" + id: DomainId! + + """The Label this Domain represents in the ENS Namegraph""" + label: Label! + + """ + The Canonical Name for this Domain. If the Domain is not Canonical, then `name` will be null. + """ + name: Name + + """The owner of this Domain.""" + owner: Account + + """The parent Domain of this Domain in the ENSv1 nametree.""" + parent: ENSv1Domain + + """ + The Canonical Path from the ENS Root to this Domain. `path` is null if the Domain is not Canonical. + """ + path: [Domain!] + + """The latest Registration for this Domain, if exists.""" + registration: Registration + + """All Registrations for a Domain, including the latest Registration.""" + registrations(after: String, before: String, first: Int, last: Int): DomainRegistrationsConnection + + """ + The Resolver that this Domain has assigned, if any. NOTE that this is the Domain's _assigned_ Resolver, _not_ its _effective_ Resolver, which can only be determined by following ENS Forward Resolution and ENSIP-10. + """ + resolver: Resolver + + """ + The rootRegistryOwner of this Domain, i.e. the owner() of this Domain within the ENSv1 Registry. + """ + rootRegistryOwner: Account + + """ + All Domains that are direct descendents of this Domain in the namegraph. + """ + subdomains(after: String, before: String, first: Int, last: Int, order: DomainsOrderInput, where: SubdomainsWhereInput): DomainSubdomainsConnection +} + +"""An ENSv2Domain represents an ENSv2 Domain.""" +type ENSv2Domain implements Domain { + """The ENSv2Domain's Canonical Id.""" + canonicalId: BigInt! + + """All Events associated with this Domain.""" + events(after: String, before: String, first: Int, last: Int, where: EventsWhereInput): DomainEventsConnection + + """A unique reference to this Domain.""" + id: DomainId! + + """The Label this Domain represents in the ENS Namegraph""" + label: Label! + + """ + The Canonical Name for this Domain. If the Domain is not Canonical, then `name` will be null. + """ + name: Name + + """The owner of this Domain.""" + owner: Account + + """ + The Canonical Path from the ENS Root to this Domain. `path` is null if the Domain is not Canonical. + """ + path: [Domain!] + + """ + Permissions for this Domain within its Registry, representing the roles granted to users for this Domain's token. + """ + permissions(after: String, before: String, first: Int, last: Int, where: DomainPermissionsWhereInput): ENSv2DomainPermissionsConnection + + """The latest Registration for this Domain, if exists.""" + registration: Registration + + """All Registrations for a Domain, including the latest Registration.""" + registrations(after: String, before: String, first: Int, last: Int): DomainRegistrationsConnection + + """The Registry under which this ENSv2Domain exists.""" + registry: Registry! + + """ + The Resolver that this Domain has assigned, if any. NOTE that this is the Domain's _assigned_ Resolver, _not_ its _effective_ Resolver, which can only be determined by following ENS Forward Resolution and ENSIP-10. + """ + resolver: Resolver + + """ + All Domains that are direct descendents of this Domain in the namegraph. + """ + subdomains(after: String, before: String, first: Int, last: Int, order: DomainsOrderInput, where: SubdomainsWhereInput): DomainSubdomainsConnection + + """The Registry this ENSv2Domain declares as its Subregistry, if exists.""" + subregistry: Registry + + """The ENSv2Domain's current Token Id.""" + tokenId: BigInt! +} + +type ENSv2DomainPermissionsConnection { + edges: [ENSv2DomainPermissionsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type ENSv2DomainPermissionsConnectionEdge { + cursor: String! + node: PermissionsUser +} + +""" +ENSv2RegistryRegistration represents a Registration within an ENSv2 Registry. +""" +type ENSv2RegistryRegistration implements Registration { + """The Domain for which this Registration exists.""" + domain: Domain! + + """The Event for which this Registration was created.""" + event: Event! + + """ + Indicates whether this Registration is expired. If the Registration is for an ENSv1Domain, a Registration is only considered `expired` after the Grace Period has elapsed. + """ + expired: Boolean! + + """A UnixTimestamp indicating the Registration's expiry, if exists.""" + expiry: BigInt + + """A unique reference to this Registration.""" + id: RegistrationId! + + """The extra `referrer` data provided with a Registration, if exists.""" + referrer: Hex + + """The Registrant of a Registration, if exists.""" + registrant: Account + + """The Registrar contract under which this Registration is managed.""" + registrar: AccountId! + + """ + Renewals that have occurred within this Registration's lifespan to extend its expiration. + """ + renewals(after: String, before: String, first: Int, last: Int): RegistrationRenewalsConnection + + """A UnixTimestamp indicating when this Registration was created.""" + start: BigInt! + + """The Unregistrant of a Registration, if exists.""" + unregistrant: Account +} + +""" +ENSv2RegistryReservation represents a Reservation within an ENSv2 Registry. +""" +type ENSv2RegistryReservation implements Registration { + """The Domain for which this Registration exists.""" + domain: Domain! + + """The Event for which this Registration was created.""" + event: Event! + + """ + Indicates whether this Registration is expired. If the Registration is for an ENSv1Domain, a Registration is only considered `expired` after the Grace Period has elapsed. + """ + expired: Boolean! + + """A UnixTimestamp indicating the Registration's expiry, if exists.""" + expiry: BigInt + + """A unique reference to this Registration.""" + id: RegistrationId! + + """The extra `referrer` data provided with a Registration, if exists.""" + referrer: Hex + + """The Registrant of a Registration, if exists.""" + registrant: Account + + """The Registrar contract under which this Registration is managed.""" + registrar: AccountId! + + """ + Renewals that have occurred within this Registration's lifespan to extend its expiration. + """ + renewals(after: String, before: String, first: Int, last: Int): RegistrationRenewalsConnection + + """A UnixTimestamp indicating when this Registration was created.""" + start: BigInt! + + """The Unregistrant of a Registration, if exists.""" + unregistrant: Account +} + +""" +An Event represents a discrete Log Event that was emitted on an EVM chain, including associated metadata. +""" +type Event { + """Identifies the contract by which this Event was emitted.""" + address: Address! + + """Identifies the Block within which this Event was emitted.""" + blockHash: Hex! + + """The block number within which this Event was emitted.""" + blockNumber: BigInt! + + """The ChainId upon which this Event was emitted.""" + chainId: ChainId! + + """The non-indexed data of this Event's log.""" + data: Hex! + + """ + Identifies the sender of the Transaction within which this Event was emitted. + """ + from: Address! + + """A unique reference to this Event.""" + id: ID! + + """The index of this Event's log within the Block.""" + logIndex: Int! + + """ + The UnixTimestamp indicating the moment in which this Event was emitted. + """ + timestamp: BigInt! + + """ + Identifies the recipient of the Transaction within which this Event was emitted. Null if the transaction deployed a contract. + """ + to: Address + + """The indexed topics of this Event's log.""" + topics: [Hex!]! + + """Identifies the Transaction within which this Event was emitted.""" + transactionHash: Hex! + + """The index of the Transaction within the Block.""" + transactionIndex: Int! +} + +"""Filter conditions for an events connection.""" +input EventsWhereInput { + """Filter to events sent by this address.""" + from: Address + + """ + Filter to events whose selector (event signature) is one of the provided values. + """ + selector_in: [Hex!] + + """Filter to events at or after this UnixTimestamp.""" + timestamp_gte: BigInt + + """Filter to events at or before this UnixTimestamp.""" + timestamp_lte: BigInt +} + +"""Hex represents viem#Hex.""" +scalar Hex + +""" +Represents a Label within ENS, providing its hash and interpreted representation. +""" +type Label { + """ + The Label's LabelHash + (@see https://ensnode.io/docs/reference/terminology#labels-labelhashes-labelhash-function) + """ + hash: Hex! + + """ + The Label represented as an Interpreted Label. This is either a normalized Literal Label or an Encoded LabelHash. + (@see https://ensnode.io/docs/reference/terminology#interpreted-label) + """ + interpreted: String! +} + +"""Name represents a @ensnode/ensnode-sdk#InterpretedName.""" +scalar Name + +"""Constructs a reference to a specific Node via one of `name` or `node`.""" +input NameOrNodeInput @oneOf { + name: Name + node: Node +} + +""" +A NameWrapperRegistration represents a Registration initiated by the ENSv1 NameWrapper. +""" +type NameWrapperRegistration implements Registration { + """The Domain for which this Registration exists.""" + domain: Domain! + + """The Event for which this Registration was created.""" + event: Event! + + """ + Indicates whether this Registration is expired. If the Registration is for an ENSv1Domain, a Registration is only considered `expired` after the Grace Period has elapsed. + """ + expired: Boolean! + + """A UnixTimestamp indicating the Registration's expiry, if exists.""" + expiry: BigInt + + """The Fuses for this Registration's Domain in the NameWrapper.""" + fuses: Int! + + """A unique reference to this Registration.""" + id: RegistrationId! + + """The extra `referrer` data provided with a Registration, if exists.""" + referrer: Hex + + """The Registrant of a Registration, if exists.""" + registrant: Account + + """The Registrar contract under which this Registration is managed.""" + registrar: AccountId! + + """ + Renewals that have occurred within this Registration's lifespan to extend its expiration. + """ + renewals(after: String, before: String, first: Int, last: Int): RegistrationRenewalsConnection + + """A UnixTimestamp indicating when this Registration was created.""" + start: BigInt! + + """The Unregistrant of a Registration, if exists.""" + unregistrant: Account +} + +"""Node represents a @ensnode/ensnode-sdk#Node.""" +scalar Node + +"""Sort direction""" +enum OrderDirection { + ASC + DESC +} + +type PageInfo { + endCursor: String + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String +} + +"""Permissions""" +type Permissions { + """The contract within which these Permissions are granted.""" + contract: AccountId! + + """All Events associated with these Permissions.""" + events(after: String, before: String, first: Int, last: Int, where: EventsWhereInput): PermissionsEventsConnection + + """A unique reference to this Permission.""" + id: PermissionsId! + + """All PermissionResources managed by this contract.""" + resources(after: String, before: String, first: Int, last: Int): PermissionsResourcesConnection + + """The Root Resource.""" + root: PermissionsResource! +} + +type PermissionsEventsConnection { + edges: [PermissionsEventsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type PermissionsEventsConnectionEdge { + cursor: String! + node: Event +} + +"""PermissionsId represents a @ensnode/ensnode-sdk#PermissionsId.""" +scalar PermissionsId + +"""PermissionsResource""" +type PermissionsResource { + """The contract within which these Permissions are granted.""" + contract: AccountId! + + """A unique reference to this PermissionsResource.""" + id: PermissionsResourceId! + + """The Permissions within which this Resource is managed.""" + permissions: Permissions! + + """Identifies the Resource that this PermissionsResource represents.""" + resource: BigInt! + + """The PermissionUsers who have Roles within this Resource.""" + users(after: String, before: String, first: Int, last: Int): PermissionsResourceUsersConnection +} + +""" +PermissionsResourceId represents a @ensnode/ensnode-sdk#PermissionsResourceId. +""" +scalar PermissionsResourceId + +type PermissionsResourceUsersConnection { + edges: [PermissionsResourceUsersConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type PermissionsResourceUsersConnectionEdge { + cursor: String! + node: PermissionsUser +} + +type PermissionsResourcesConnection { + edges: [PermissionsResourcesConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type PermissionsResourcesConnectionEdge { + cursor: String! + node: PermissionsResource +} + +"""PermissionsUser""" +type PermissionsUser { + """The contract within which these Permissions are granted.""" + contract: AccountId! + + """A unique reference to this PermissionsUser.""" + id: PermissionsUserId! + + """The Resource that this user has Roles within.""" + resource: BigInt! + + """The Roles this User has been granted within this Resource.""" + roles: BigInt! + + """The User for whom these Roles are granted.""" + user: Account! +} + +"""PermissionsUserId represents a @ensnode/ensnode-sdk#PermissionsUserId.""" +scalar PermissionsUserId + +type Query { + """Identify an Account by Address.""" + account(address: Address!): Account + + """Identify a Domain by Name or DomainId""" + domain(by: DomainIdInput!): Domain + + """Find Domains by Name.""" + domains(after: String, before: String, first: Int, last: Int, order: DomainsOrderInput, where: DomainsWhereInput!): QueryDomainsConnection + + """Find Permissions in a contract by AccountId.""" + permissions(for: AccountIdInput!): Permissions + + """TODO""" + registrations(after: String, before: String, first: Int, last: Int): QueryRegistrationsConnection + + """Identify a Registry by ID or AccountId.""" + registry(by: RegistryIdInput!): Registry + + """Identify a Resolver by ID or AccountId.""" + resolver(by: ResolverIdInput!): Resolver + + """TODO""" + resolvers(after: String, before: String, first: Int, last: Int): QueryResolversConnection + + """The ENSv2 Root Registry, if exists.""" + root: Registry + + """TODO""" + v1Domains(after: String, before: String, first: Int, last: Int): QueryV1DomainsConnection + + """TODO""" + v2Domains(after: String, before: String, first: Int, last: Int): QueryV2DomainsConnection +} + +type QueryDomainsConnection { + edges: [QueryDomainsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type QueryDomainsConnectionEdge { + cursor: String! + node: Domain +} + +type QueryRegistrationsConnection { + edges: [QueryRegistrationsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type QueryRegistrationsConnectionEdge { + cursor: String! + node: Registration +} + +type QueryResolversConnection { + edges: [QueryResolversConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type QueryResolversConnectionEdge { + cursor: String! + node: Resolver +} + +type QueryV1DomainsConnection { + edges: [QueryV1DomainsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type QueryV1DomainsConnectionEdge { + cursor: String! + node: ENSv1Domain +} + +type QueryV2DomainsConnection { + edges: [QueryV2DomainsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type QueryV2DomainsConnectionEdge { + cursor: String! + node: ENSv2Domain +} + +""" +A Registration represents a Domain's registration status within the various registries. +""" +interface Registration { + """The Domain for which this Registration exists.""" + domain: Domain! + + """The Event for which this Registration was created.""" + event: Event! + + """ + Indicates whether this Registration is expired. If the Registration is for an ENSv1Domain, a Registration is only considered `expired` after the Grace Period has elapsed. + """ + expired: Boolean! + + """A UnixTimestamp indicating the Registration's expiry, if exists.""" + expiry: BigInt + + """A unique reference to this Registration.""" + id: RegistrationId! + + """The extra `referrer` data provided with a Registration, if exists.""" + referrer: Hex + + """The Registrant of a Registration, if exists.""" + registrant: Account + + """The Registrar contract under which this Registration is managed.""" + registrar: AccountId! + + """ + Renewals that have occurred within this Registration's lifespan to extend its expiration. + """ + renewals(after: String, before: String, first: Int, last: Int): RegistrationRenewalsConnection + + """A UnixTimestamp indicating when this Registration was created.""" + start: BigInt! + + """The Unregistrant of a Registration, if exists.""" + unregistrant: Account +} + +"""RegistrationId represents a @ensnode/ensnode-sdk#RegistrationId.""" +scalar RegistrationId + +type RegistrationRenewalsConnection { + edges: [RegistrationRenewalsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type RegistrationRenewalsConnectionEdge { + cursor: String! + node: Renewal +} + +"""A Registry represents an ENSv2 Registry contract.""" +type Registry { + """Contract metadata for this Registry""" + contract: AccountId! + + """The Domains managed by this Registry.""" + domains(after: String, before: String, first: Int, last: Int, order: DomainsOrderInput, where: RegistryDomainsWhereInput): RegistryDomainsConnection + + """A unique reference to this Registry.""" + id: RegistryId! + + """The Domains for which this Registry is a Subregistry.""" + parents(after: String, before: String, first: Int, last: Int): RegistryParentsConnection + + """The Permissions managed by this Registry.""" + permissions: Permissions +} + +type RegistryDomainsConnection { + edges: [RegistryDomainsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type RegistryDomainsConnectionEdge { + cursor: String! + node: Domain +} + +"""Filter for Registry.domains query.""" +input RegistryDomainsWhereInput { + """ + A partial Interpreted Name by which to filter Domains in this Registry. + """ + name: String +} + +"""RegistryId represents a @ensnode/ensnode-sdk#RegistryId.""" +scalar RegistryId + +"""Address a Registry by ID or AccountId.""" +input RegistryIdInput @oneOf { + contract: AccountIdInput + id: RegistryId +} + +type RegistryParentsConnection { + edges: [RegistryParentsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type RegistryParentsConnectionEdge { + cursor: String! + node: ENSv2Domain +} + +type RegistryPermissionsUser { + """A unique reference to this RegistryPermissionsUser.""" + id: PermissionsUserId! + + """The Registry in which this Permission is granted.""" + registry: Registry! + + """The Resource for which this Permission is granted.""" + resource: BigInt! + + """The Roles that this Permission grants.""" + roles: BigInt! + + """The User for whom these Roles are granted.""" + user: Account! +} + +"""A Renewal represents an extension of a Registration's expiry.""" +type Renewal { + """The `base` cost of a Renewal, in wei, if exists.""" + base: BigInt + + """The duration for which a Registration was extended.""" + duration: BigInt! + + """The Event for which this Renewal was created.""" + event: Event! + + """A unique reference to this Renewal.""" + id: RenewalId! + + """The `premium` cost of a Renewal, in wei, if exists.""" + premium: BigInt + + """The extra `referrer` data provided with a Renewal, if exists.""" + referrer: Hex +} + +"""RenewalId represents a @ensnode/ensnode-sdk#RenewalId.""" +scalar RenewalId + +"""A Resolver represents a Resolver contract on-chain.""" +type Resolver { + """Whether Resolver is a BridgedResolver.""" + bridged: AccountId + + """Contract metadata for this Resolver.""" + contract: AccountId! + + """All Events associated with this Resolver.""" + events(after: String, before: String, first: Int, last: Int, where: EventsWhereInput): ResolverEventsConnection + + """A unique reference to this Resolver.""" + id: ResolverId! + + """Permissions granted by this Resolver.""" + permissions: Permissions + + """ResolverRecords issued by this Resolver.""" + records(after: String, before: String, first: Int, last: Int): ResolverRecordsConnection + + """Identify a ResolverRecord by `name` or `node`.""" + records_(for: NameOrNodeInput!): ResolverRecords +} + +type ResolverEventsConnection { + edges: [ResolverEventsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type ResolverEventsConnectionEdge { + cursor: String! + node: Event +} + +"""ResolverId represents a @ensnode/ensnode-sdk#ResolverId.""" +scalar ResolverId + +"""Address a Resolver by ID or AccountId.""" +input ResolverIdInput @oneOf { + contract: AccountIdInput + id: ResolverId +} + +type ResolverPermissionsUser { + """A unique reference to this ResolverPermissionsUser.""" + id: PermissionsUserId! + + """The Resolver in which this Permission is granted.""" + resolver: Resolver! + + """The Resource for which this Permission is granted.""" + resource: BigInt! + + """The Roles that this Permission grants.""" + roles: BigInt! + + """The User for whom these Roles are granted.""" + user: Account! +} + +"""ResolverRecords represents the _indexed_ records within a Resolver.""" +type ResolverRecords { + """Unique CoinTypes of `addr` records for this `node`.""" + coinTypes: [CoinType!]! + + """A unique reference to these ResolverRecords.""" + id: ResolverRecordsId! + + """Unique keys of `text` records for this `node`.""" + keys: [String!]! + + """The `name` record for this `node`, if any.""" + name: String + + """The Node for which these ResolverRecords are issued.""" + node: Node! +} + +type ResolverRecordsConnection { + edges: [ResolverRecordsConnectionEdge] + pageInfo: PageInfo! + totalCount: Int! +} + +type ResolverRecordsConnectionEdge { + cursor: String! + node: ResolverRecords +} + +"""ResolverRecordsId represents a @ensnode/ensnode-sdk#ResolverRecordsId.""" +scalar ResolverRecordsId + +"""Filter for Domain.subdomains query.""" +input SubdomainsWhereInput { + """A partial Interpreted Name by which to filter subdomains.""" + name: String +} + +"""ThreeDNSRegistration represents a Registration within ThreeDNSToken.""" +type ThreeDNSRegistration implements Registration { + """The Domain for which this Registration exists.""" + domain: Domain! + + """The Event for which this Registration was created.""" + event: Event! + + """ + Indicates whether this Registration is expired. If the Registration is for an ENSv1Domain, a Registration is only considered `expired` after the Grace Period has elapsed. + """ + expired: Boolean! + + """A UnixTimestamp indicating the Registration's expiry, if exists.""" + expiry: BigInt + + """A unique reference to this Registration.""" + id: RegistrationId! + + """The extra `referrer` data provided with a Registration, if exists.""" + referrer: Hex + + """The Registrant of a Registration, if exists.""" + registrant: Account + + """The Registrar contract under which this Registration is managed.""" + registrar: AccountId! + + """ + Renewals that have occurred within this Registration's lifespan to extend its expiration. + """ + renewals(after: String, before: String, first: Int, last: Int): RegistrationRenewalsConnection + + """A UnixTimestamp indicating when this Registration was created.""" + start: BigInt! + + """The Unregistrant of a Registration, if exists.""" + unregistrant: Account +} + +""" +Additional metadata for BaseRegistrar Registrations wrapped by the NameWrapper (i.e. in the case of a wrapped .eth name) +""" +type WrappedBaseRegistrarRegistration { + """The Fuses for this Registration's Domain in the NameWrapper.""" + fuses: Int! + + """The TokenID for this Domain in the NameWrapper.""" + tokenId: BigInt! +} \ No newline at end of file diff --git a/packages/enssdk/src/omnigraph/index.ts b/packages/enssdk/src/omnigraph/index.ts index 6217b024cd..54d8f8ebe2 100644 --- a/packages/enssdk/src/omnigraph/index.ts +++ b/packages/enssdk/src/omnigraph/index.ts @@ -9,7 +9,7 @@ export { graphql, readFragment } from "./graphql"; type GraphQLDocument = string | DocumentNode | TadaDocumentNode; -type QueryOptions = { +type QueryOptions | undefined> = { query: GraphQLDocument; variables?: V; signal?: AbortSignal; @@ -17,23 +17,32 @@ type QueryOptions = { type QueryResult = { data?: R; - errors?: Array<{ message: string; path?: (string | number)[] }>; + errors?: Array<{ + message: string; + path?: (string | number)[]; + extensions?: Record; + }>; }; export interface OmnigraphModule { omnigraph: { - query(options: QueryOptions): Promise>; + query | undefined = undefined>( + options: QueryOptions, + ): Promise>; }; } export function omnigraph(client: ENSSDKClient): OmnigraphModule { const { config } = client; const _fetch = config.fetch ?? globalThis.fetch; + const endpoint = new URL("/api/omnigraph", config.url).href; return { omnigraph: { - async query(opts: QueryOptions): Promise> { - const response = await _fetch(`${config.url}/api/omnigraph`, { + async query | undefined>( + opts: QueryOptions, + ): Promise> { + const response = await _fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/packages/enssdk/src/omnigraph/omnigraph.test.ts b/packages/enssdk/src/omnigraph/omnigraph.test.ts index c76bba5af2..5e4fe96f2b 100644 --- a/packages/enssdk/src/omnigraph/omnigraph.test.ts +++ b/packages/enssdk/src/omnigraph/omnigraph.test.ts @@ -3,6 +3,13 @@ import { describe, expect, it, vi } from "vitest"; import { createENSSDKClient } from "../core/index"; import { omnigraph } from "./index"; +function createMockClient(mockFetch: ReturnType) { + return createENSSDKClient({ + url: "https://example.com", + fetch: mockFetch as unknown as typeof globalThis.fetch, + }).extend(omnigraph); +} + describe("omnigraph module", () => { it("attaches omnigraph namespace to client", () => { const client = createENSSDKClient({ url: "https://example.com" }).extend(omnigraph); @@ -17,10 +24,7 @@ describe("omnigraph module", () => { json: () => Promise.resolve(mockResponse), }); - const client = createENSSDKClient({ - url: "https://example.com", - fetch: mockFetch as unknown as typeof globalThis.fetch, - }).extend(omnigraph); + const client = createMockClient(mockFetch); const result = await client.omnigraph.query({ query: 'query { domain(by: { name: "nick.eth" }) { name } }', @@ -44,10 +48,7 @@ describe("omnigraph module", () => { json: () => Promise.resolve({ data: null }), }); - const client = createENSSDKClient({ - url: "https://example.com", - fetch: mockFetch as unknown as typeof globalThis.fetch, - }).extend(omnigraph); + const client = createMockClient(mockFetch); await client.omnigraph.query({ query: "query($name: String!) { domain(by: { name: $name }) { name } }", @@ -64,10 +65,7 @@ describe("omnigraph module", () => { }); const controller = new AbortController(); - const client = createENSSDKClient({ - url: "https://example.com", - fetch: mockFetch as unknown as typeof globalThis.fetch, - }).extend(omnigraph); + const client = createMockClient(mockFetch); await client.omnigraph.query({ query: "query { domains { name } }", From edbe99c915fd70792832047bc7f1d3b75595c0c2 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 18:51:05 -0500 Subject: [PATCH 16/29] chore: add changeset for enssdk Co-Authored-By: Claude Opus 4.6 (1M context) --- .changeset/warm-snails-teach.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/warm-snails-teach.md diff --git a/.changeset/warm-snails-teach.md b/.changeset/warm-snails-teach.md new file mode 100644 index 0000000000..53a0c551be --- /dev/null +++ b/.changeset/warm-snails-teach.md @@ -0,0 +1,5 @@ +--- +"enssdk": minor +--- + +add core client factory with viem-style extend() and omnigraph module with gql.tada typed queries From aba31b0937dd3200caf7118950715c51a7c59e89 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 18:53:33 -0500 Subject: [PATCH 17/29] fix: vscode settings for tsconfig usage --- .vscode/settings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a7d408410d..f2bf775887 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { - "typescript.tsdk": "node_modules/typescript/lib", - "typescript.preferences.importModuleSpecifier": "non-relative", + "js/ts.tsdk.path": "node_modules/typescript/lib", + "js/ts.preferences.importModuleSpecifier": "non-relative", + "js/ts.tsdk.promptToUseWorkspaceVersion": true, "files.insertFinalNewline": true, "editor.formatOnSave": true, "editor.codeActionsOnSave": { From 7f1247ce086055b71e4f6d5aded03d4aa3248b55 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 18:56:18 -0500 Subject: [PATCH 18/29] docs: update enssdk README with usage examples and docs link Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/enssdk/README.md | 52 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/enssdk/README.md b/packages/enssdk/README.md index 4319d5ff6d..abd4bc8ca7 100644 --- a/packages/enssdk/README.md +++ b/packages/enssdk/README.md @@ -1,5 +1,53 @@ # enssdk -This package name is reserved for the [ENSNode](https://ensnode.io) project by [NameHash Labs](https://namehashlabs.org). +The foundational ENS client library. Isomorphic, tree-shakable, with composable modules via subpath exports. -For more information, visit [ensnode.io](https://ensnode.io). +Learn more about [ENSNode](https://ensnode.io/) from [the ENSNode docs](https://ensnode.io/docs). + +## Installation + +```bash +npm install enssdk +``` + +## Usage + +### Core Client + +```typescript +import { createENSSDKClient } from "enssdk/core"; + +const client = createENSSDKClient({ url: "https://api.alpha.ensnode.io" }); +``` + +### Omnigraph (Typed GraphQL) + +```typescript +import { createENSSDKClient } from "enssdk/core"; +import { omnigraph, graphql } from "enssdk/omnigraph"; + +const client = createENSSDKClient({ url: "https://api.alpha.ensnode.io" }) + .extend(omnigraph); + +const MyQuery = graphql(` + query MyQuery($name: Name!) { + domain(by: { name: $name }) { + name + registration { expiry } + } + } +`); + +const result = await client.omnigraph.query({ + query: MyQuery, + variables: { name: "nick.eth" }, +}); +``` + +Modules are composable via `extend()` — only import what you use. + +## License + +Licensed under the MIT License, Copyright © 2025-present [NameHash Labs](https://namehashlabs.org). + +See [LICENSE](./LICENSE) for more information. From 68becd69c0963c788bc046fded23c480ed9abd03 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 18:59:46 -0500 Subject: [PATCH 19/29] chore: add biome config for enssdk, ignore generated files Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/enssdk/biome.jsonc | 7 +++++++ packages/enssdk/src/core/core.test.ts | 16 ++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 packages/enssdk/biome.jsonc diff --git a/packages/enssdk/biome.jsonc b/packages/enssdk/biome.jsonc new file mode 100644 index 0000000000..608ccaefbb --- /dev/null +++ b/packages/enssdk/biome.jsonc @@ -0,0 +1,7 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", + "extends": "//", + "files": { + "includes": ["**", "!src/omnigraph/generated"] + } +} diff --git a/packages/enssdk/src/core/core.test.ts b/packages/enssdk/src/core/core.test.ts index 7b5ba9e061..a1e1d65563 100644 --- a/packages/enssdk/src/core/core.test.ts +++ b/packages/enssdk/src/core/core.test.ts @@ -24,22 +24,18 @@ describe("createENSSDKClient", () => { describe("extend", () => { it("adds module properties to the client", () => { - const client = createENSSDKClient({ url: "https://example.com" }).extend( - () => ({ - myModule: { greet: () => "hello" }, - }), - ); + const client = createENSSDKClient({ url: "https://example.com" }).extend(() => ({ + myModule: { greet: () => "hello" }, + })); expect(client.myModule.greet()).toBe("hello"); expect(client.config.url).toBe("https://example.com"); }); it("passes the base client to the decorator function", () => { - const client = createENSSDKClient({ url: "https://example.com" }).extend( - (base) => ({ - meta: { getUrl: () => base.config.url }, - }), - ); + const client = createENSSDKClient({ url: "https://example.com" }).extend((base) => ({ + meta: { getUrl: () => base.config.url }, + })); expect(client.meta.getUrl()).toBe("https://example.com"); }); From ffb349252be6819e6c019c46f6e29beb59e56757 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 19:09:05 -0500 Subject: [PATCH 20/29] feat: add CLI entrypoint for ensapi schema generation allows `pnpm --filter ensapi generate:schema` without starting the server Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/ensapi/package.json | 3 ++- .../src/graphql-api/lib/generate-schema.ts | 25 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/ensapi/package.json b/apps/ensapi/package.json index 3049946ac8..842085ed92 100644 --- a/apps/ensapi/package.json +++ b/apps/ensapi/package.json @@ -18,7 +18,8 @@ "test:integration": "vitest run --config vitest.integration.config.ts", "lint": "biome check --write .", "lint:ci": "biome ci", - "typecheck": "tsgo --noEmit" + "typecheck": "tsgo --noEmit", + "generate:schema": "tsx src/graphql-api/lib/generate-schema.ts" }, "dependencies": { "@ensdomains/ensjs": "^4.0.2", diff --git a/apps/ensapi/src/graphql-api/lib/generate-schema.ts b/apps/ensapi/src/graphql-api/lib/generate-schema.ts index a704255620..03425c2d9a 100644 --- a/apps/ensapi/src/graphql-api/lib/generate-schema.ts +++ b/apps/ensapi/src/graphql-api/lib/generate-schema.ts @@ -12,22 +12,29 @@ import { makeLogger } from "@/lib/logger"; const logger = makeLogger("generate-schema"); const MONOREPO_ROOT = resolve(import.meta.dirname, "../../../../../"); -const OUTPUT_PATH = resolve( - MONOREPO_ROOT, - "packages/enssdk/src/omnigraph/generated/schema.graphql", -); +const ENSSDK_ROOT = resolve(MONOREPO_ROOT, "packages/enssdk/"); +const OUTPUT_PATH = resolve(ENSSDK_ROOT, "src/omnigraph/generated/schema.graphql"); + +async function writeSchema() { + const { schema } = await import("@/graphql-api/schema"); + const schemaAsString = printSchema(lexicographicSortSchema(schema)); + + await writeFile(OUTPUT_PATH, schemaAsString); + logger.info(`Wrote SDL to ${OUTPUT_PATH}`); +} export async function writeGeneratedSchema() { const ENSv2Root = maybeGetDatasource(config.namespace, DatasourceNames.ENSv2Root); if (!ENSv2Root) return; - const { schema } = await import("@/graphql-api/schema"); - const schemaAsString = printSchema(lexicographicSortSchema(schema)); - try { - await writeFile(OUTPUT_PATH, schemaAsString); - logger.info(`Wrote SDL to ${OUTPUT_PATH}`); + await writeSchema(); } catch (error) { logger.error(error, `Unable to write SDL to ${OUTPUT_PATH}`); } } + +// when executed directly: generate schema without requiring ensapi config +if (import.meta.url === `file://${process.argv[1]}`) { + await writeSchema(); +} From 0fb593ebc8dc084c29456e970325c02012820d86 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 19:12:40 -0500 Subject: [PATCH 21/29] ci: run ensapi generate:schema before gql.tada output in schema check Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/test_ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 9f96ede44e..2c94ba050d 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -96,6 +96,9 @@ jobs: - uses: actions/checkout@v6 - uses: ./.github/actions/setup_node_environment + - name: Generate SDL from Pothos schema + run: pnpm --filter ensapi generate:schema + - name: Generate gql.tada output and turbo cache run: pnpm --filter enssdk generate:schema @@ -107,7 +110,9 @@ jobs: echo "The following generated files differ from what is committed:" git diff --name-status packages/enssdk/src/omnigraph/generated/ echo "" - echo "To fix, run: pnpm --filter enssdk generate:schema" + echo "To fix, run:" + echo " pnpm --filter ensapi generate:schema" + echo " pnpm --filter enssdk generate:schema" echo "Then commit the updated generated files." exit 1 fi From 86072773baefd62949aa1a3436f9b5d740f0febc Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 19:14:49 -0500 Subject: [PATCH 22/29] =?UTF-8?q?chore:=20remove=20gql.tada=20turbo=20from?= =?UTF-8?q?=20enssdk=20=E2=80=94=20consumer=20concern,=20not=20SDK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/enssdk/package.json | 2 +- .../enssdk/src/omnigraph/generated/graphql-cache.d.ts | 9 --------- packages/enssdk/tsconfig.json | 3 +-- 3 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts diff --git a/packages/enssdk/package.json b/packages/enssdk/package.json index e4c00a462b..e08b1a24e4 100644 --- a/packages/enssdk/package.json +++ b/packages/enssdk/package.json @@ -42,7 +42,7 @@ "lint:ci": "biome ci", "test": "vitest", "typecheck": "tsgo --noEmit", - "generate:schema": "gql.tada generate-output && gql.tada turbo" + "generate:schema": "gql.tada generate-output" }, "dependencies": { "gql.tada": "^1.8.10", diff --git a/packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts b/packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts deleted file mode 100644 index e664361ffe..0000000000 --- a/packages/enssdk/src/omnigraph/generated/graphql-cache.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ -import type { TadaDocumentNode, $tada } from 'gql.tada'; - -declare module 'gql.tada' { - interface setupCache { - - } -} diff --git a/packages/enssdk/tsconfig.json b/packages/enssdk/tsconfig.json index 983afbfe85..6af4a81c9f 100644 --- a/packages/enssdk/tsconfig.json +++ b/packages/enssdk/tsconfig.json @@ -6,8 +6,7 @@ { "name": "gql.tada/ts-plugin", "schema": "./src/omnigraph/generated/schema.graphql", - "tadaOutputLocation": "./src/omnigraph/generated/graphql-env.d.ts", - "tadaTurboLocation": "./src/omnigraph/generated/graphql-cache.d.ts" + "tadaOutputLocation": "./src/omnigraph/generated/graphql-env.d.ts" } ] }, From 8cb837032e77794fd71773fb264a076d4da768fd Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 19:16:26 -0500 Subject: [PATCH 23/29] fix: generation --- .github/workflows/test_ci.yml | 12 ++++------ .../src/graphql-api/lib/generate-schema.ts | 22 +++++-------------- package.json | 3 ++- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 2c94ba050d..a7fda471dc 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -96,23 +96,19 @@ jobs: - uses: actions/checkout@v6 - uses: ./.github/actions/setup_node_environment - - name: Generate SDL from Pothos schema - run: pnpm --filter ensapi generate:schema - - - name: Generate gql.tada output and turbo cache - run: pnpm --filter enssdk generate:schema + - name: Generate Schemas & Typings + run: pnpm generate:schema - name: Verify generated files are committed run: | if ! git diff --quiet packages/enssdk/src/omnigraph/generated/; then - echo "Error: gql.tada generated files are out of sync" + echo "Error: Generated files are out of sync" echo "" echo "The following generated files differ from what is committed:" git diff --name-status packages/enssdk/src/omnigraph/generated/ echo "" echo "To fix, run:" - echo " pnpm --filter ensapi generate:schema" - echo " pnpm --filter enssdk generate:schema" + echo " pnpm generate:schema" echo "Then commit the updated generated files." exit 1 fi diff --git a/apps/ensapi/src/graphql-api/lib/generate-schema.ts b/apps/ensapi/src/graphql-api/lib/generate-schema.ts index 03425c2d9a..5a5c102576 100644 --- a/apps/ensapi/src/graphql-api/lib/generate-schema.ts +++ b/apps/ensapi/src/graphql-api/lib/generate-schema.ts @@ -1,12 +1,8 @@ -import config from "@/config"; - import { writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { lexicographicSortSchema, printSchema } from "graphql"; -import { DatasourceNames, maybeGetDatasource } from "@ensnode/datasources"; - import { makeLogger } from "@/lib/logger"; const logger = makeLogger("generate-schema"); @@ -15,26 +11,20 @@ const MONOREPO_ROOT = resolve(import.meta.dirname, "../../../../../"); const ENSSDK_ROOT = resolve(MONOREPO_ROOT, "packages/enssdk/"); const OUTPUT_PATH = resolve(ENSSDK_ROOT, "src/omnigraph/generated/schema.graphql"); -async function writeSchema() { +export async function writeGeneratedSchema() { const { schema } = await import("@/graphql-api/schema"); const schemaAsString = printSchema(lexicographicSortSchema(schema)); - await writeFile(OUTPUT_PATH, schemaAsString); - logger.info(`Wrote SDL to ${OUTPUT_PATH}`); -} - -export async function writeGeneratedSchema() { - const ENSv2Root = maybeGetDatasource(config.namespace, DatasourceNames.ENSv2Root); - if (!ENSv2Root) return; - try { - await writeSchema(); + await writeFile(OUTPUT_PATH, schemaAsString); + logger.info(`Wrote SDL to ${OUTPUT_PATH}`); } catch (error) { logger.error(error, `Unable to write SDL to ${OUTPUT_PATH}`); } } -// when executed directly: generate schema without requiring ensapi config +// when executed directly, write generated schema and exit if (import.meta.url === `file://${process.argv[1]}`) { - await writeSchema(); + await writeGeneratedSchema(); + process.exit(0); } diff --git a/package.json b/package.json index a7b8af8779..84a16c0097 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "docker:build:ensrainbow": "docker build -f apps/ensrainbow/Dockerfile -t ghcr.io/namehash/ensnode/ensrainbow:latest .", "docker:build:ensapi": "docker build -f apps/ensapi/Dockerfile -t ghcr.io/namehash/ensnode/ensapi:latest .", "otel-desktop-viewer": "docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 davetron5000/otel-desktop-viewer:alpine-3", - "generate:openapi": "pnpm -r --if-present generate:openapi" + "generate:openapi": "pnpm -r --if-present generate:openapi", + "generate:schema": "pnpm -F ensapi generate:schema && pnpm -F enssdk generate:schema" }, "devDependencies": { "@biomejs/biome": "^2.3.1", From d232132c8dedbe2f1456b311b5791c0546c85228 Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 19:21:20 -0500 Subject: [PATCH 24/29] fix: address PR review feedback - map all schema scalars (Address, Hex, ChainId, Node), remove nonexistent (Bytes, UnixTimestamp) - conditional variables: require when V is Record, forbid when undefined - data field: R | null to match GraphQL spec - prevent config/extend override in extend() constraint - add DocumentNode test for print() branch - comment documenting fragile relative path Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/enssdk/src/core/index.ts | 2 +- packages/enssdk/src/omnigraph/graphql.ts | 9 +++++---- packages/enssdk/src/omnigraph/index.ts | 5 ++--- packages/enssdk/src/omnigraph/omnigraph.test.ts | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/enssdk/src/core/index.ts b/packages/enssdk/src/core/index.ts index 95d4e90149..6e4390bdfb 100644 --- a/packages/enssdk/src/core/index.ts +++ b/packages/enssdk/src/core/index.ts @@ -12,7 +12,7 @@ export interface ENSSDKClientConfig { export type ENSSDKClient = TExtended & { readonly config: Readonly; - extend( + extend( fn: (client: ENSSDKClient) => T, ): ENSSDKClient; }; diff --git a/packages/enssdk/src/omnigraph/graphql.ts b/packages/enssdk/src/omnigraph/graphql.ts index 7a6f10ba73..75cbc89fca 100644 --- a/packages/enssdk/src/omnigraph/graphql.ts +++ b/packages/enssdk/src/omnigraph/graphql.ts @@ -5,16 +5,17 @@ import type { introspection } from "./generated/graphql-env"; // Semantic scalar types — these will eventually be imported from enssdk's // own type definitions. For now, defined inline. type Name = string; -type UnixTimestamp = number; export const graphql = initGraphQLTada<{ introspection: introspection; scalars: { - Name: Name; + Address: `0x${string}`; BigInt: bigint; - Bytes: `0x${string}`; + ChainId: number; + Hex: `0x${string}`; ID: string; - UnixTimestamp: UnixTimestamp; + Name: Name; + Node: `0x${string}`; }; }>(); diff --git a/packages/enssdk/src/omnigraph/index.ts b/packages/enssdk/src/omnigraph/index.ts index 54d8f8ebe2..45b2a34714 100644 --- a/packages/enssdk/src/omnigraph/index.ts +++ b/packages/enssdk/src/omnigraph/index.ts @@ -11,12 +11,11 @@ type GraphQLDocument = string | DocumentNode | TadaDoc type QueryOptions | undefined> = { query: GraphQLDocument; - variables?: V; signal?: AbortSignal; -}; +} & (V extends Record ? { variables: V } : { variables?: undefined }); type QueryResult = { - data?: R; + data?: R | null; errors?: Array<{ message: string; path?: (string | number)[]; diff --git a/packages/enssdk/src/omnigraph/omnigraph.test.ts b/packages/enssdk/src/omnigraph/omnigraph.test.ts index 5e4fe96f2b..c955944127 100644 --- a/packages/enssdk/src/omnigraph/omnigraph.test.ts +++ b/packages/enssdk/src/omnigraph/omnigraph.test.ts @@ -1,3 +1,4 @@ +import { parse } from "graphql"; import { describe, expect, it, vi } from "vitest"; import { createENSSDKClient } from "../core/index"; @@ -74,4 +75,19 @@ describe("omnigraph module", () => { expect(mockFetch.mock.calls[0][1].signal).toBe(controller.signal); }); + + it("prints DocumentNode queries to string", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + json: () => Promise.resolve({ data: null }), + }); + + const client = createMockClient(mockFetch); + const doc = parse("query { domain(by: { name: \"nick.eth\" }) { name } }"); + + await client.omnigraph.query({ query: doc }); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + expect(body.query).toContain("domain"); + expect(body.query).toContain("nick.eth"); + }); }); From 9d4704619cc1f20ec766e86cb425a32b9d15b93c Mon Sep 17 00:00:00 2001 From: shrugs Date: Mon, 30 Mar 2026 19:22:48 -0500 Subject: [PATCH 25/29] fix lint --- apps/ensapi/src/index.ts | 6 +++--- packages/enssdk/src/omnigraph/omnigraph.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ensapi/src/index.ts b/apps/ensapi/src/index.ts index 83829ea889..394500f8c1 100644 --- a/apps/ensapi/src/index.ts +++ b/apps/ensapi/src/index.ts @@ -27,15 +27,15 @@ const server = serve( async (info) => { logger.info({ config: redactEnsApiConfig(config) }, `ENSApi listening on port ${info.port}`); - // Write the generated graphql schema - await writeGeneratedSchema(); + // Write the generated graphql schema in the background + void writeGeneratedSchema(); // Trigger proactive initialization of the indexing status cache at startup. // SWRCache with proactivelyInitialize: true starts fetching immediately upon // construction, but construction is deferred via the lazy proxy until first // access — so we access it explicitly here rather than waiting for the first // user request. - await indexingStatusCache.read(); + void indexingStatusCache.read(); }, ); diff --git a/packages/enssdk/src/omnigraph/omnigraph.test.ts b/packages/enssdk/src/omnigraph/omnigraph.test.ts index c955944127..e8bfaa578f 100644 --- a/packages/enssdk/src/omnigraph/omnigraph.test.ts +++ b/packages/enssdk/src/omnigraph/omnigraph.test.ts @@ -82,7 +82,7 @@ describe("omnigraph module", () => { }); const client = createMockClient(mockFetch); - const doc = parse("query { domain(by: { name: \"nick.eth\" }) { name } }"); + const doc = parse('query { domain(by: { name: "nick.eth" }) { name } }'); await client.omnigraph.query({ query: doc }); From fdafe2f05fa36b70a933b93cdd0881d866ac7f00 Mon Sep 17 00:00:00 2001 From: shrugs Date: Tue, 31 Mar 2026 14:54:10 -0500 Subject: [PATCH 26/29] fix: pr notes --- .github/workflows/test_ci.yml | 4 ++-- apps/ensapi/package.json | 2 +- ...erate-schema.ts => write-graphql-schema.ts} | 4 ++-- apps/ensapi/src/index.ts | 4 ++-- package.json | 2 +- packages/enssdk/README.md | 10 +++++----- packages/enssdk/package.json | 4 ++-- packages/enssdk/src/core/core.test.ts | 16 ++++++++-------- packages/enssdk/src/core/index.ts | 18 +++++++++--------- packages/enssdk/src/omnigraph/index.ts | 4 ++-- .../enssdk/src/omnigraph/omnigraph.test.ts | 6 +++--- 11 files changed, 37 insertions(+), 37 deletions(-) rename apps/ensapi/src/graphql-api/lib/{generate-schema.ts => write-graphql-schema.ts} (92%) diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index a7fda471dc..f14f510614 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -97,7 +97,7 @@ jobs: - uses: ./.github/actions/setup_node_environment - name: Generate Schemas & Typings - run: pnpm generate:schema + run: pnpm generate:gqlschema - name: Verify generated files are committed run: | @@ -108,7 +108,7 @@ jobs: git diff --name-status packages/enssdk/src/omnigraph/generated/ echo "" echo "To fix, run:" - echo " pnpm generate:schema" + echo " pnpm generate:gqlschema" echo "Then commit the updated generated files." exit 1 fi diff --git a/apps/ensapi/package.json b/apps/ensapi/package.json index 842085ed92..dbe31de72a 100644 --- a/apps/ensapi/package.json +++ b/apps/ensapi/package.json @@ -19,7 +19,7 @@ "lint": "biome check --write .", "lint:ci": "biome ci", "typecheck": "tsgo --noEmit", - "generate:schema": "tsx src/graphql-api/lib/generate-schema.ts" + "generate:gqlschema": "tsx src/graphql-api/lib/generate-schema.ts" }, "dependencies": { "@ensdomains/ensjs": "^4.0.2", diff --git a/apps/ensapi/src/graphql-api/lib/generate-schema.ts b/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts similarity index 92% rename from apps/ensapi/src/graphql-api/lib/generate-schema.ts rename to apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts index 5a5c102576..d844a9f266 100644 --- a/apps/ensapi/src/graphql-api/lib/generate-schema.ts +++ b/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts @@ -11,7 +11,7 @@ const MONOREPO_ROOT = resolve(import.meta.dirname, "../../../../../"); const ENSSDK_ROOT = resolve(MONOREPO_ROOT, "packages/enssdk/"); const OUTPUT_PATH = resolve(ENSSDK_ROOT, "src/omnigraph/generated/schema.graphql"); -export async function writeGeneratedSchema() { +export async function writeGraphQLSchema() { const { schema } = await import("@/graphql-api/schema"); const schemaAsString = printSchema(lexicographicSortSchema(schema)); @@ -25,6 +25,6 @@ export async function writeGeneratedSchema() { // when executed directly, write generated schema and exit if (import.meta.url === `file://${process.argv[1]}`) { - await writeGeneratedSchema(); + await writeGraphQLSchema(); process.exit(0); } diff --git a/apps/ensapi/src/index.ts b/apps/ensapi/src/index.ts index 394500f8c1..1c741b90b0 100644 --- a/apps/ensapi/src/index.ts +++ b/apps/ensapi/src/index.ts @@ -7,7 +7,7 @@ import { getReferralLeaderboardEditionsCaches } from "@/cache/referral-leaderboa import { referralProgramEditionConfigSetCache } from "@/cache/referral-program-edition-set.cache"; import { referrerLeaderboardCache } from "@/cache/referrer-leaderboard.cache"; import { redactEnsApiConfig } from "@/config/redact"; -import { writeGeneratedSchema } from "@/graphql-api/lib/generate-schema"; +import { writeGraphQLSchema } from "@/graphql-api/lib/write-graphql-schema"; import { sdk } from "@/lib/instrumentation"; import logger from "@/lib/logger"; @@ -28,7 +28,7 @@ const server = serve( logger.info({ config: redactEnsApiConfig(config) }, `ENSApi listening on port ${info.port}`); // Write the generated graphql schema in the background - void writeGeneratedSchema(); + void writeGraphQLSchema(); // Trigger proactive initialization of the indexing status cache at startup. // SWRCache with proactivelyInitialize: true starts fetching immediately upon diff --git a/package.json b/package.json index 84a16c0097..4b6d2fce7b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "docker:build:ensapi": "docker build -f apps/ensapi/Dockerfile -t ghcr.io/namehash/ensnode/ensapi:latest .", "otel-desktop-viewer": "docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 davetron5000/otel-desktop-viewer:alpine-3", "generate:openapi": "pnpm -r --if-present generate:openapi", - "generate:schema": "pnpm -F ensapi generate:schema && pnpm -F enssdk generate:schema" + "generate:gqlschema": "pnpm -F ensapi generate:gqlschema && pnpm -F enssdk generate:gqlschema" }, "devDependencies": { "@biomejs/biome": "^2.3.1", diff --git a/packages/enssdk/README.md b/packages/enssdk/README.md index abd4bc8ca7..d753ee4ca3 100644 --- a/packages/enssdk/README.md +++ b/packages/enssdk/README.md @@ -1,6 +1,6 @@ # enssdk -The foundational ENS client library. Isomorphic, tree-shakable, with composable modules via subpath exports. +The foundational ENS developer library. Isomorphic, tree-shakable, with composable modules via subpath exports. Learn more about [ENSNode](https://ensnode.io/) from [the ENSNode docs](https://ensnode.io/docs). @@ -15,18 +15,18 @@ npm install enssdk ### Core Client ```typescript -import { createENSSDKClient } from "enssdk/core"; +import { createEnsNodeClient } from "enssdk/core"; -const client = createENSSDKClient({ url: "https://api.alpha.ensnode.io" }); +const client = createEnsNodeClient({ url: "https://api.alpha.ensnode.io" }); ``` ### Omnigraph (Typed GraphQL) ```typescript -import { createENSSDKClient } from "enssdk/core"; +import { createEnsNodeClient } from "enssdk/core"; import { omnigraph, graphql } from "enssdk/omnigraph"; -const client = createENSSDKClient({ url: "https://api.alpha.ensnode.io" }) +const client = createEnsNodeClient({ url: "https://api.alpha.ensnode.io" }) .extend(omnigraph); const MyQuery = graphql(` diff --git a/packages/enssdk/package.json b/packages/enssdk/package.json index e08b1a24e4..276fa00e5c 100644 --- a/packages/enssdk/package.json +++ b/packages/enssdk/package.json @@ -2,7 +2,7 @@ "name": "enssdk", "version": "1.9.0", "type": "module", - "description": "The foundational ENS client library", + "description": "The foundational ENS development library", "license": "MIT", "repository": { "type": "git", @@ -42,7 +42,7 @@ "lint:ci": "biome ci", "test": "vitest", "typecheck": "tsgo --noEmit", - "generate:schema": "gql.tada generate-output" + "generate:gqlschema": "gql.tada generate-output" }, "dependencies": { "gql.tada": "^1.8.10", diff --git a/packages/enssdk/src/core/core.test.ts b/packages/enssdk/src/core/core.test.ts index a1e1d65563..38b4f04ac5 100644 --- a/packages/enssdk/src/core/core.test.ts +++ b/packages/enssdk/src/core/core.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it, vi } from "vitest"; -import { createENSSDKClient } from "./index"; +import { createEnsNodeClient } from "./index"; -describe("createENSSDKClient", () => { +describe("createEnsNodeClient", () => { it("creates a client with frozen config", () => { - const client = createENSSDKClient({ url: "https://example.com" }); + const client = createEnsNodeClient({ url: "https://example.com" }); expect(client.config.url).toBe("https://example.com"); expect(client.config.fetch).toBeUndefined(); @@ -13,7 +13,7 @@ describe("createENSSDKClient", () => { it("preserves custom fetch in config", () => { const customFetch = vi.fn(); - const client = createENSSDKClient({ + const client = createEnsNodeClient({ url: "https://example.com", fetch: customFetch as unknown as typeof globalThis.fetch, }); @@ -24,7 +24,7 @@ describe("createENSSDKClient", () => { describe("extend", () => { it("adds module properties to the client", () => { - const client = createENSSDKClient({ url: "https://example.com" }).extend(() => ({ + const client = createEnsNodeClient({ url: "https://example.com" }).extend(() => ({ myModule: { greet: () => "hello" }, })); @@ -33,7 +33,7 @@ describe("extend", () => { }); it("passes the base client to the decorator function", () => { - const client = createENSSDKClient({ url: "https://example.com" }).extend((base) => ({ + const client = createEnsNodeClient({ url: "https://example.com" }).extend((base) => ({ meta: { getUrl: () => base.config.url }, })); @@ -41,7 +41,7 @@ describe("extend", () => { }); it("supports chaining multiple extend calls", () => { - const client = createENSSDKClient({ url: "https://example.com" }) + const client = createEnsNodeClient({ url: "https://example.com" }) .extend(() => ({ a: { value: 1 } })) .extend(() => ({ b: { value: 2 } })); @@ -51,7 +51,7 @@ describe("extend", () => { }); it("later extensions can see earlier extensions via the base client", () => { - const client = createENSSDKClient({ url: "https://example.com" }) + const client = createEnsNodeClient({ url: "https://example.com" }) .extend(() => ({ a: { value: 42 } })) .extend((base) => ({ b: { doubled: () => base.a.value * 2 }, diff --git a/packages/enssdk/src/core/index.ts b/packages/enssdk/src/core/index.ts index 6e4390bdfb..ac55b6d66a 100644 --- a/packages/enssdk/src/core/index.ts +++ b/packages/enssdk/src/core/index.ts @@ -1,4 +1,4 @@ -export interface ENSSDKClientConfig { +export interface EnsNodeClientConfig { /** * ENSNode instance URL (e.g. "https://api.alpha.ensnode.io") */ @@ -10,17 +10,17 @@ export interface ENSSDKClientConfig { fetch?: typeof globalThis.fetch; } -export type ENSSDKClient = TExtended & { - readonly config: Readonly; +export type EnsNodeClient = TExtended & { + readonly config: Readonly; extend( - fn: (client: ENSSDKClient) => T, - ): ENSSDKClient; + fn: (client: EnsNodeClient) => T, + ): EnsNodeClient; }; -export function createENSSDKClient(config: ENSSDKClientConfig): ENSSDKClient { +export function createEnsNodeClient(config: EnsNodeClientConfig): EnsNodeClient { const frozenConfig = Object.freeze({ ...config }); - function makeClient(base: Record): ENSSDKClient> { + function makeClient(base: Record): EnsNodeClient> { const client = { ...base, config: frozenConfig, @@ -32,8 +32,8 @@ export function createENSSDKClient(config: ENSSDKClientConfig): ENSSDKClient { }); }, }; - return client as ENSSDKClient>; + return client as EnsNodeClient>; } - return makeClient({}) as ENSSDKClient; + return makeClient({}) as EnsNodeClient; } diff --git a/packages/enssdk/src/omnigraph/index.ts b/packages/enssdk/src/omnigraph/index.ts index 45b2a34714..a5a833d95c 100644 --- a/packages/enssdk/src/omnigraph/index.ts +++ b/packages/enssdk/src/omnigraph/index.ts @@ -2,7 +2,7 @@ import type { TadaDocumentNode } from "gql.tada"; import type { DocumentNode } from "graphql"; import { print } from "graphql"; -import type { ENSSDKClient } from "../core/index"; +import type { EnsNodeClient } from "../core/index"; export type { FragmentOf, ResultOf, VariablesOf } from "./graphql"; export { graphql, readFragment } from "./graphql"; @@ -31,7 +31,7 @@ export interface OmnigraphModule { }; } -export function omnigraph(client: ENSSDKClient): OmnigraphModule { +export function omnigraph(client: EnsNodeClient): OmnigraphModule { const { config } = client; const _fetch = config.fetch ?? globalThis.fetch; const endpoint = new URL("/api/omnigraph", config.url).href; diff --git a/packages/enssdk/src/omnigraph/omnigraph.test.ts b/packages/enssdk/src/omnigraph/omnigraph.test.ts index e8bfaa578f..62c2bbdbfe 100644 --- a/packages/enssdk/src/omnigraph/omnigraph.test.ts +++ b/packages/enssdk/src/omnigraph/omnigraph.test.ts @@ -1,11 +1,11 @@ import { parse } from "graphql"; import { describe, expect, it, vi } from "vitest"; -import { createENSSDKClient } from "../core/index"; +import { createEnsNodeClient } from "../core/index"; import { omnigraph } from "./index"; function createMockClient(mockFetch: ReturnType) { - return createENSSDKClient({ + return createEnsNodeClient({ url: "https://example.com", fetch: mockFetch as unknown as typeof globalThis.fetch, }).extend(omnigraph); @@ -13,7 +13,7 @@ function createMockClient(mockFetch: ReturnType) { describe("omnigraph module", () => { it("attaches omnigraph namespace to client", () => { - const client = createENSSDKClient({ url: "https://example.com" }).extend(omnigraph); + const client = createEnsNodeClient({ url: "https://example.com" }).extend(omnigraph); expect(client.omnigraph).toBeDefined(); expect(typeof client.omnigraph.query).toBe("function"); From 6d2578edf71f2a144939857e71c14b11c6f158c0 Mon Sep 17 00:00:00 2001 From: shrugs Date: Tue, 31 Mar 2026 14:56:08 -0500 Subject: [PATCH 27/29] fix: merge conflicts --- packages/integration-test-env/README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/integration-test-env/README.md b/packages/integration-test-env/README.md index c0277a1982..5eb0320fbf 100644 --- a/packages/integration-test-env/README.md +++ b/packages/integration-test-env/README.md @@ -91,11 +91,7 @@ cd apps/ensindexer && pnpm dev with environment variables: ```env -<<<<<<< HEAD -DATABASE_SCHEMA=public -======= DATABASE_SCHEMA=ensindexer_0 ->>>>>>> main NAMESPACE=ens-test-env PLUGINS=ensv2,protocol-acceleration ``` @@ -112,11 +108,7 @@ with environment variables: ```env DATABASE_URL=postgresql://ensnode:ensnode@localhost:5432/ensnode -<<<<<<< HEAD -ENSINDEXER_SCHEMA_NAME=public -======= ENSINDEXER_SCHEMA_NAME=ensindexer_0 ->>>>>>> main ``` `ENSINDEXER_SCHEMA_NAME` must match the `DATABASE_SCHEMA` used by ENSIndexer above. From 26313d30b92435e72512acbc68bf5852919c9eac Mon Sep 17 00:00:00 2001 From: shrugs Date: Tue, 31 Mar 2026 15:00:59 -0500 Subject: [PATCH 28/29] fix: throw if write failed via direct execution --- .../graphql-api/lib/write-graphql-schema.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts b/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts index d844a9f266..8f0f545e10 100644 --- a/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts +++ b/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts @@ -11,20 +11,33 @@ const MONOREPO_ROOT = resolve(import.meta.dirname, "../../../../../"); const ENSSDK_ROOT = resolve(MONOREPO_ROOT, "packages/enssdk/"); const OUTPUT_PATH = resolve(ENSSDK_ROOT, "src/omnigraph/generated/schema.graphql"); -export async function writeGraphQLSchema() { +async function _writeGraphQLSchema() { const { schema } = await import("@/graphql-api/schema"); const schemaAsString = printSchema(lexicographicSortSchema(schema)); + await writeFile(OUTPUT_PATH, schemaAsString); +} + +/** + * Attempts to write the GraphQL Schema, swallowing any errors. + */ +export async function writeGraphQLSchema() { try { - await writeFile(OUTPUT_PATH, schemaAsString); + await _writeGraphQLSchema(); logger.info(`Wrote SDL to ${OUTPUT_PATH}`); } catch (error) { - logger.error(error, `Unable to write SDL to ${OUTPUT_PATH}`); + logger.warn(error, `Unable to write SDL to ${OUTPUT_PATH}`); } } -// when executed directly, write generated schema and exit +// when executed directly (`pnpm generate:gqlschema`), write generated schema and produce an exit code if (import.meta.url === `file://${process.argv[1]}`) { - await writeGraphQLSchema(); - process.exit(0); + try { + await writeGraphQLSchema(); + console.log(`Wrote SDL to ${OUTPUT_PATH}`); + process.exit(0); + } catch (error) { + console.error(error); + process.exit(1); + } } From 31471da9e4dac0e157eb47c6c5057b8ec38d859c Mon Sep 17 00:00:00 2001 From: shrugs Date: Tue, 31 Mar 2026 15:04:57 -0500 Subject: [PATCH 29/29] fixes --- apps/ensapi/package.json | 2 +- apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/ensapi/package.json b/apps/ensapi/package.json index dbe31de72a..db942274f5 100644 --- a/apps/ensapi/package.json +++ b/apps/ensapi/package.json @@ -19,7 +19,7 @@ "lint": "biome check --write .", "lint:ci": "biome ci", "typecheck": "tsgo --noEmit", - "generate:gqlschema": "tsx src/graphql-api/lib/generate-schema.ts" + "generate:gqlschema": "tsx src/graphql-api/lib/write-graphql-schema.ts" }, "dependencies": { "@ensdomains/ensjs": "^4.0.2", diff --git a/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts b/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts index 8f0f545e10..a1ade33f10 100644 --- a/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts +++ b/apps/ensapi/src/graphql-api/lib/write-graphql-schema.ts @@ -1,11 +1,12 @@ import { writeFile } from "node:fs/promises"; import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import { lexicographicSortSchema, printSchema } from "graphql"; import { makeLogger } from "@/lib/logger"; -const logger = makeLogger("generate-schema"); +const logger = makeLogger("write-graphql-schema"); const MONOREPO_ROOT = resolve(import.meta.dirname, "../../../../../"); const ENSSDK_ROOT = resolve(MONOREPO_ROOT, "packages/enssdk/"); @@ -31,9 +32,9 @@ export async function writeGraphQLSchema() { } // when executed directly (`pnpm generate:gqlschema`), write generated schema and produce an exit code -if (import.meta.url === `file://${process.argv[1]}`) { +if (resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { try { - await writeGraphQLSchema(); + await _writeGraphQLSchema(); console.log(`Wrote SDL to ${OUTPUT_PATH}`); process.exit(0); } catch (error) {