diff --git a/AGENTS.md b/AGENTS.md index c6a4e0b054..7c8693e7b3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,7 @@ Runnable commands for validating changes; lint and format with Biome. - Lint and format: `pnpm lint` (fixes where applicable); CI lint: `pnpm lint:ci` - Type checking: `pnpm typecheck` (runs typecheck in all workspaces) - Always use `pnpm -F typecheck`, never call `tsc` or `tsgo` directly +- Omnigraph example sample responses (docs): after changing SDK Omnigraph example queries/variables in `packages/ensnode-sdk` or when refreshing live JSON shown in the docs Omnigraph examples, run `pnpm -F @docs/ensnode omnigraph-examples:refresh-responses` (requires `curl`, network). Updates `docs/ensnode.io/src/data/omnigraph-examples/responses.json`. ## Testing diff --git a/apps/ensadmin/package.json b/apps/ensadmin/package.json index 6d5bf008b8..d790446d08 100644 --- a/apps/ensadmin/package.json +++ b/apps/ensadmin/package.json @@ -23,9 +23,9 @@ }, "dependencies": { "@ensnode/datasources": "workspace:*", - "@ensnode/ensnode-sdk": "workspace:*", + "@ensnode/ensnode-sdk": "0.0.0-preview-fix-sha-89c022b-20260518142147", "@ensnode/scalar-react": "workspace:*", - "enssdk": "workspace:*", + "enssdk": "0.0.0-preview-fix-sha-89c022b-20260518142147", "@formkit/auto-animate": "^0.9.0", "@graphiql/plugin-explorer": "5.1.1", "@graphiql/react": "0.37.1", diff --git a/apps/ensapi/src/omnigraph-api/schema/example-queries.integration.test.ts b/apps/ensapi/src/omnigraph-api/schema/example-queries.integration.test.ts index 5b94d03c75..80a1a682d1 100644 --- a/apps/ensapi/src/omnigraph-api/schema/example-queries.integration.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/example-queries.integration.test.ts @@ -2,19 +2,20 @@ import { type OperationDefinitionNode, parse } from "graphql"; import { describe, expect, it } from "vitest"; import { ENSNamespaceIds } from "@ensnode/datasources"; +import { getNamespaceSpecificValue } from "@ensnode/ensnode-sdk"; import { GRAPHQL_API_EXAMPLE_QUERIES } from "@ensnode/ensnode-sdk/internal"; import { request } from "@/test/integration/graphql-utils"; const namespace = ENSNamespaceIds.EnsTestEnv; -const EXAMPLE_QUERY_TEST_CASES = GRAPHQL_API_EXAMPLE_QUERIES.map((entry, i) => { +const EXAMPLE_QUERY_TEST_CASES = GRAPHQL_API_EXAMPLE_QUERIES.map((entry) => { const document = parse(entry.query); const operation = document.definitions.find( (d): d is OperationDefinitionNode => d.kind === "OperationDefinition", ); - const name = operation?.name?.value ?? `Query #${i}`; - const variables = entry.variables[namespace] ?? entry.variables.default; + const name = operation?.name?.value ?? entry.id; + const variables = getNamespaceSpecificValue(namespace, entry.variables); return { name, query: entry.query, variables }; }); diff --git a/apps/ensapi/src/omnigraph-api/schema/example-queries.test.ts b/apps/ensapi/src/omnigraph-api/schema/example-queries.test.ts index 14f9eaef84..ea9ab1186c 100644 --- a/apps/ensapi/src/omnigraph-api/schema/example-queries.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/example-queries.test.ts @@ -5,8 +5,8 @@ import { GRAPHQL_API_EXAMPLE_QUERIES } from "@ensnode/ensnode-sdk/internal"; describe("Example Queries", () => { it.each( - GRAPHQL_API_EXAMPLE_QUERIES.map((entry, i) => ({ - name: `Query #${i}`, + GRAPHQL_API_EXAMPLE_QUERIES.map((entry) => ({ + name: entry.id, query: entry.query, })), )("$name parses as valid GraphQL", ({ query }) => { diff --git a/docs/ensnode.io/astro.config.mjs b/docs/ensnode.io/astro.config.mjs index 2629287f56..514dfc912a 100644 --- a/docs/ensnode.io/astro.config.mjs +++ b/docs/ensnode.io/astro.config.mjs @@ -1,3 +1,5 @@ +import { fileURLToPath } from "node:url"; + import mdx from "@astrojs/mdx"; import react from "@astrojs/react"; import tailwindcss from "@tailwindcss/vite"; @@ -13,6 +15,18 @@ export default defineConfig({ trailingSlash: "never", integrations: [mermaid(), starlight(), sitemap(), react(), mdx(), icon()], vite: { + resolve: { + alias: { + "@assets": fileURLToPath(new URL("./src/assets", import.meta.url)), + "@components": fileURLToPath(new URL("./src/components", import.meta.url)), + "@content": fileURLToPath(new URL("./src/content", import.meta.url)), + "@data": fileURLToPath(new URL("./src/data", import.meta.url)), + "@lib": fileURLToPath(new URL("./src/lib", import.meta.url)), + "@scripts": fileURLToPath(new URL("./src/scripts", import.meta.url)), + "@styles": fileURLToPath(new URL("./src/styles", import.meta.url)), + "@workspace": fileURLToPath(new URL("../..", import.meta.url)), + }, + }, ssr: { noExternal: ["@namehash/namehash-ui"], }, diff --git a/docs/ensnode.io/config/integrations/llms-txt.ts b/docs/ensnode.io/config/integrations/llms-txt.ts index e69de29bb2..c4bcd3191f 100644 --- a/docs/ensnode.io/config/integrations/llms-txt.ts +++ b/docs/ensnode.io/config/integrations/llms-txt.ts @@ -0,0 +1,20 @@ +import starlightLlmsTxt from "starlight-llms-txt"; + +/** + * `starlight-llms-txt` renders each docs entry for `/llms-full.txt` and `/llms-small.txt` through + * an Astro container that only registers the MDX SSR renderer, not React. MDX pages that import + * `.tsx` islands must be omitted from those exports or `astro build` fails with `NoMatchingRenderer`. + * + * Patterns use micromatch against each entry's `id` in the Starlight `docs` collection (paths are + * relative to `src/content/docs/`). + * + * The Interactive example imports a React playground and cannot be rendered by `starlight-llms-txt`. + * The Schema Reference imports a React playground and cannot be rendered by `starlight-llms-txt`. + */ +export const starlightLlmsTxtPlugin = starlightLlmsTxt({ + exclude: [ + "docs/integrate/integration-options/enssdk/example", + "docs/integrate/integration-options/enskit/example", + "docs/integrate/omnigraph/schema-reference", + ], +}); diff --git a/docs/ensnode.io/config/integrations/starlight/index.ts b/docs/ensnode.io/config/integrations/starlight/index.ts index ee976fb7bc..a40b1598fa 100644 --- a/docs/ensnode.io/config/integrations/starlight/index.ts +++ b/docs/ensnode.io/config/integrations/starlight/index.ts @@ -1,13 +1,14 @@ import AstroStarlight from "@astrojs/starlight"; import type { AstroIntegration } from "astro"; -import starlightLlmsTxt from "starlight-llms-txt"; import starlightSidebarTopics from "starlight-sidebar-topics"; +import { starlightLlmsTxtPlugin } from "../llms-txt"; import { starlightSidebarTopicsConfig } from "./sidebar-topics"; export function starlight(): AstroIntegration { return AstroStarlight({ components: { + PageFrame: "./src/components/overrides/PageFrame.astro", ThemeProvider: "./src/components/overrides/ThemeProvider.astro", ThemeSelect: "./src/components/overrides/ThemeSelect.astro", SocialIcons: "./src/components/overrides/SocialIcons.astro", @@ -29,7 +30,7 @@ export function starlight(): AstroIntegration { "@fontsource/inter/800.css", "@fontsource/inter/900.css", ], - plugins: [starlightLlmsTxt(), starlightSidebarTopics(starlightSidebarTopicsConfig)], + plugins: [starlightLlmsTxtPlugin, starlightSidebarTopics(starlightSidebarTopicsConfig)], title: "ENSNode", disable404Route: true, logo: { diff --git a/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts b/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts index b82cf04ef6..04a08eebee 100644 --- a/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts +++ b/docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts @@ -28,8 +28,62 @@ export const integrateSidebarTopic = { link: "/docs/integrate/omnigraph", }, { - label: "Cookbook", - link: "/docs/integrate/omnigraph/cookbook", + label: "Examples", + collapsed: false, + items: [ + { + label: "Overview", + link: "/docs/integrate/omnigraph/examples", + }, + { + label: "Domain By Name", + link: "/docs/integrate/omnigraph/examples/domain-by-name", + }, + { + label: "Find Domains", + link: "/docs/integrate/omnigraph/examples/find-domains", + }, + { + label: "Domain Subdomains", + link: "/docs/integrate/omnigraph/examples/domain-subdomains", + }, + { + label: "Domain Events", + link: "/docs/integrate/omnigraph/examples/domain-events", + }, + { + label: "Account Domains", + link: "/docs/integrate/omnigraph/examples/domains-by-address", + }, + { + label: "Account Events", + link: "/docs/integrate/omnigraph/examples/account-events", + }, + { + label: "Registry Domains", + link: "/docs/integrate/omnigraph/examples/registry-domains", + }, + { + label: "Permissions By Contract", + link: "/docs/integrate/omnigraph/examples/permissions-by-contract", + }, + { + label: "Permissions By User", + link: "/docs/integrate/omnigraph/examples/permissions-by-user", + }, + { + label: "Account Resolver Permissions", + link: "/docs/integrate/omnigraph/examples/account-resolver-permissions", + }, + { + label: "Domain Resolver", + link: "/docs/integrate/omnigraph/examples/domain-resolver", + }, + { + label: "Namegraph", + link: "/docs/integrate/omnigraph/examples/namegraph", + }, + ], }, { label: "Schema Reference", @@ -47,11 +101,31 @@ export const integrateSidebarTopic = { }, { label: "enskit (React)", - link: "/docs/integrate/integration-options/enskit", + collapsed: false, + items: [ + { + label: "Overview", + link: "/docs/integrate/integration-options/enskit", + }, + { + label: "⚡ Interactive example", + link: "/docs/integrate/integration-options/enskit/example", + }, + ], }, { label: "enssdk (TypeScript)", - link: "/docs/integrate/integration-options/enssdk", + collapsed: false, + items: [ + { + label: "Overview", + link: "/docs/integrate/integration-options/enssdk", + }, + { + label: "⚡ Interactive example", + link: "/docs/integrate/integration-options/enssdk/example", + }, + ], }, { label: "ENS Omnigraph (GraphQL)", diff --git a/docs/ensnode.io/package.json b/docs/ensnode.io/package.json index d03bc200f5..aac763862f 100644 --- a/docs/ensnode.io/package.json +++ b/docs/ensnode.io/package.json @@ -13,6 +13,8 @@ "astro": "astro", "lint": "biome check --write", "lint:ci": "biome ci", + "omnigraph-examples:refresh-responses": "tsx --tsconfig tsconfig.json scripts/fetch-omnigraph-example-responses.mts", + "test": "vitest", "generate:openapi": "pnpm --filter ensapi exec tsx --tsconfig tsconfig.json ../../scripts/generate-ensapi-openapi.mts" }, "dependencies": { @@ -26,6 +28,12 @@ "@iconify-json/lucide": "^1.2.52", "@namehash/namehash-ui": "workspace:*", "@octokit/rest": "^20.1.2", + "@ensnode/ensnode-sdk": "0.0.0-preview-fix-sha-89c022b-20260518142147", + "@graphiql/plugin-doc-explorer": "0.4.1", + "@graphiql/react": "0.37.1", + "enssdk": "0.0.0-preview-fix-sha-89c022b-20260518142147", + "graphql": "^16.10.0", + "@stackblitz/sdk": "^1.11.0", "@scalar/astro": "^0.2.16", "@tailwindcss/vite": "catalog:", "astro": "catalog:", @@ -40,12 +48,15 @@ "react-dom": "catalog:", "react-wrap-balancer": "^1.1.1", "sharp": "^0.33.5", + "yaml": "^2.8.3", "starlight-llms-txt": "^0.10.0", "starlight-sidebar-topics": "^0.7.1", "tailwindcss": "catalog:" }, "devDependencies": { "@types/react": "catalog:", - "@types/react-dom": "catalog:" + "@types/react-dom": "catalog:", + "tsx": "^4.19.3", + "vitest": "catalog:" } } diff --git a/docs/ensnode.io/scripts/fetch-omnigraph-example-responses.mts b/docs/ensnode.io/scripts/fetch-omnigraph-example-responses.mts new file mode 100644 index 0000000000..73ac401be3 --- /dev/null +++ b/docs/ensnode.io/scripts/fetch-omnigraph-example-responses.mts @@ -0,0 +1,97 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { getNamespaceSpecificValue } from "@ensnode/ensnode-sdk"; +import { getGraphqlApiExampleQueryById } from "@ensnode/ensnode-sdk/internal"; + +import { OMNIGRAPH_EXAMPLES_META } from "../src/data/omnigraph-examples/meta.ts"; +import { DOCS_OMNIGRAPH_NAMESPACE, ENSNODE_URL } from "../src/lib/playground/constants.ts"; + +function logStep(message: string, id?: string) { + console.log(`[omnigraph-examples] ${message} ${id ? `for '${id}'` : ""}`); +} + +function logError(message: string, id?: string) { + console.error(`[omnigraph-examples] ERROR: ${message} ${id ? `for example '${id}'` : ""}`); +} + +const allExampleIds = (Object.keys(OMNIGRAPH_EXAMPLES_META) as string[]).sort(); + +const outputPath = join( + dirname(fileURLToPath(import.meta.url)), + "../src/data/omnigraph-examples/responses.json", +); + +// Optional filter: `pnpm omnigraph-examples:refresh-responses ,` +const argIds = + process.argv[2] + ?.split(",") + .map((s) => s.trim()) + .filter(Boolean) ?? []; + +if (argIds.length > 0) { + const unknown = argIds.filter((id) => !allExampleIds.includes(id)); + if (unknown.length > 0) { + logError(`Unknown example ID(s): ${unknown.join(", ")}. Known: ${allExampleIds.join(", ")}`); + process.exit(1); + } +} + +const exampleIds = argIds.length > 0 ? argIds : allExampleIds; + +const base = ENSNODE_URL.replace(/\/+$/, ""); +const url = `${base}/api/omnigraph`; + +logStep( + argIds.length > 0 + ? `Refreshing ${exampleIds.length} of ${allExampleIds.length} examples from ${url}: ${exampleIds.join(", ")}` + : `Fetching all ${exampleIds.length} Omnigraph examples from ${url}`, +); + +// When refreshing a subset, load the existing responses so unaffected entries are preserved. +const out: Record = + argIds.length > 0 && existsSync(outputPath) + ? (JSON.parse(readFileSync(outputPath, "utf8")) as Record) + : {}; + +for (const id of exampleIds) { + logStep("Getting example query", id); + + const example = getGraphqlApiExampleQueryById(id); + const query = example.query.trim(); + const variables = getNamespaceSpecificValue(DOCS_OMNIGRAPH_NAMESPACE, example.variables); + + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query, variables }), + signal: AbortSignal.timeout(120_000), + }); + + if (!response.ok) { + const text = await response.text(); + logError(`HTTP ${response.status}. Body (first 800 chars):\n${text.slice(0, 800)}`, id); + process.exit(1); + } + + const body = await response.json(); + + if ( + typeof body === "object" && + body !== null && + "errors" in body && + Array.isArray((body as { errors: unknown }).errors) && + (body as { errors: unknown[] }).errors.length > 0 + ) { + logError(`GraphQL errors: ${JSON.stringify(body, null, 2)}`, id); + process.exit(1); + } + + out[id] = body; + logStep("Success", id); +} + +logStep(`Writing responses to ${outputPath}`); +writeFileSync(outputPath, `${JSON.stringify(out, null, 2)}\n`, "utf8"); +logStep("Done."); diff --git a/docs/ensnode.io/src/components/molecules/CodePlayground.tsx b/docs/ensnode.io/src/components/molecules/CodePlayground.tsx new file mode 100644 index 0000000000..f0bafe8638 --- /dev/null +++ b/docs/ensnode.io/src/components/molecules/CodePlayground.tsx @@ -0,0 +1,138 @@ +import sdk, { type EmbedOptions, type Project } from "@stackblitz/sdk"; +import { useEffect, useMemo, useRef } from "react"; + +import type { + PlaygroundProject, + PlaygroundRuntime, + PlaygroundView, +} from "src/lib/playground/example-project/types"; + +type CodePlaygroundProps = PlaygroundProject & { + height?: number; + terminalHeight?: number; +}; + +/** StackBlitz SDK templates: https://developer.stackblitz.com/platform/api/javascript-sdk-options#projecttemplate */ +const STACKBLITZ_WEBCONTAINERS_TEMPLATE = "node" as const; + +function buildStartScript(runtime: PlaygroundRuntime, entryFileName: string): string { + if (runtime === "node-vite") { + return "vite"; + } + return `tsx ${entryFileName}`; +} + +function embedViewForPlayground(view: PlaygroundView | undefined): EmbedOptions["view"] { + switch (view) { + case "preview": + return "preview"; + case "both": + return "default"; + default: + return "editor"; + } +} + +export default function CodePlayground({ + title, + description, + runtime, + files, + dependencies, + devDependencies, + entryFileName, + openFile, + view, + tsconfig, + height = 500, + terminalHeight = 35, +}: CodePlaygroundProps) { + const ref = useRef(null); + const resolvedOpenFile = openFile ?? entryFileName; + + const project = useMemo(() => { + const defaultTsconfig = JSON.stringify( + { + compilerOptions: { + target: "es2022", + module: "nodenext", + moduleResolution: "nodenext", + strict: true, + }, + }, + null, + 2, + ); + + const packageJson = JSON.stringify( + { + name: title.toLowerCase().replace(/\s+/g, "-"), + version: "0.0.0", + private: true, + type: "module", + scripts: { + dev: buildStartScript(runtime, entryFileName), + start: buildStartScript(runtime, entryFileName), + }, + dependencies, + devDependencies, + }, + null, + 2, + ); + + const projectFiles = { + "package.json": packageJson, + ...files, + "tsconfig.json": files["tsconfig.json"] ?? tsconfig ?? defaultTsconfig, + }; + + return { + title, + description, + template: STACKBLITZ_WEBCONTAINERS_TEMPLATE, + files: projectFiles, + } as Project; + }, [title, description, runtime, files, dependencies, devDependencies, entryFileName, tsconfig]); + + const embedOptions = useMemo( + () => + ({ + openFile: resolvedOpenFile, + terminalHeight, + height, + hideNavigation: true, + hideExplorer: true, + hideDevTools: true, + showSidebar: true, + view: embedViewForPlayground(view), + theme: "light", + }) as EmbedOptions, + [resolvedOpenFile, terminalHeight, height, view], + ); + + // `project` and `embedOptions` are memoized from all embed-affecting props. + useEffect(() => { + const container = ref.current; + if (!container) return; + + let disposed = false; + + void (async () => { + container.replaceChildren(); + await sdk.embedProject(container, project, embedOptions); + if (disposed) container.replaceChildren(); + })(); + + return () => { + disposed = true; + container.replaceChildren(); + }; + }, [project, embedOptions]); + + return ( +
+
+
+ ); +} diff --git a/docs/ensnode.io/src/components/molecules/HeaderButtons.tsx b/docs/ensnode.io/src/components/molecules/HeaderButtons.tsx index 6f2d2cc9c9..3a2706879b 100644 --- a/docs/ensnode.io/src/components/molecules/HeaderButtons.tsx +++ b/docs/ensnode.io/src/components/molecules/HeaderButtons.tsx @@ -2,7 +2,7 @@ import HeaderMobileNavigation from "@workspace/docs/ensnode.io/src/components/mo import { GithubIcon } from "@workspace/docs/ensrainbow.io/src/components/atoms/icons/GithubIcon.tsx"; import { TelegramIcon } from "@workspace/docs/ensrainbow.io/src/components/atoms/icons/TelegramIcon.tsx"; import { TwitterIcon } from "@workspace/docs/ensrainbow.io/src/components/atoms/icons/TwitterIcon.tsx"; -import "../../styles/onScrollHeader.css"; +import "@styles/onScrollHeader.css"; import cc from "classcat"; diff --git a/docs/ensnode.io/src/components/molecules/HeaderMobileNavigation.tsx b/docs/ensnode.io/src/components/molecules/HeaderMobileNavigation.tsx index 86fe90707f..4dece9a81f 100644 --- a/docs/ensnode.io/src/components/molecules/HeaderMobileNavigation.tsx +++ b/docs/ensnode.io/src/components/molecules/HeaderMobileNavigation.tsx @@ -8,7 +8,7 @@ import { TwitterIcon } from "@workspace/docs/ensrainbow.io/src/components/atoms/ import cc from "classcat"; import { Fragment } from "react"; -import ENSNode2D from "../../assets/dark-logo.svg"; +import ENSNode2D from "@assets/dark-logo.svg"; export default function HeaderMobileNavigation() { const MobileNavigationLinks = [ diff --git a/docs/ensnode.io/src/components/molecules/IntegrateHostedEnsNodeTip.astro b/docs/ensnode.io/src/components/molecules/IntegrateHostedEnsNodeTip.astro index c2a57f8849..99187a3479 100644 --- a/docs/ensnode.io/src/components/molecules/IntegrateHostedEnsNodeTip.astro +++ b/docs/ensnode.io/src/components/molecules/IntegrateHostedEnsNodeTip.astro @@ -1,32 +1,13 @@ --- import { Aside, LinkCard } from "@astrojs/starlight/components"; - -type Variant = "withDeployments" | "quickstart"; - -interface Props { - variant?: Variant; - includeHostedInstancesLink?: boolean; -} - -const { variant = "withDeployments", includeHostedInstancesLink = false } = Astro.props; - -const body: Record = { - withDeployments: - "You don't need to run your own ENSNode to follow this guide — the steps below default to a NameHash-hosted instance. Browse the available deployments below.", - quickstart: - "You don't need to run your own ENSNode to follow this guide — point at any NameHash-hosted instance to get started.", -}; --- + + -{ - includeHostedInstancesLink ? ( - - ) : null -} diff --git a/docs/ensnode.io/src/components/molecules/JoinTelegram.tsx b/docs/ensnode.io/src/components/molecules/JoinTelegram.tsx index ba63b9a1d5..da8f65765d 100644 --- a/docs/ensnode.io/src/components/molecules/JoinTelegram.tsx +++ b/docs/ensnode.io/src/components/molecules/JoinTelegram.tsx @@ -2,8 +2,8 @@ import { legacyButtonVariants } from "@namehash/namehash-ui/legacy"; import { TelegramIcon } from "@workspace/docs/ensrainbow.io/src/components/atoms/icons/TelegramIcon.tsx"; import { Balancer } from "react-wrap-balancer"; -import TelegramBanner from "../../assets/telegram_image.svg"; -import MobileTelegramBanner from "../../assets/telegram_mobile_image.svg"; +import TelegramBanner from "@assets/telegram_image.svg"; +import MobileTelegramBanner from "@assets/telegram_mobile_image.svg"; export default function JoinTelegram() { return ( diff --git a/docs/ensnode.io/src/components/molecules/OmnigraphAPIExamplePanels.astro b/docs/ensnode.io/src/components/molecules/OmnigraphAPIExamplePanels.astro new file mode 100644 index 0000000000..efa33c3bcc --- /dev/null +++ b/docs/ensnode.io/src/components/molecules/OmnigraphAPIExamplePanels.astro @@ -0,0 +1,305 @@ +--- +import { Code } from "@astrojs/starlight/components"; + +import { buildOmnigraphCurlExample, getHostedEnsNodeInstanceDocUrl } from "@lib/playground/utils"; + +export interface OmnigraphAPIExamplePanelsProps { + /** Unique id prefix for aria / tab wiring (e.g. example id). */ + panelId: string; + query: string; + variablesJson: string; + variablesObject: Record; + responseJson: string | null; + /** ENSNode base URL (same as enssdk `createEnsNodeClient({ url })`). */ + connectionBaseUrl: string; + /** Starlight heading anchor on the hosted instances page (e.g. `ensnode-v2-sepolia`). */ + hostedInstanceAnchor: string; + /** ENS namespace label for the hosted instance (e.g. `sepolia-v2`). */ + hostedInstanceNamespace: string; + adminUrl: string; + hideCurl?: boolean; +} + +const { + panelId, + query, + variablesJson, + variablesObject, + responseJson, + connectionBaseUrl, + hostedInstanceAnchor, + hostedInstanceNamespace, + adminUrl, + hideCurl = false, +} = Astro.props; + +const hostedInstanceDocUrl = getHostedEnsNodeInstanceDocUrl(hostedInstanceAnchor); + +const uid = panelId.replace(/[^a-zA-Z0-9_-]/g, "-"); +const tabIds = { + vars: `${uid}-tab-vars`, + resp: `${uid}-tab-resp`, + curl: `${uid}-tab-curl`, +} as const; + +const curlExample = !hideCurl + ? buildOmnigraphCurlExample({ connectionBaseUrl, query, variables: variablesObject }) + : ""; +const tabList: { id: string; label: string }[] = [ + { id: tabIds.vars, label: "Variables" }, + ...(responseJson ? [{ id: tabIds.resp, label: "Response" }] : []), + ...(!hideCurl && curlExample ? [{ id: tabIds.curl, label: "Curl" }] : []), +]; +--- + +
+
+ + Run in ENSAdmin + + +
+ +
{query}
+
{variablesJson}
+ {responseJson ?
{responseJson}
: null} + {curlExample ?
{curlExample}
: null} + +
+
+ + ENS Omnigraph GraphQL + + +
+
+ +
+
+ +
+

Payload and transport examples

+ +
+
+ { + tabList.map((t, i) => ( + + )) + } +
+ +
+ +
+
+
+ +
+
+ + { + responseJson ? ( + + ) : null + } + + { + !hideCurl && curlExample ? ( + + ) : null + } + +
+
+
+ +

+ Response is an illustrative snapshot; live data depends on your ENSNode instance. The curl tab shows a POST to + {connectionBaseUrl}/api/omnigraph +

+
+ + + + diff --git a/docs/ensnode.io/src/components/molecules/StaticHeader.astro b/docs/ensnode.io/src/components/molecules/StaticHeader.astro index f014bef6ee..1399498fa8 100644 --- a/docs/ensnode.io/src/components/molecules/StaticHeader.astro +++ b/docs/ensnode.io/src/components/molecules/StaticHeader.astro @@ -1,5 +1,5 @@ --- -import ENSNode2DLight from "../../assets/light-logo.svg"; +import ENSNode2DLight from "@assets/light-logo.svg"; import CustomSearch from "../molecules/CustomSearch.astro"; import HeaderButtons from "../molecules/HeaderButtons"; --- diff --git a/docs/ensnode.io/src/components/molecules/TelegramInvite.astro b/docs/ensnode.io/src/components/molecules/TelegramInvite.astro index e293dcf150..10eb41436c 100644 --- a/docs/ensnode.io/src/components/molecules/TelegramInvite.astro +++ b/docs/ensnode.io/src/components/molecules/TelegramInvite.astro @@ -3,8 +3,8 @@ import { GithubIcon } from "@workspace/docs/ensrainbow.io/src/components/atoms/i import { TelegramIcon } from "@workspace/docs/ensrainbow.io/src/components/atoms/icons/TelegramIcon.tsx"; import cc from "classcat"; import { Balancer } from "react-wrap-balancer"; -import TelegramBanner from "../../assets/telegram_image.svg"; -import MobileTelegramBanner from "../../assets/telegram_mobile_image.svg"; +import TelegramBanner from "@assets/telegram_image.svg"; +import MobileTelegramBanner from "@assets/telegram_mobile_image.svg"; import { ENSNodeLogoLight } from "../atoms/logos/ENSNodeLogoLight"; const buttonStyles = diff --git a/docs/ensnode.io/src/components/organisms/ENSNodeSuite.tsx b/docs/ensnode.io/src/components/organisms/ENSNodeSuite.tsx index a4e6af0578..b58de54cc3 100644 --- a/docs/ensnode.io/src/components/organisms/ENSNodeSuite.tsx +++ b/docs/ensnode.io/src/components/organisms/ENSNodeSuite.tsx @@ -4,9 +4,9 @@ import cc from "classcat"; import { Fragment } from "react"; import { Balancer } from "react-wrap-balancer"; -import ENSAdmin3DImage from "../../assets/ENSAdmin3D.png"; -import ENSIndexer3DImage from "../../assets/ENSIndexer3D.png"; -import ENSRainbow3DImage from "../../assets/ENSRainbow3D.png"; +import ENSAdmin3DImage from "@assets/ENSAdmin3D.png"; +import ENSIndexer3DImage from "@assets/ENSIndexer3D.png"; +import ENSRainbow3DImage from "@assets/ENSRainbow3D.png"; import JoinTelegram from "../molecules/JoinTelegram.tsx"; const appsSuite: { diff --git a/docs/ensnode.io/src/components/organisms/EnskitExampleInteractivePlayground.tsx b/docs/ensnode.io/src/components/organisms/EnskitExampleInteractivePlayground.tsx new file mode 100644 index 0000000000..18571e22a6 --- /dev/null +++ b/docs/ensnode.io/src/components/organisms/EnskitExampleInteractivePlayground.tsx @@ -0,0 +1,19 @@ +import { loadEnskitExampleProject } from "src/lib/playground/loadEnskitExampleProject"; + +import CodePlayground from "../molecules/CodePlayground"; + +type EnskitExampleInteractivePlaygroundProps = { + height?: number; + terminalHeight?: number; +}; + +const enskitExampleProject = loadEnskitExampleProject(); + +export default function EnskitExampleInteractivePlayground({ + height = 800, + terminalHeight = 25, +}: EnskitExampleInteractivePlaygroundProps) { + return ( + + ); +} diff --git a/docs/ensnode.io/src/components/organisms/EnssdkExampleInteractivePlayground.tsx b/docs/ensnode.io/src/components/organisms/EnssdkExampleInteractivePlayground.tsx new file mode 100644 index 0000000000..2fae91a4eb --- /dev/null +++ b/docs/ensnode.io/src/components/organisms/EnssdkExampleInteractivePlayground.tsx @@ -0,0 +1,27 @@ +import { loadEnssdkExampleProject } from "src/lib/playground/loadEnssdkExampleProject"; +import { getNiceHeightForCodeSnippet } from "src/lib/playground/utils"; + +import CodePlayground from "../molecules/CodePlayground"; + +type EnssdkExampleInteractivePlaygroundProps = { + height?: number; + terminalHeight?: number; +}; + +const enssdkExampleProject = loadEnssdkExampleProject(); + +export default function EnssdkExampleInteractivePlayground({ + height, + terminalHeight, +}: EnssdkExampleInteractivePlaygroundProps) { + const entrySource = enssdkExampleProject.files[enssdkExampleProject.entryFileName] ?? ""; + const resolvedHeight = height ?? getNiceHeightForCodeSnippet(entrySource); + + return ( + + ); +} diff --git a/docs/ensnode.io/src/components/organisms/ExampleCard.astro b/docs/ensnode.io/src/components/organisms/ExampleCard.astro index d3d6bab088..891fc457c3 100644 --- a/docs/ensnode.io/src/components/organisms/ExampleCard.astro +++ b/docs/ensnode.io/src/components/organisms/ExampleCard.astro @@ -1,7 +1,7 @@ --- import { ENSADMIN_URL } from "astro:env/client"; import { Icon } from "astro-icon/components"; -import type { SavedQuery } from "../../data/savedQueries"; +import type { SavedQuery } from "@data/ens-v1-examples-queries"; export interface Props { example: SavedQuery; diff --git a/docs/ensnode.io/src/components/organisms/Header.astro b/docs/ensnode.io/src/components/organisms/Header.astro index 0f27fa07d1..1ab0787c3f 100644 --- a/docs/ensnode.io/src/components/organisms/Header.astro +++ b/docs/ensnode.io/src/components/organisms/Header.astro @@ -1,7 +1,7 @@ --- -import ENSNode2DDark from "../../assets/dark-logo.svg"; -import ENSNode2DLight from "../../assets/light-logo.svg"; -import ScrollHeader from "../../scripts/ScrollHeader"; +import ENSNode2DDark from "@assets/dark-logo.svg"; +import ENSNode2DLight from "@assets/light-logo.svg"; +import ScrollHeader from "@scripts/ScrollHeader"; import CustomSearch from "../molecules/CustomSearch.astro"; import HeaderButtons from "../molecules/HeaderButtons"; --- diff --git a/docs/ensnode.io/src/components/organisms/InfrastructureInnovations.tsx b/docs/ensnode.io/src/components/organisms/InfrastructureInnovations.tsx index 0e44b716bf..0cbeda4885 100644 --- a/docs/ensnode.io/src/components/organisms/InfrastructureInnovations.tsx +++ b/docs/ensnode.io/src/components/organisms/InfrastructureInnovations.tsx @@ -7,9 +7,9 @@ import { LostENSNamesImage } from "@workspace/docs/ensnode.io/src/components/ato import { Fragment } from "react"; import { Balancer } from "react-wrap-balancer"; -import ENSAdminDecentralizationMobileImage from "../../assets/Decentralization.png"; -import InfrastructureGapsMobileImage from "../../assets/InfrastructureGaps.png"; -import ProtocolInnovationsMobileImage from "../../assets/ProtocolInnovations.png"; +import ENSAdminDecentralizationMobileImage from "@assets/Decentralization.png"; +import InfrastructureGapsMobileImage from "@assets/InfrastructureGaps.png"; +import ProtocolInnovationsMobileImage from "@assets/ProtocolInnovations.png"; import SectionDivider from "../atoms/SectionDivider.tsx"; import InnovationSection, { type InnovationSectionProps } from "../molecules/InnovationSection.tsx"; @@ -30,7 +30,7 @@ export default function InfrastructureInnovations() {
{innovationSections.map((section) => ( - + ))} diff --git a/docs/ensnode.io/src/components/organisms/OmnigraphAPIExample.astro b/docs/ensnode.io/src/components/organisms/OmnigraphAPIExample.astro new file mode 100644 index 0000000000..e7112d6acf --- /dev/null +++ b/docs/ensnode.io/src/components/organisms/OmnigraphAPIExample.astro @@ -0,0 +1,51 @@ +--- +import OmnigraphAPIExamplePanels from "../molecules/OmnigraphAPIExamplePanels.astro"; + +import { ENSADMIN_URL } from "astro:env/client"; + +import { getOmnigraphExampleById } from "@data/omnigraph-examples/examples"; +import { DOCS_HOSTED_INSTANCE_ANCHOR, DOCS_OMNIGRAPH_NAMESPACE } from "@lib/playground/constants"; +import { buildEnsadminOmnigraphUrl, stringifyJsonForDocs } from "@lib/playground/utils"; + +interface Props { + id: string; + /** Hide curl tab (e.g. if connection is admin-only). */ + hideCurl?: boolean; + hideDescription?: boolean; + hideBackToExamples?: boolean; +} + +const { id, hideCurl = false, hideDescription = false, hideBackToExamples = false } = Astro.props; +const example = getOmnigraphExampleById(id); +const adminUrl = buildEnsadminOmnigraphUrl({ + ensadminBaseUrl: ENSADMIN_URL, + query: example.query, + connection: example.connection, + variables: example.variables, +}); +const variablesJson = stringifyJsonForDocs(example.variables); +const responseJson = example.response ? stringifyJsonForDocs(example.response) : null; +--- + +{!hideDescription && example.description} + + + +{ + !hideBackToExamples && ( +

+ Back to Examples +

+ ) +} diff --git a/docs/ensnode.io/src/components/organisms/OmnigraphSchemaDocExplorer.tsx b/docs/ensnode.io/src/components/organisms/OmnigraphSchemaDocExplorer.tsx new file mode 100644 index 0000000000..4fd5e68419 --- /dev/null +++ b/docs/ensnode.io/src/components/organisms/OmnigraphSchemaDocExplorer.tsx @@ -0,0 +1,41 @@ +import "@graphiql/react/style.css"; +import "@graphiql/plugin-doc-explorer/style.css"; + +import { DocExplorer, DocExplorerStore } from "@graphiql/plugin-doc-explorer"; +import { GraphiQLProvider } from "@graphiql/react"; +import omnigraphSchemaSdl from "enssdk/omnigraph/schema.graphql?raw"; +import { buildSchema } from "graphql"; + +const omnigraphSchema = buildSchema(omnigraphSchemaSdl); + +export default function OmnigraphSchemaDocExplorer() { + return ( +
+
+ Promise.resolve({})} + > + + +
+
+ ); +} diff --git a/docs/ensnode.io/src/components/overrides/Hero.astro b/docs/ensnode.io/src/components/overrides/Hero.astro index 84ad21eccf..da265f8225 100644 --- a/docs/ensnode.io/src/components/overrides/Hero.astro +++ b/docs/ensnode.io/src/components/overrides/Hero.astro @@ -1,7 +1,7 @@ --- import { Image } from "astro:assets"; import { Balancer } from "react-wrap-balancer"; -import skies_image from "../../assets/hero_skies_image.png"; +import skies_image from "@assets/hero_skies_image.png"; ---
background skies diff --git a/docs/ensnode.io/src/components/overrides/PageFrame.astro b/docs/ensnode.io/src/components/overrides/PageFrame.astro new file mode 100644 index 0000000000..dd580dbea3 --- /dev/null +++ b/docs/ensnode.io/src/components/overrides/PageFrame.astro @@ -0,0 +1,101 @@ +--- +import MobileMenuToggle from "virtual:starlight/components/MobileMenuToggle"; + +const { hasSidebar, entry } = Astro.locals.starlightRoute; +const sidebarDocked = entry.data.sidebarDocked === true; +--- + +
+
+ { + hasSidebar && ( + + ) + } +
+
+ + diff --git a/docs/ensnode.io/src/content.config.ts b/docs/ensnode.io/src/content.config.ts index 07852d5177..1779f2a927 100644 --- a/docs/ensnode.io/src/content.config.ts +++ b/docs/ensnode.io/src/content.config.ts @@ -1,8 +1,9 @@ import { defineCollection } from "astro:content"; +import { z } from "astro/zod"; import { docsLoader } from "@astrojs/starlight/loaders"; import { docsSchema } from "@astrojs/starlight/schema"; -import { exampleQuerySchema, savedQueries } from "./data/savedQueries"; +import { exampleQuerySchema, savedQueries } from "./data/ens-v1-examples-queries"; const examples = defineCollection({ loader: () => @@ -14,6 +15,14 @@ const examples = defineCollection({ }); export const collections = { - docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), + docs: defineCollection({ + loader: docsLoader(), + schema: docsSchema({ + extend: z.object({ + /** Collapse the global sidebar off-canvas on desktop; peek strip expands on hover. */ + sidebarDocked: z.boolean().optional(), + }), + }), + }), examples, }; diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/hosted-instances.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/hosted-instances.mdx index 5ab490cd6b..b2c66a17aa 100644 --- a/docs/ensnode.io/src/content/docs/docs/integrate/hosted-instances.mdx +++ b/docs/ensnode.io/src/content/docs/docs/integrate/hosted-instances.mdx @@ -5,7 +5,7 @@ sidebar: order: 1 --- -import HostedEnsNodeInstances from "../../../../components/molecules/HostedEnsNodeInstances.astro"; +import HostedEnsNodeInstances from "@components/molecules/HostedEnsNodeInstances.astro"; ## Hosted Instances diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/index.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/index.mdx index 9f1938b803..d36ea0815b 100644 --- a/docs/ensnode.io/src/content/docs/docs/integrate/index.mdx +++ b/docs/ensnode.io/src/content/docs/docs/integrate/index.mdx @@ -7,10 +7,11 @@ sidebar: --- import { LinkCard, CardGrid, Aside } from "@astrojs/starlight/components"; +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; ## What is ENSv2? -ENSv2 is the next generation of the Ethereum Name Service — a protocol upgrade that fundamentally changes the onchain data model of ENS. +[ENSv2](https://ens.domains/ensv2) is the next generation of the Ethereum Name Service — a protocol upgrade that fundamentally changes the onchain data model of ENS. :::tip[Prepare for ENSv2] The ENSv2 upgrade to the ENS protocol is coming **Summer 2026**! Your app, regardless of how it interacts with names, needs to be updated to avoid being left behind. @@ -127,6 +128,12 @@ export function RenderDomainAndSubdomains({ name }: { name: InterpretedName }) { href="https://github.com/namehash/ensnode/tree/main/examples/enskit-react-example" /> + + ### 2. `enssdk` + Omnigraph With `enssdk`, leverage ENSNode and the Omnigraph from any JavaScript runtime to power your frontend or backend apps. `enssdk` comes with built-in type-safety and editor autocomplete for Omnigraph queries. @@ -162,24 +169,19 @@ const result = await client.omnigraph.query({ query: HelloWorldQuery }); href="https://github.com/namehash/ensnode/tree/main/examples/enssdk-example" /> + + + ### 3. ENS Omnigraph GraphQL API The ENS Omnigraph API is a GraphQL API following the Relay specification, so you get built-in support for efficient infinite pagination and idiomatic access to all of the ENS protocol within a _unified_ ENSv1 + ENSv2 datamodel. -```gql -query MyDomains($address: Address!) { - account(by: { address: $address }) { - domains { - edges { - node { - label { interpreted } - canonical { name { interpreted } } - } - } - } - } -} -``` + + + +[Back to enskit overview](/docs/integrate/integration-options/enskit) diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enskit.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enskit/index.mdx similarity index 94% rename from docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enskit.mdx rename to docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enskit/index.mdx index f96c06a8a1..101dd4bec0 100644 --- a/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enskit.mdx +++ b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enskit/index.mdx @@ -4,13 +4,13 @@ description: React toolkit for ENSv2 development, includes fully typed providers --- import { LinkCard, Steps } from '@astrojs/starlight/components'; -import IntegrateHostedEnsNodeTip from '../../../../../components/molecules/IntegrateHostedEnsNodeTip.astro'; +import IntegrateHostedEnsNodeTip from '@components/molecules/IntegrateHostedEnsNodeTip.astro'; `enskit` is the React toolkit for ENSv2 development. It provides a fully typed Omnigraph API client (powered by [`urql`](https://nearform.com/open-source/urql/) and [`gql.tada`](https://gql-tada.0no.co/)), the `OmnigraphProvider`, and the `useOmnigraphQuery` hook for writing type-safe ENS queries with editor autocomplete, Relay-style pagination, and Omnigraph-specific cache directives. This guide walks you from an empty directory to a working React component that renders an [ENS Domain](/docs/concepts/the-ens-protocol) and a paginated list of its subdomains — the same flow as the [`DomainView`](https://github.com/namehash/ensnode/blob/main/examples/enskit-react-example/src/DomainView.tsx) in our example app. - + ## 1. Scaffold a React app @@ -386,13 +386,20 @@ Open the printed URL and you should see the `eth` Domain, its owner, and the fir ## Where to go next +- Try the [Interactive Example](/docs/integrate/integration-options/enskit/example): edit and run the full `enskit-react-example` app in your browser with a live preview. - Swap the hardcoded `"eth"` for a name from props or a router — see [`EnsureInterpretedName`](https://github.com/namehash/ensnode/blob/main/examples/enskit-react-example/src/DomainView.tsx) in the example app for safe handling of user-provided names. -- See the [Omnigraph Cookbook](/docs/integrate/omnigraph/cookbook) for ready-to-copy queries: account-owned domains, events, registrar permissions, full-text search, and more. +- See [Omnigraph examples](/docs/integrate/omnigraph/examples) for ready-to-copy queries: account-owned domains, events, registrar permissions, full-text search, and more. - See the [Omnigraph Schema Reference](/docs/integrate/omnigraph/schema-reference) for the full set of types, fields, and arguments you can query. - Need data outside React? Use [`enssdk`](/docs/integrate/integration-options/enssdk) directly with the same `graphql(...)` helper. + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk/example.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk/example.mdx new file mode 100644 index 0000000000..3028bcae7c --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk/example.mdx @@ -0,0 +1,22 @@ +--- +title: ⚡ Interactive enssdk example +description: Edit and run the enssdk-example script against a live ENSNode endpoint in your browser. +tableOfContents: false +sidebarDocked: true +sidebar: + order: 2 +--- + +import EnssdkExampleInteractivePlayground from "@components/organisms/EnssdkExampleInteractivePlayground"; + +This playground loads the same source as [`enssdk-example`](https://github.com/namehash/ensnode/tree/main/examples/enssdk-example): a TypeScript script that queries the `eth` domain and lists its first 20 subdomains via [`enssdk`](/docs/integrate/integration-options/enssdk) and the [ENS Omnigraph API](/docs/integrate/omnigraph). + +:::note[First load may take a moment] +The editor runs entirely in your browser. The editor runs entirely in your browser. Downloading of **enssdk** and heavy dependencies may take 30-60 seconds. +::: + +You can edit the script and run **`npm start`** in the terminal to run it again. + + + +[Back to enssdk overview](/docs/integrate/integration-options/enssdk) diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk/index.mdx similarity index 95% rename from docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk.mdx rename to docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk/index.mdx index f61d2a7a19..7d632d3600 100644 --- a/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk.mdx +++ b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/enssdk/index.mdx @@ -4,7 +4,7 @@ description: SDK for ENSv2 development in TypeScript/JavaScript. --- import { LinkCard } from '@astrojs/starlight/components'; -import IntegrateHostedEnsNodeTip from '../../../../../components/molecules/IntegrateHostedEnsNodeTip.astro'; +import IntegrateHostedEnsNodeTip from '@components/molecules/IntegrateHostedEnsNodeTip.astro'; `enssdk` is the foundational TypeScript/JavaScript SDK for ENS development. It's a fully modular and tree-shakeable modern TypeScript/JavaScript package that provides ENS-specific types and helpers — usable from any JS runtime, browser or server. @@ -12,7 +12,7 @@ import IntegrateHostedEnsNodeTip from '../../../../../components/molecules/Integ This guide walks you from an empty directory to a working TypeScript script that queries the `eth` Domain and queries its subdomains — the same flow as our [enssdk-example](https://github.com/namehash/ensnode/tree/main/examples/enssdk-example). - + ## 1. Scaffold a TypeScript project @@ -39,6 +39,7 @@ npm install -D tsx typescript @types/node Always pin an **exact** version (no `^` or `~`) of `enssdk`. The Omnigraph GraphQL schema is bundled inside `enssdk` and consumed by the `gql.tada` TypeScript plugin to type your queries — a minor or patch bump can change the schema and silently drift your generated types away from your queries. Locking the exact version keeps types and runtime in sync. ::: + ## 3. Configure the `gql.tada` TypeScript plugin `gql.tada` is what gives your `graphql(...)` query strings end-to-end type safety. It reads the Omnigraph schema from `enssdk` at typecheck time. @@ -293,12 +294,18 @@ You should see the `eth` Domain, followed by its first 20 subdomains and the tot ## Where to go next -- See the [Omnigraph Cookbook](/docs/integrate/omnigraph/cookbook) for ready-to-copy queries: account-owned domains, events, registrar permissions, full-text search, and more. +- See [Omnigraph examples](/docs/integrate/omnigraph/examples) for ready-to-copy GraphQL queries: account-owned domains, events, registrar permissions, full-text search, and more. - See the [Omnigraph Schema Reference](/docs/integrate/omnigraph/schema-reference) for the full set of types, fields, and arguments you can query. - Building a React app? Use [`enskit`](/docs/integrate/integration-options/enskit) — same `graphql(...)` helper, with `useOmnigraphQuery` and a graphcache. + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/omnigraph-graphql-api.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/omnigraph-graphql-api.mdx index a7831c10a9..4978309b70 100644 --- a/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/omnigraph-graphql-api.mdx +++ b/docs/ensnode.io/src/content/docs/docs/integrate/integration-options/omnigraph-graphql-api.mdx @@ -4,7 +4,7 @@ description: Query the ENS Omnigraph API directly via HTTP from any language. --- import { LinkCard } from '@astrojs/starlight/components'; -import IntegrateHostedEnsNodeTip from '../../../../../components/molecules/IntegrateHostedEnsNodeTip.astro'; +import IntegrateHostedEnsNodeTip from '@components/molecules/IntegrateHostedEnsNodeTip.astro'; The Omnigraph is **a GraphQL API** following the [Relay specification](https://relay.dev/graphql/connections.htm). There's no proprietary protocol or transport — any GraphQL client in any language works, from `curl` and `fetch` to [`urql`](https://nearform.com/open-source/urql/), [`Apollo`](https://www.apollographql.com/), [`graphql-request`](https://github.com/jasonkuhrt/graphql-request), and beyond. @@ -12,7 +12,7 @@ This guide walks you through the minimum: a single `fetch` call against `/api/om If you want end-to-end typed queries (via [`gql.tada`](https://gql-tada.0no.co/)) with editor autocomplete and a built-in client, use [`enssdk`](/docs/integrate/integration-options/enssdk) instead — but if you need to integrate from a language without first-class GraphQL tooling, or you're already in a stack with its own GraphQL client, this is the path. - + ## 1. The endpoint @@ -165,7 +165,7 @@ You should see the `eth` Domain, its owner, and the first 20 of its subdomains. - Want typed queries with editor autocomplete and a real GraphQL client? Use [`enssdk`](/docs/integrate/integration-options/enssdk) — same API, with `gql.tada` types and an `EnsNodeClient`. - Building a React app? Use [`enskit`](/docs/integrate/integration-options/enskit) — same `graphql(...)` helper plus `useOmnigraphQuery` and a graphcache. -- See the [Omnigraph Cookbook](/docs/integrate/omnigraph/cookbook) for ready-to-copy queries: account-owned domains, events, registrar permissions, full-text search, and more. +- See [Omnigraph examples](/docs/integrate/omnigraph/examples) for ready-to-copy queries: account-owned domains, events, registrar permissions, full-text search, and more. - See the [Omnigraph Schema Reference](/docs/integrate/omnigraph/schema-reference) for the full set of types, fields, and arguments you can query. + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/account-resolver-permissions.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/account-resolver-permissions.mdx new file mode 100644 index 0000000000..46570127c0 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/account-resolver-permissions.mdx @@ -0,0 +1,11 @@ +--- +title: Account Resolver Permissions +description: Resolver contracts where an account holds resolver-scoped permissions. +sidebar: + order: 11 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-by-name.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-by-name.mdx new file mode 100644 index 0000000000..a21cefbab7 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-by-name.mdx @@ -0,0 +1,11 @@ +--- +title: Domain By Name +description: Omnigraph query for a single domain with v1/v2 fields. +sidebar: + order: 2 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-events.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-events.mdx new file mode 100644 index 0000000000..f3ecbf22e3 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-events.mdx @@ -0,0 +1,11 @@ +--- +title: Domain Events +description: Contract events linked to a domain’s on-chain records. +sidebar: + order: 5 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-resolver.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-resolver.mdx new file mode 100644 index 0000000000..9796c2262f --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-resolver.mdx @@ -0,0 +1,10 @@ +--- +title: Domain Resolver +description: Assigned resolver, records, permissions, and resolver events for a name. +sidebar: + order: 12 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-subdomains.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-subdomains.mdx new file mode 100644 index 0000000000..f2e0401e1b --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domain-subdomains.mdx @@ -0,0 +1,11 @@ +--- +title: Domain Subdomains +description: Paginate child names under a parent domain. +sidebar: + order: 4 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domains-by-address.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domains-by-address.mdx new file mode 100644 index 0000000000..8b70851c4a --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/domains-by-address.mdx @@ -0,0 +1,11 @@ +--- +title: Account Domains +description: Omnigraph query listing domains for an address. +sidebar: + order: 6 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/find-domains.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/find-domains.mdx new file mode 100644 index 0000000000..c6dad333a9 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/find-domains.mdx @@ -0,0 +1,11 @@ +--- +title: Find Domains +description: List domains by name filter with ordering and registration fields. +sidebar: + order: 3 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/index.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/index.mdx new file mode 100644 index 0000000000..3721ebbeb0 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/index.mdx @@ -0,0 +1,23 @@ +--- +title: ENS Omnigraph Queries Examples +description: Ready-to-run ENS Omnigraph GraphQL examples with variables, sample JSON, cURL, and ENSAdmin links. +sidebar: + order: 1 +--- + +import { LinkCard, CardGrid } from "@astrojs/starlight/components"; + +import { graphqlApiOmnigraphExamples } from "@data/omnigraph-examples/examples"; + +The **Omnigraph examples** are GraphQL queries with input variables, response examples, `curl` samples, and links to the ENSAdmin playground. + + + {graphqlApiOmnigraphExamples.map((recipe) => ( + + ))} + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/namegraph.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/namegraph.mdx new file mode 100644 index 0000000000..e84bfe45c7 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/namegraph.mdx @@ -0,0 +1,11 @@ +--- +title: Namegraph +description: Explore the root tree with nested subdomain connections. +sidebar: + order: 13 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/permissions-by-contract.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/permissions-by-contract.mdx new file mode 100644 index 0000000000..0673cc57c5 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/permissions-by-contract.mdx @@ -0,0 +1,11 @@ +--- +title: Permissions By Contract +description: Role assignments on resources for a registrar or registry contract. +sidebar: + order: 9 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/permissions-by-user.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/permissions-by-user.mdx new file mode 100644 index 0000000000..64f8125f35 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/permissions-by-user.mdx @@ -0,0 +1,11 @@ +--- +title: Permissions By User +description: Resources and roles granted to an address in the permissions graph. +sidebar: + order: 10 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/registry-domains.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/registry-domains.mdx new file mode 100644 index 0000000000..8e78735bb8 --- /dev/null +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/examples/registry-domains.mdx @@ -0,0 +1,11 @@ +--- +title: Registry Domains +description: Domains registered under a v2 ETH registry contract. +sidebar: + order: 8 +--- + +import OmnigraphAPIExample from "@components/organisms/OmnigraphAPIExample.astro"; + + + diff --git a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/schema-reference.mdx b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/schema-reference.mdx index 71cf866520..c41c0a5b3b 100644 --- a/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/schema-reference.mdx +++ b/docs/ensnode.io/src/content/docs/docs/integrate/omnigraph/schema-reference.mdx @@ -1,10 +1,16 @@ --- title: Schema Reference description: Full reference of the ENS Omnigraph GraphQL schema. +tableOfContents: false --- -:::caution[Coming Soon] -We're actively working on this page right now. Check back by May 18th for full content! -::: +import OmnigraphSchemaDocExplorer from "@components/organisms/OmnigraphSchemaDocExplorer.tsx"; -This page will provide a full reference of the ENS Omnigraph GraphQL schema — types, queries, fields, and arguments. Generated from the ENSNode introspection output. +Browse the ENS Omnigraph GraphQL schema — types, fields, and arguments. Sourced from the same SDL as [`enssdk/omnigraph/schema.graphql`](https://github.com/namehash/ensnode/blob/main/packages/enssdk/src/omnigraph/generated/schema.graphql) + +* For example queries, see [ENS Omnigraph examples](/docs/integrate/omnigraph/examples) + +* For runnable queries in web playground, see [ENSAdmin playground](https://admin.ensnode.io/api/omnigraph). + + + diff --git a/docs/ensnode.io/src/content/docs/docs/services/ensadmin/index.mdx b/docs/ensnode.io/src/content/docs/docs/services/ensadmin/index.mdx index 803e6762fe..af8a8b0c03 100644 --- a/docs/ensnode.io/src/content/docs/docs/services/ensadmin/index.mdx +++ b/docs/ensnode.io/src/content/docs/docs/services/ensadmin/index.mdx @@ -14,22 +14,22 @@ ENSAdmin is a user interface designed to help you monitor and manage your ENSNod A powerful GraphQL interface for querying the new Omnigraph API of your ENSNode. -![ENSAdmin: Omnigraph API Playground](../../../../../assets/ensadmin-omnigraph.png) +![ENSAdmin: Omnigraph API Playground](@assets/ensadmin-omnigraph.png) ### ENS Protocol Inspector Inspect and analyze ENSNode data in detail. For example, use the ENS Protocol Inspector for the Primary Name Resolution. -![ENSAdmin: ENS Protocol Inspector](../../../../../assets/ensadmin-primary-name-inspector.png) +![ENSAdmin: ENS Protocol Inspector](@assets/ensadmin-primary-name-inspector.png) ### ENSNode Stack Info Get real-time insights into the config of all services running on your ENSNode. -![ENSAdmin: ENSNode Stack Info](../../../../../assets/ensadmin-connection-view.png) +![ENSAdmin: ENSNode Stack Info](@assets/ensadmin-connection-view.png) ### Indexing Status Track the indexing status of your ENSNode to understand its current performance. -![ENSAdmin: Indexing Status](../../../../../assets/ensadmin-status-view.png) +![ENSAdmin: Indexing Status](@assets/ensadmin-status-view.png) diff --git a/docs/ensnode.io/src/content/docs/docs/services/ensdb/index.mdx b/docs/ensnode.io/src/content/docs/docs/services/ensdb/index.mdx index 91f5b00332..6f07351470 100644 --- a/docs/ensnode.io/src/content/docs/docs/services/ensdb/index.mdx +++ b/docs/ensnode.io/src/content/docs/docs/services/ensdb/index.mdx @@ -7,7 +7,7 @@ sidebar: --- import { LinkCard, Aside } from "@astrojs/starlight/components"; -import EnsDbUseCases from "../../../../../components/molecules/EnsDbUseCases.astro"; +import EnsDbUseCases from "@components/molecules/EnsDbUseCases.astro"; ## Vision diff --git a/docs/ensnode.io/src/data/savedQueries.ts b/docs/ensnode.io/src/data/ens-v1-examples-queries.ts similarity index 100% rename from docs/ensnode.io/src/data/savedQueries.ts rename to docs/ensnode.io/src/data/ens-v1-examples-queries.ts diff --git a/docs/ensnode.io/src/data/omnigraph-examples/examples.ts b/docs/ensnode.io/src/data/omnigraph-examples/examples.ts new file mode 100644 index 0000000000..e101281eff --- /dev/null +++ b/docs/ensnode.io/src/data/omnigraph-examples/examples.ts @@ -0,0 +1,37 @@ +import { getNamespaceSpecificValue } from "@ensnode/ensnode-sdk"; +import { getGraphqlApiExampleQueryById } from "@ensnode/ensnode-sdk/internal"; + +import { DOCS_OMNIGRAPH_NAMESPACE, ENSNODE_URL } from "src/lib/playground/constants"; +import { OmnigraphExampleQuerySchema, type OmnigraphExampleQuery } from "src/lib/playground/types"; + +import { OMNIGRAPH_EXAMPLES_META } from "./meta"; +import omnigraphExampleResponses from "./responses.json"; + +const responsesById = omnigraphExampleResponses as Record>; + +export const graphqlApiOmnigraphExamples: OmnigraphExampleQuery[] = Object.entries( + OMNIGRAPH_EXAMPLES_META, +).map(([id, meta]) => { + const example = getGraphqlApiExampleQueryById(id); + const response = responsesById[id]; + return OmnigraphExampleQuerySchema.parse({ + id: example.id, + name: meta.name, + description: meta.description, + category: meta.category, + query: example.query.trim(), + variables: getNamespaceSpecificValue(DOCS_OMNIGRAPH_NAMESPACE, example.variables), + ...(response ? { response } : {}), + connection: ENSNODE_URL, + }); +}); + +const byId = new Map(graphqlApiOmnigraphExamples.map((e) => [e.id, e])); + +export function getOmnigraphExampleById(id: string): OmnigraphExampleQuery { + const found = byId.get(id); + if (!found) { + throw new Error(`Unknown Omnigraph example id: ${id}`); + } + return found; +} diff --git a/docs/ensnode.io/src/data/omnigraph-examples/meta.ts b/docs/ensnode.io/src/data/omnigraph-examples/meta.ts new file mode 100644 index 0000000000..bb580e723a --- /dev/null +++ b/docs/ensnode.io/src/data/omnigraph-examples/meta.ts @@ -0,0 +1,71 @@ +/** Human-authored example copy. Example responses live in `responses.json` (refresh with `pnpm omnigraph-examples:refresh-responses`). */ +export const OMNIGRAPH_EXAMPLES_META: Record< + string, + { + name: string; + description: string; + category: string; + } +> = { + "find-domains": { + name: "Find Domains", + description: "List domains matching a name prefix with ordering and registration metadata.", + category: "Search", + }, + "domain-by-name": { + name: "Domain By Name", + description: + "Load a domain by interpreted name, including v1/v2 discriminated fields and subregistry on ENSv2.", + category: "Resolution", + }, + "domain-subdomains": { + name: "Domain Subdomains", + description: "Paginate direct child names under a parent domain.", + category: "Resolution", + }, + "domain-events": { + name: "Domain Events", + description: "Raw contract events associated with a domain’s registry records.", + category: "History", + }, + "domains-by-address": { + name: "Account Domains", + description: "Load domains owned by an address via the Omnigraph `account` root field.", + category: "Accounts", + }, + "account-events": { + name: "Account Events", + description: "Events touching an account across indexed ENS contracts.", + category: "History", + }, + "registry-domains": { + name: "Registry Domains", + description: "Enumerate domains under a specific v2 ETH registry contract.", + category: "Registry", + }, + "permissions-by-contract": { + name: "Permissions By Contract", + description: "Roles and users granted on resources for a registrar or registry contract.", + category: "Permissions", + }, + "permissions-by-user": { + name: "Permissions By User", + description: "Resources and roles for an address in the permissions graph.", + category: "Permissions", + }, + "account-resolver-permissions": { + name: "Account Resolver Permissions", + description: "Resolver contracts where an account has been granted resolver ACLs.", + category: "Permissions", + }, + "domain-resolver": { + name: "Domain Resolver", + description: "Assigned resolver, stored records, resolver permissions, and events.", + category: "Resolution", + }, + namegraph: { + name: "Namegraph", + description: "Walk the root tree: root → domains → nested subdomains (depth-limited).", + category: "Exploration", + }, +}; diff --git a/docs/ensnode.io/src/data/omnigraph-examples/responses.json b/docs/ensnode.io/src/data/omnigraph-examples/responses.json new file mode 100644 index 0000000000..4414604fa1 --- /dev/null +++ b/docs/ensnode.io/src/data/omnigraph-examples/responses.json @@ -0,0 +1,2479 @@ +{ + "account-events": { + "data": { + "account": { + "events": { + "totalCount": 34, + "edges": [ + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777903151" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777903159" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x6e0a14a9e926ffb9b4329a70e2d7a20ba06ed73c076dfef4e3b5a53600000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001110000000000000000000000000000001100000", + "timestamp": "1777903241" + } + }, + { + "node": { + "topics": [ + "0xebd3982eafd13b820e3edb2a4abd57a82ce3b8802e0cd45637a5de51383f9fac", + "0x6e0a14a9e926ffb9b4329a70e2d7a20ba06ed73c076dfef4e3b5a53600000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eae9395e07389740e192e1ca1ab2b40d78062c0000000000000000000000000000000000000000000000000000000001e13380000000000000000000000000f2942507cb33422a800ff9aa4cb05522a5e1d9e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c3df4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066f6c646e65770000000000000000000000000000000000000000000000000000", + "timestamp": "1777903241" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777903289" + } + }, + { + "node": { + "topics": [ + "0x448bc014f1536726cf8d54ff3d6481ed3cbc683c2591ca204274009afa09b1a1", + "0x5a5ad90209af8252cd3de958dcd48b83a841a2bf08082158d3293cadc2eb25d3", + "0x8ff70d326c02df6f81873c6010ab75efb2fd146f5e8e39686459cb387c2a6df5" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004777772770000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", + "timestamp": "1777903405" + } + }, + { + "node": { + "topics": [ + "0x448bc014f1536726cf8d54ff3d6481ed3cbc683c2591ca204274009afa09b1a1", + "0x5a5ad90209af8252cd3de958dcd48b83a841a2bf08082158d3293cadc2eb25d3", + "0x2361458367e696363fbcc70777d07ebbd2394e89fd0adcaf147faccd1d294d60" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000046e616d650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", + "timestamp": "1777903785" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777904282" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777904315" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777904506" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777904529" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x4ff0d1ec44e7361ac103d6278fc8874ce5805ad09171a2e40bd7aa7600000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001110000000000000000000000000000001100000", + "timestamp": "1777904615" + } + }, + { + "node": { + "topics": [ + "0xebd3982eafd13b820e3edb2a4abd57a82ce3b8802e0cd45637a5de51383f9fac", + "0x4ff0d1ec44e7361ac103d6278fc8874ce5805ad09171a2e40bd7aa7600000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a09d0166f8d5a18a015f2641d9978d83180b3f60000000000000000000000000000000000000000000000000000000001e187e0000000000000000000000000f2942507cb33422a800ff9aa4cb05522a5e1d9e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4b52000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053574657374000000000000000000000000000000000000000000000000000000", + "timestamp": "1777904615" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777904781" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x7dc965611c3e6e4e3eb83ae75164dbe53f2d971316a7452bbd40853700000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001110000000000000000000000000000001100000", + "timestamp": "1777904853" + } + }, + { + "node": { + "topics": [ + "0xebd3982eafd13b820e3edb2a4abd57a82ce3b8802e0cd45637a5de51383f9fac", + "0x7dc965611c3e6e4e3eb83ae75164dbe53f2d971316a7452bbd40853700000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000891475534fb2833865975a8187589347cfb8b7290000000000000000000000000000000000000000000000000000000001e187e0000000000000000000000000f2942507cb33422a800ff9aa4cb05522a5e1d9e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4b52000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073636367465737400000000000000000000000000000000000000000000000000", + "timestamp": "1777904853" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777904985" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x5f9c7bd9c6b0a29bf7d62ba5b3ba8ef0b4db6bc47cb1f50b69092fee00000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001110000000000000000000000000000001100000", + "timestamp": "1777905087" + } + }, + { + "node": { + "topics": [ + "0xebd3982eafd13b820e3edb2a4abd57a82ce3b8802e0cd45637a5de51383f9fac", + "0x5f9c7bd9c6b0a29bf7d62ba5b3ba8ef0b4db6bc47cb1f50b69092fee00000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b8d53072bbc78500b71c5b99c600ccdc6a3494bf0000000000000000000000000000000000000000000000000000000001e187e0000000000000000000000000f2942507cb33422a800ff9aa4cb05522a5e1d9e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c4b52000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073939397465737400000000000000000000000000000000000000000000000000", + "timestamp": "1777905087" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777905602" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777909592" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777909603" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0xc4ea3bee0eacbf41c56543e5da7c2639572a9634ca708145798da09300000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001110000000000000000000000000000001100000", + "timestamp": "1777909678" + } + }, + { + "node": { + "topics": [ + "0xebd3982eafd13b820e3edb2a4abd57a82ce3b8802e0cd45637a5de51383f9fac", + "0xc4ea3bee0eacbf41c56543e5da7c2639572a9634ca708145798da09300000000" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ea85c0ce9c7cfec7632a5ab892e74ef6b2ee3300000000000000000000000000000000000000000000000000000000009675300000000000000000000000000f2942507cb33422a800ff9aa4cb05522a5e1d9e6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017d6b3b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013696e64657865726973666b6e776f726b696e6700000000000000000000000000", + "timestamp": "1777909678" + } + }, + { + "node": { + "topics": [ + "0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82", + "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae", + "0x8cfae7abf74b0a8e28a63f0089b1a68133c4f412e05d81726b163695c6fc3580" + ], + "data": "0x0000000000000000000000007255589860cba4e9ef7299865d070fa8dbfd9c93", + "timestamp": "1778528141" + } + }, + { + "node": { + "topics": [ + "0xb3d987963d01b2f68493b4bdb130988f157ea43070d4ad840fee0466ed9370d9", + "0x8cfae7abf74b0a8e28a63f0089b1a68133c4f412e05d81726b163695c6fc3580", + "0x0000000000000000000000007255589860cba4e9ef7299865d070fa8dbfd9c93" + ], + "data": "0x000000000000000000000000000000000000000000000000000000006a27198d", + "timestamp": "1778528141" + } + }, + { + "node": { + "topics": [ + "0xd4735d920b0f87494915f556dd9b54c8f309026070caea5c737245152564d266", + "0xc5be7b748a54c9267c0c776d6f30b6c4b67cd158f283f1723e8a182c089b6af8" + ], + "data": "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf", + "timestamp": "1778528141" + } + }, + { + "node": { + "topics": [ + "0x335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a0", + "0xc5be7b748a54c9267c0c776d6f30b6c4b67cd158f283f1723e8a182c089b6af8" + ], + "data": "0x000000000000000000000000e99638b40e4fff0129d56f03b55b6bbc4bbe49b5", + "timestamp": "1778528141" + } + }, + { + "node": { + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000007255589860cba4e9ef7299865d070fa8dbfd9c93", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf", + "0x8cfae7abf74b0a8e28a63f0089b1a68133c4f412e05d81726b163695c6fc3580" + ], + "data": "0x", + "timestamp": "1778528141" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1778528303" + } + }, + { + "node": { + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf", + "0x0000000000000000000000005587003f8eeee1bc236d48ab39059cbfd99207d7", + "0x8cfae7abf74b0a8e28a63f0089b1a68133c4f412e05d81726b163695c6fc3580" + ], + "data": "0x", + "timestamp": "1778528309" + } + }, + { + "node": { + "topics": [ + "0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82", + "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae", + "0x8cfae7abf74b0a8e28a63f0089b1a68133c4f412e05d81726b163695c6fc3580" + ], + "data": "0x0000000000000000000000005587003f8eeee1bc236d48ab39059cbfd99207d7", + "timestamp": "1778528309" + } + }, + { + "node": { + "topics": [ + "0x335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a0", + "0xc5be7b748a54c9267c0c776d6f30b6c4b67cd158f283f1723e8a182c089b6af8" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "1778528309" + } + }, + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x8cfae7abf74b0a8e28a63f0089b1a68133c4f412e05d81726b16369500000000", + "0x000000000000000000000000205d2686da3bf33f64c17f21462c51b5ead462cf" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001110000000000000000000000000000001100000", + "timestamp": "1778528309" + } + } + ] + } + } + } + }, + "account-resolver-permissions": { + "data": { + "account": { + "resolverPermissions": { + "edges": [ + { + "node": { + "resolver": { + "contract": { + "address": "0x5f35454af804f1131d6c1261c55b77308be5a11c" + } + } + } + } + ] + } + } + } + }, + "domain-by-name": { + "data": { + "domain": { + "__typename": "ENSv2Domain", + "id": "99911155111-0x31a2bb5d933557cce1b3129993193896d074db92-18650549467948381174706470291653511222307197070371999253038345217664991887360", + "label": { + "interpreted": "test-name", + "hash": "0x293bd640008c5863fbe17a08ae5df5b2484357f5dc95e0fdd089f85e7edbfe5a" + }, + "owner": null, + "subregistry": null, + "name": "test-name.eth" + } + } + }, + "domain-events": { + "data": { + "domain": { + "events": { + "totalCount": 3, + "edges": [ + { + "node": { + "from": "0xffffffffff52d316b7bd028358089bc8066b8f80", + "to": "0x63736415c705949705ce65ae24db033eaf76c4dc", + "topics": [ + "0x734822851860327a80c624af1471efac6bb0ac641852fc6c7bfeeee3202ae6a8", + "0x7d329898b2e1764e620391e6ee7a37fa65015d506502d80d8a6faaa600000000", + "0x7d329898b2e1764e620391e6ee7a37fa65015d506502d80d8a6faaa6014e8749", + "0x00000000000000000000000063736415c705949705ce65ae24db033eaf76c4dc" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000006bd63f83000000000000000000000000000000000000000000000000000000000000000d73666d6f6e69636465626d696700000000000000000000000000000000000000", + "timestamp": "1777667143", + "transactionHash": "0xea711d3e06bab0782613d1354a68b7a6005279a66d239f2fc273f36ab68e74ae" + } + }, + { + "node": { + "from": "0x2f8e8b1126e75fde0b7f731e7cb5847eba2d2574", + "to": "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85", + "topics": [ + "0x2fe093918572373e9f1f0368f414dffd0043a74ae8c9fd7b0e390b26a0d20b6e", + "0x7d329898b2e1764e620391e6ee7a37fa65015d506502d80d8a6faaa600000000", + "0x7d329898b2e1764e620391e6ee7a37fa65015d506502d80d8a6faaa6014e8749", + "0x0000000000000000000000005587003f8eeee1bc236d48ab39059cbfd99207d7" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002f8e8b1126e75fde0b7f731e7cb5847eba2d2574000000000000000000000000000000000000000000000000000000006bd63f83000000000000000000000000000000000000000000000000000000000000000d73666d6f6e69636465626d696700000000000000000000000000000000000000", + "timestamp": "1777667978", + "transactionHash": "0x2e1e3b9dbd5c0c354894ccc7aa00107b2fa504fa3fda44b7a36c71098ca897f6" + } + }, + { + "node": { + "from": "0x2f8e8b1126e75fde0b7f731e7cb5847eba2d2574", + "to": "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85", + "topics": [ + "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62", + "0x0000000000000000000000005587003f8eeee1bc236d48ab39059cbfd99207d7", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000002f8e8b1126e75fde0b7f731e7cb5847eba2d2574" + ], + "data": "0x7d329898b2e1764e620391e6ee7a37fa65015d506502d80d8a6faaa6000000000000000000000000000000000000000000000000000000000000000000000001", + "timestamp": "1777667978", + "transactionHash": "0x2e1e3b9dbd5c0c354894ccc7aa00107b2fa504fa3fda44b7a36c71098ca897f6" + } + } + ] + } + } + } + }, + "domain-resolver": { + "data": { + "domain": { + "resolver": null + } + } + }, + "domain-subdomains": { + "data": { + "domain": { + "name": "eth", + "subdomains": { + "edges": [ + { + "node": { + "name": "⌐◨-◨.eth" + } + }, + { + "node": { + "name": "♂♂♂♂.eth" + } + }, + { + "node": { + "name": "♾♾♾♾.eth" + } + }, + { + "node": { + "name": "⚱⚱⚱⚱.eth" + } + }, + { + "node": { + "name": "🏴‍☠.eth" + } + }, + { + "node": { + "name": "🐮💻🐛.eth" + } + }, + { + "node": { + "name": "👑👑👑👑👑👑.eth" + } + }, + { + "node": { + "name": "👨🏼‍💻.eth" + } + }, + { + "node": { + "name": "👱‍♀👱‍♀.eth" + } + }, + { + "node": { + "name": "🔞🔞🔞🔞🔞.eth" + } + } + ] + } + } + } + }, + "domains-by-address": { + "data": { + "account": { + "domains": { + "edges": [ + { + "node": { + "label": { + "interpreted": "5test" + }, + "name": "5test.eth" + } + }, + { + "node": { + "label": { + "interpreted": "666test" + }, + "name": "666test.eth" + } + }, + { + "node": { + "label": { + "interpreted": "999test" + }, + "name": "999test.eth" + } + }, + { + "node": { + "label": { + "interpreted": "indexerisfknworking" + }, + "name": "indexerisfknworking.eth" + } + }, + { + "node": { + "label": { + "interpreted": "oldnew" + }, + "name": "oldnew.eth" + } + }, + { + "node": { + "label": { + "interpreted": "test3wallet" + }, + "name": "test3wallet.eth" + } + } + ] + } + } + } + }, + "find-domains": { + "data": { + "domains": { + "edges": [ + { + "node": { + "__typename": "ENSv1Domain", + "id": "99911155111-0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e-0xfef70852da2a0a32225b513391c3dcf0f3b5cccf343bde23fa1f8464cbe5a2c2", + "label": { + "interpreted": "test-names", + "hash": "0xf8899b8332fc898c86bfe588219b29362bb1335ce9738d7a34508d817ab31133" + }, + "name": "test-names.eth", + "registration": { + "expiry": "1751190612", + "event": { + "timestamp": "1747216212" + } + } + } + }, + { + "node": { + "__typename": "ENSv2Domain", + "id": "99911155111-0x31a2bb5d933557cce1b3129993193896d074db92-14580361689616036777059995592070852680799975896349261373821065539059361775616", + "label": { + "interpreted": "test-namers", + "hash": "0x203c3138956e0cab1c57684ba6b9cf550db4e624055a6b0e30caee16609eac9d" + }, + "name": "test-namers.eth", + "registration": { + "expiry": "1793279532", + "event": { + "timestamp": "1777667178" + } + } + } + }, + { + "node": { + "__typename": "ENSv1Domain", + "id": "99911155111-0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e-0x598e24a25300d326443e87af828c71764964f2e318850c2a517e9dc0115b43c9", + "label": { + "interpreted": "test-namers", + "hash": "0x203c3138956e0cab1c57684ba6b9cf550db4e624055a6b0e30caee16609eac9d" + }, + "name": "test-namers.eth", + "registration": { + "expiry": "1793279532", + "event": { + "timestamp": "1761743532" + } + } + } + }, + { + "node": { + "__typename": "ENSv2Domain", + "id": "99911155111-0x31a2bb5d933557cce1b3129993193896d074db92-18650549467948381174706470291653511222307197070371999253038345217664991887360", + "label": { + "interpreted": "test-name", + "hash": "0x293bd640008c5863fbe17a08ae5df5b2484357f5dc95e0fdd089f85e7edbfe5a" + }, + "name": "test-name.eth", + "registration": { + "expiry": "1785702972", + "event": { + "timestamp": "1777667203" + } + } + } + }, + { + "node": { + "__typename": "ENSv1Domain", + "id": "99911155111-0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e-0x527f8d39fa87ac23b6431913e880aa29425dc0450651a22d9a3b881a29661a0b", + "label": { + "interpreted": "test-name", + "hash": "0x293bd640008c5863fbe17a08ae5df5b2484357f5dc95e0fdd089f85e7edbfe5a" + }, + "name": "test-name.eth", + "registration": { + "expiry": "1785702972", + "event": { + "timestamp": "1754166972" + } + } + } + } + ] + } + } + }, + "namegraph": { + "data": { + "root": { + "id": "99911155111-0xfd43dc00ab0d0e247a2827d15dddfc5bd9646a29", + "domains": { + "edges": [ + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aaa" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aarp" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "abb" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "abbott" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "abbvie" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "abc" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "able" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "abogado" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "abudhabi" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ac" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "academy" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "accenture" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "accountant" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "accountants" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aco" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "actor" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ad" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ads" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "adult" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ae" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aeg" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aero" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aetna" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "af" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "afl" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "africa" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ag" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "agakhan" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "agency" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ai" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aig" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "airbus" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "airforce" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "airtel" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "akdn" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "al" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "alibaba" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "alipay" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "allfinanz" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "allstate" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ally" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "alsace" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "alstom" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "am" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "amazon" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "americanexpress" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "americanfamily" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "amex" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "amfam" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "amica" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "amsterdam" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "analytics" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "android" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "anquan" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "anz" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ao" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aol" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "apartments" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "app" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "apple" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aq" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aquarelle" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ar" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "arab" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aramco" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "archi" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "army" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "arpa" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "art" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "arte" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "as" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "asda" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "asia" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "associates" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "at" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "athleta" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "attorney" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "au" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "auction" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "audi" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "audible" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "audio" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "auspost" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "author" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "auto" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "autos" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aw" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "aws" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ax" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "axa" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "az" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "azure" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "ba" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "baby" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "baidu" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "banamex" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "band" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "bank" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "bar" + } + }, + { + "node": { + "subdomains": { + "edges": [] + }, + "name": "barcelona" + } + } + ] + } + } + } + }, + "permissions-by-contract": { + "data": { + "permissions": { + "events": { + "totalCount": 1, + "edges": [ + { + "node": { + "topics": [ + "0x0d35bf721a39b614de00ca5038e1deb0cb0c69a278645e83405a7226cf80ba3c", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000ffffffffff52d316b7bd028358089bc8066b8f80" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111", + "timestamp": "1777666931" + } + } + ] + }, + "resources": { + "edges": [ + { + "node": { + "resource": "0", + "users": { + "edges": [ + { + "node": { + "id": "99911155111-0x26e5e80e8f36607ef401443fb34eea363c86e8f7-0-0xffffffffff52d316b7bd028358089bc8066b8f80", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329", + "user": { + "address": "0xffffffffff52d316b7bd028358089bc8066b8f80" + } + } + } + ] + } + } + } + ] + } + } + } + }, + "permissions-by-user": { + "data": { + "account": { + "permissions": { + "edges": [ + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "36158207168415100260214102608348213577766657549044985901579930652532998668288", + "roles": "97409655027181761882228017414928043058140282880" + } + }, + { + "node": { + "resource": "43246203543549314115802810355044689536917792524799488001708226217367113302016", + "roles": "97409655027181761882228017414928043058140282880" + } + }, + { + "node": { + "resource": "49772224430518816311047281626325127025073684260557621488740515161950090428416", + "roles": "97409655027181761882228017414928043058140282880" + } + }, + { + "node": { + "resource": "56894942027399033099161118898458574805733373504465726899553475007361084030976", + "roles": "97409655027181761882228017414928043058140282880" + } + }, + { + "node": { + "resource": "63767109507451885371964516220238430314957607793518180461160132644539853701120", + "roles": "97409655027181761882228017414928043058140282880" + } + }, + { + "node": { + "resource": "89067174156525557596752560761174666328155117264317152284715254762292895547392", + "roles": "97409655027181761882228017414928043058140282880" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + }, + { + "node": { + "resource": "0", + "roles": "7719472615821079694904732333912527190217998977709370935963838933860875309329" + } + } + ] + } + } + } + }, + "registry-domains": { + "data": { + "registry": { + "domains": { + "edges": [ + { + "node": { + "label": { + "interpreted": "⌐◨-◨" + }, + "name": "⌐◨-◨.eth" + } + }, + { + "node": { + "label": { + "interpreted": "♂♂♂♂" + }, + "name": "♂♂♂♂.eth" + } + }, + { + "node": { + "label": { + "interpreted": "♾♾♾♾" + }, + "name": "♾♾♾♾.eth" + } + }, + { + "node": { + "label": { + "interpreted": "⚱⚱⚱⚱" + }, + "name": "⚱⚱⚱⚱.eth" + } + }, + { + "node": { + "label": { + "interpreted": "🏴‍☠" + }, + "name": "🏴‍☠.eth" + } + }, + { + "node": { + "label": { + "interpreted": "🐮💻🐛" + }, + "name": "🐮💻🐛.eth" + } + }, + { + "node": { + "label": { + "interpreted": "👑👑👑👑👑👑" + }, + "name": "👑👑👑👑👑👑.eth" + } + }, + { + "node": { + "label": { + "interpreted": "👨🏼‍💻" + }, + "name": "👨🏼‍💻.eth" + } + }, + { + "node": { + "label": { + "interpreted": "👱‍♀👱‍♀" + }, + "name": "👱‍♀👱‍♀.eth" + } + }, + { + "node": { + "label": { + "interpreted": "🔞🔞🔞🔞🔞" + }, + "name": "🔞🔞🔞🔞🔞.eth" + } + }, + { + "node": { + "label": { + "interpreted": "🔥🔥🔥💤💤💤" + }, + "name": "🔥🔥🔥💤💤💤.eth" + } + }, + { + "node": { + "label": { + "interpreted": "🔫🔫🔫🔫🔫" + }, + "name": "🔫🔫🔫🔫🔫.eth" + } + }, + { + "node": { + "label": { + "interpreted": "😀😀😀😀😀😀" + }, + "name": "😀😀😀😀😀😀.eth" + } + }, + { + "node": { + "label": { + "interpreted": "😠😠😠😠😠😠" + }, + "name": "😠😠😠😠😠😠.eth" + } + }, + { + "node": { + "label": { + "interpreted": "🚀🚀🚀🚀🚀🚀" + }, + "name": "🚀🚀🚀🚀🚀🚀.eth" + } + }, + { + "node": { + "label": { + "interpreted": "$2442" + }, + "name": "$2442.eth" + } + }, + { + "node": { + "label": { + "interpreted": "$bless" + }, + "name": "$bless.eth" + } + }, + { + "node": { + "label": { + "interpreted": "$degenhobo" + }, + "name": "$degenhobo.eth" + } + }, + { + "node": { + "label": { + "interpreted": "$hila" + }, + "name": "$hila.eth" + } + }, + { + "node": { + "label": { + "interpreted": "$pauly" + }, + "name": "$pauly.eth" + } + }, + { + "node": { + "label": { + "interpreted": "$phunks" + }, + "name": "$phunks.eth" + } + }, + { + "node": { + "label": { + "interpreted": "$vince" + }, + "name": "$vince.eth" + } + }, + { + "node": { + "label": { + "interpreted": "000" + }, + "name": "000.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0000" + }, + "name": "0000.eth" + } + }, + { + "node": { + "label": { + "interpreted": "000000" + }, + "name": "000000.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0000000" + }, + "name": "0000000.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00000000" + }, + "name": "00000000.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00000000000" + }, + "name": "00000000000.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0000000001" + }, + "name": "0000000001.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00000002" + }, + "name": "00000002.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00000008" + }, + "name": "00000008.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00001" + }, + "name": "00001.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00002" + }, + "name": "00002.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00003" + }, + "name": "00003.eth" + } + }, + { + "node": { + "label": { + "interpreted": "00004" + }, + "name": "00004.eth" + } + }, + { + "node": { + "label": { + "interpreted": "-0003" + }, + "name": "-0003.eth" + } + }, + { + "node": { + "label": { + "interpreted": "-0004" + }, + "name": "-0004.eth" + } + }, + { + "node": { + "label": { + "interpreted": "-0008" + }, + "name": "-0008.eth" + } + }, + { + "node": { + "label": { + "interpreted": "001" + }, + "name": "001.eth" + } + }, + { + "node": { + "label": { + "interpreted": "-0034" + }, + "name": "-0034.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0-1-2-3" + }, + "name": "0-1-2-3.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0123456789" + }, + "name": "0123456789.eth" + } + }, + { + "node": { + "label": { + "interpreted": "01249" + }, + "name": "01249.eth" + } + }, + { + "node": { + "label": { + "interpreted": "01283018238012938123" + }, + "name": "01283018238012938123.eth" + } + }, + { + "node": { + "label": { + "interpreted": "012983102938012812" + }, + "name": "012983102938012812.eth" + } + }, + { + "node": { + "label": { + "interpreted": "01425" + }, + "name": "01425.eth" + } + }, + { + "node": { + "label": { + "interpreted": "01935" + }, + "name": "01935.eth" + } + }, + { + "node": { + "label": { + "interpreted": "01chayan" + }, + "name": "01chayan.eth" + } + }, + { + "node": { + "label": { + "interpreted": "02017" + }, + "name": "02017.eth" + } + }, + { + "node": { + "label": { + "interpreted": "02278" + }, + "name": "02278.eth" + } + }, + { + "node": { + "label": { + "interpreted": "02686" + }, + "name": "02686.eth" + } + }, + { + "node": { + "label": { + "interpreted": "02734" + }, + "name": "02734.eth" + } + }, + { + "node": { + "label": { + "interpreted": "02905" + }, + "name": "02905.eth" + } + }, + { + "node": { + "label": { + "interpreted": "03141" + }, + "name": "03141.eth" + } + }, + { + "node": { + "label": { + "interpreted": "03466" + }, + "name": "03466.eth" + } + }, + { + "node": { + "label": { + "interpreted": "04552" + }, + "name": "04552.eth" + } + }, + { + "node": { + "label": { + "interpreted": "04761" + }, + "name": "04761.eth" + } + }, + { + "node": { + "label": { + "interpreted": "04799" + }, + "name": "04799.eth" + } + }, + { + "node": { + "label": { + "interpreted": "05873" + }, + "name": "05873.eth" + } + }, + { + "node": { + "label": { + "interpreted": "06557" + }, + "name": "06557.eth" + } + }, + { + "node": { + "label": { + "interpreted": "07171" + }, + "name": "07171.eth" + } + }, + { + "node": { + "label": { + "interpreted": "07194" + }, + "name": "07194.eth" + } + }, + { + "node": { + "label": { + "interpreted": "07333" + }, + "name": "07333.eth" + } + }, + { + "node": { + "label": { + "interpreted": "08586" + }, + "name": "08586.eth" + } + }, + { + "node": { + "label": { + "interpreted": "09jul" + }, + "name": "09jul.eth" + } + }, + { + "node": { + "label": { + "interpreted": "[0ab910abde95e9e1b434c50f9a04428d68bc48b201e3f33906e087eff9c6d37c]" + }, + "name": "[0ab910abde95e9e1b434c50f9a04428d68bc48b201e3f33906e087eff9c6d37c].eth" + } + }, + { + "node": { + "label": { + "interpreted": "0cf5e" + }, + "name": "0cf5e.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0mcp" + }, + "name": "0mcp.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0rxafj" + }, + "name": "0rxafj.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0trust" + }, + "name": "0trust.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0vortex" + }, + "name": "0vortex.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x0" + }, + "name": "0x0.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x0002" + }, + "name": "0x0002.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x010y3" + }, + "name": "0x010y3.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x420" + }, + "name": "0x420.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x55559e7da7aec04b3156e16a60cf57a348843dfb" + }, + "name": "0x55559e7da7aec04b3156e16a60cf57a348843dfb.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x5dc5b884e1cf9e0e31e8f645ae98a18e8e22b18b" + }, + "name": "0x5dc5b884e1cf9e0e31e8f645ae98a18e8e22b18b.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x666" + }, + "name": "0x666.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x7c26" + }, + "name": "0x7c26.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0x8bit" + }, + "name": "0x8bit.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xaacaa" + }, + "name": "0xaacaa.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xaegis" + }, + "name": "0xaegis.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xakhil" + }, + "name": "0xakhil.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xalice" + }, + "name": "0xalice.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xarkaw" + }, + "name": "0xarkaw.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xasd" + }, + "name": "0xasd.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xbnb" + }, + "name": "0xbnb.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xbr1" + }, + "name": "0xbr1.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xbtc" + }, + "name": "0xbtc.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xbuns" + }, + "name": "0xbuns.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "name": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xc0d3rs" + }, + "name": "0xc0d3rs.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xcallme" + }, + "name": "0xcallme.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xcryptomarine" + }, + "name": "0xcryptomarine.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xdao" + }, + "name": "0xdao.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xden" + }, + "name": "0xden.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xestate" + }, + "name": "0xestate.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xfanatic" + }, + "name": "0xfanatic.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xfliz" + }, + "name": "0xfliz.eth" + } + }, + { + "node": { + "label": { + "interpreted": "0xfoundation" + }, + "name": "0xfoundation.eth" + } + } + ] + } + } + } + } +} diff --git a/docs/ensnode.io/src/lib/playground/constants.ts b/docs/ensnode.io/src/lib/playground/constants.ts new file mode 100644 index 0000000000..80dc322bcd --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/constants.ts @@ -0,0 +1,9 @@ +import { ENSNamespaceIds } from "@ensnode/ensnode-sdk"; + +/** TODO: Update all to the latest ENSNode URL */ +/** Sepolia v2 namespace — matches the public v2 Sepolia ENSNode URL in docs playgrounds. */ +export const DOCS_OMNIGRAPH_NAMESPACE = ENSNamespaceIds.SepoliaV2; +/** Heading anchor for the docs playground instance (`#### ENSNode 'v2 Sepolia'` on /docs/integrate/hosted-instances). */ +export const DOCS_HOSTED_INSTANCE_ANCHOR = "ensnode-v2-sepolia"; +/** Sepolia v2 ENSNode URL — matches the public v2 Sepolia ENSNode URL in docs playgrounds. */ +export const ENSNODE_URL = "https://api.v2-sepolia.ensnode.io"; diff --git a/docs/ensnode.io/src/lib/playground/example-project/assemblePlaygroundProject.ts b/docs/ensnode.io/src/lib/playground/example-project/assemblePlaygroundProject.ts new file mode 100644 index 0000000000..fdc82827ae --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/assemblePlaygroundProject.ts @@ -0,0 +1,27 @@ +import type { PlaygroundProject, TransformedExampleProject } from "./types"; + +export function assemblePlaygroundProject(params: { + title: string; + description?: string; + runtime: PlaygroundProject["runtime"]; + view?: PlaygroundProject["view"]; + entryFileName: string; + openFile?: string; + transformed: TransformedExampleProject; + dependencies: Record; + devDependencies: Record; + tsconfig: string; +}): PlaygroundProject { + const { transformed, tsconfig, dependencies, devDependencies, ...meta } = params; + + return { + ...meta, + files: { + ...transformed.files, + "tsconfig.json": tsconfig, + }, + dependencies, + devDependencies, + openFile: meta.openFile ?? meta.entryFileName, + }; +} diff --git a/docs/ensnode.io/src/lib/playground/example-project/buildPlaygroundTsconfig.ts b/docs/ensnode.io/src/lib/playground/example-project/buildPlaygroundTsconfig.ts new file mode 100644 index 0000000000..a181a15d03 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/buildPlaygroundTsconfig.ts @@ -0,0 +1,39 @@ +export function buildViteReactPlaygroundTsconfig(): string { + return JSON.stringify( + { + compilerOptions: { + target: "ES2022", + module: "ESNext", + moduleResolution: "bundler", + jsx: "react-jsx", + strict: true, + esModuleInterop: true, + skipLibCheck: true, + lib: ["ESNext", "DOM", "DOM.Iterable"], + }, + include: ["src"], + }, + null, + 2, + ); +} + +export function buildNodePlaygroundTsconfig(): string { + return JSON.stringify( + { + compilerOptions: { + target: "ES2022", + module: "ESNext", + moduleResolution: "bundler", + strict: true, + esModuleInterop: true, + skipLibCheck: true, + lib: ["ESNext", "DOM", "DOM.Iterable"], + types: ["node"], + }, + include: ["src"], + }, + null, + 2, + ); +} diff --git a/docs/ensnode.io/src/lib/playground/example-project/fetchRawExampleProject.ts b/docs/ensnode.io/src/lib/playground/example-project/fetchRawExampleProject.ts new file mode 100644 index 0000000000..de155c56c2 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/fetchRawExampleProject.ts @@ -0,0 +1,42 @@ +import type { RawExampleProject } from "./types"; + +/** + * Normalize Vite `import.meta.glob(..., { query: "?raw", eager: true })` keys to paths + * relative to the example root (e.g. `src/index.ts`). + */ +export function normalizeGlobModules( + modules: Record, + pathPrefix: string, +): Record { + const files: Record = {}; + + for (const [key, value] of Object.entries(modules)) { + const content = typeof value === "string" ? value : value.default; + const normalizedKey = key.replace(/\\/g, "/"); + const marker = `${pathPrefix}/`; + const index = normalizedKey.indexOf(marker); + const relativePath = + index >= 0 + ? normalizedKey.slice(index + marker.length) + : (normalizedKey.split("/").pop() ?? key); + + files[relativePath] = content; + } + + return files; +} + +export function fetchRawExampleProjectFromGlob( + modules: Record, + pathPrefix: string, +): RawExampleProject { + return { files: normalizeGlobModules(modules, pathPrefix) }; +} + +export function mergeRawExampleProjects(...projects: RawExampleProject[]): RawExampleProject { + const files: Record = {}; + for (const project of projects) { + Object.assign(files, project.files); + } + return { files }; +} diff --git a/docs/ensnode.io/src/lib/playground/example-project/loadExampleProject.ts b/docs/ensnode.io/src/lib/playground/example-project/loadExampleProject.ts new file mode 100644 index 0000000000..29150a83fa --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/loadExampleProject.ts @@ -0,0 +1,30 @@ +import { assemblePlaygroundProject } from "./assemblePlaygroundProject"; +import { buildNodePlaygroundTsconfig } from "./buildPlaygroundTsconfig"; +import { replaceEnvWithValues } from "./replaceEnvWithValues"; +import type { ExampleProjectConfig, PlaygroundProject } from "./types"; + +export function loadExampleProject(config: ExampleProjectConfig): PlaygroundProject { + const raw = config.fetchRaw(); + const transformed = replaceEnvWithValues(raw, config.envReplacements); + const { dependencies, devDependencies } = config.resolvePackageManifest(); + const tsconfig = config.buildTsconfig?.() ?? buildNodePlaygroundTsconfig(); + + const project = assemblePlaygroundProject({ + title: config.title, + description: config.description, + runtime: config.runtime, + view: config.view, + entryFileName: config.entryFileName, + openFile: config.openFile, + transformed, + dependencies, + devDependencies, + tsconfig, + }); + + if (config.extraFiles) { + project.files = { ...project.files, ...config.extraFiles }; + } + + return project; +} diff --git a/docs/ensnode.io/src/lib/playground/example-project/replaceEnvWithValues.test.ts b/docs/ensnode.io/src/lib/playground/example-project/replaceEnvWithValues.test.ts new file mode 100644 index 0000000000..b707607132 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/replaceEnvWithValues.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; + +import { replaceEnvWithValues } from "./replaceEnvWithValues"; + +const ENSNODE_URL_ASSIGNMENT = /const ENSNODE_URL = process\.env\.ENSNODE_URL!;/; + +describe("replaceEnvWithValues", () => { + it("replaces ENSNODE_URL env access with a literal", () => { + const source = `const ENSNODE_URL = process.env.ENSNODE_URL!;\nconst x = 1;`; + const result = replaceEnvWithValues({ files: { "src/index.ts": source } }, [ + { + pattern: ENSNODE_URL_ASSIGNMENT, + replacement: 'const ENSNODE_URL = "https://example.test";', + }, + ]); + + expect(result.files["src/index.ts"]).not.toContain("process.env.ENSNODE_URL"); + expect(result.files["src/index.ts"]).toContain('const ENSNODE_URL = "https://example.test";'); + expect(result.files["src/index.ts"]).toContain("const x = 1;"); + }); +}); diff --git a/docs/ensnode.io/src/lib/playground/example-project/replaceEnvWithValues.ts b/docs/ensnode.io/src/lib/playground/example-project/replaceEnvWithValues.ts new file mode 100644 index 0000000000..dac4ad9629 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/replaceEnvWithValues.ts @@ -0,0 +1,22 @@ +import type { EnvReplacement, RawExampleProject, TransformedExampleProject } from "./types"; + +export function replaceEnvWithValues( + project: RawExampleProject, + replacements: EnvReplacement[], +): TransformedExampleProject { + if (replacements.length === 0) { + return project; + } + + const files: Record = {}; + + for (const [path, content] of Object.entries(project.files)) { + let next = content; + for (const { pattern, replacement } of replacements) { + next = next.replace(pattern, replacement); + } + files[path] = next; + } + + return { files }; +} diff --git a/docs/ensnode.io/src/lib/playground/example-project/resolveMonorepoSpecifier.test.ts b/docs/ensnode.io/src/lib/playground/example-project/resolveMonorepoSpecifier.test.ts new file mode 100644 index 0000000000..0980d39f08 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/resolveMonorepoSpecifier.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; + +import { parsePnpmCatalog, resolveMonorepoSpecifier } from "./resolveMonorepoSpecifier"; + +describe("parsePnpmCatalog", () => { + it("throws when workspace yaml has no catalog section", () => { + expect(() => parsePnpmCatalog("packages:\n - apps/*\n")).toThrow( + "Failed to parse pnpm catalog from pnpm-workspace.yaml", + ); + }); +}); + +describe("resolveMonorepoSpecifier", () => { + it("resolves catalog: specifiers from pnpm-workspace.yaml", () => { + expect(resolveMonorepoSpecifier("@types/node", "catalog:")).toBe("24.10.9"); + expect(resolveMonorepoSpecifier("typescript", "catalog:")).toBe("^5.7.3"); + }); + + it("passes through npm semver ranges", () => { + expect(resolveMonorepoSpecifier("gql.tada", "^1.8.10")).toBe("^1.8.10"); + expect(resolveMonorepoSpecifier("tsx", "^4.7.1")).toBe("^4.7.1"); + }); + + it("resolves enssdk workspace:* to the published package version", () => { + expect(resolveMonorepoSpecifier("enssdk", "workspace:*")).toMatch(/^\d+\.\d+\.\d+/); + }); + + it("resolves enskit workspace:* to the published package version", () => { + expect(resolveMonorepoSpecifier("enskit", "workspace:*")).toMatch(/^\d+\.\d+\.\d+/); + }); +}); diff --git a/docs/ensnode.io/src/lib/playground/example-project/resolveMonorepoSpecifier.ts b/docs/ensnode.io/src/lib/playground/example-project/resolveMonorepoSpecifier.ts new file mode 100644 index 0000000000..512a057c27 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/resolveMonorepoSpecifier.ts @@ -0,0 +1,72 @@ +import enskitPackageJson from "@workspace/packages/enskit/package.json"; +import enssdkPackageJson from "@workspace/packages/enssdk/package.json"; +import pnpmWorkspaceYaml from "@workspace/pnpm-workspace.yaml?raw"; +import { parse } from "yaml"; + +const pnpmCatalog = parsePnpmCatalog(pnpmWorkspaceYaml); + +const workspacePackageVersions: Record = { + enssdk: enssdkPackageJson.version, + enskit: enskitPackageJson.version, +}; + +/** Resolve pnpm `catalog:` / `workspace:` specifiers to strings npm can install in StackBlitz. */ +export function resolveMonorepoSpecifier(packageName: string, specifier: string): string { + if (specifier === "catalog:" || specifier.startsWith("catalog:")) { + const version = pnpmCatalog[packageName]; + if (!version) { + throw new Error(`No pnpm catalog entry for "${packageName}" (specifier: ${specifier})`); + } + return version; + } + + if (specifier.startsWith("workspace:")) { + const version = workspacePackageVersions[packageName]; + if (!version) { + throw new Error(`Unsupported workspace dependency "${packageName}" in playground manifest`); + } + return version; + } + + return specifier; +} + +export function parsePnpmCatalog(source: string): Record { + const doc = parse(source) as { catalog?: Record }; + const catalog = doc.catalog ?? {}; + + if (Object.keys(catalog).length === 0) { + throw new Error("Failed to parse pnpm catalog from pnpm-workspace.yaml"); + } + + return catalog; +} + +type PackageJsonWithPeers = { + peerDependencies?: Record; + devDependencies?: Record; +}; + +/** Satisfy a peer dependency using package devDependency pins, then the pnpm catalog. */ +export function resolvePeerSpecifier( + packageName: string, + peerSpecifier: string, + packages: PackageJsonWithPeers[], +): string { + for (const pkg of packages) { + const pinnedInDev = pkg.devDependencies?.[packageName]; + if ( + pinnedInDev && + !pinnedInDev.startsWith("catalog:") && + !pinnedInDev.startsWith("workspace:") + ) { + return pinnedInDev; + } + } + + if (packageName in pnpmCatalog) { + return pnpmCatalog[packageName]; + } + + return resolveMonorepoSpecifier(packageName, peerSpecifier); +} diff --git a/docs/ensnode.io/src/lib/playground/example-project/resolvePinnedDependencies.ts b/docs/ensnode.io/src/lib/playground/example-project/resolvePinnedDependencies.ts new file mode 100644 index 0000000000..d4475fb61a --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/resolvePinnedDependencies.ts @@ -0,0 +1,58 @@ +import enskitExamplePackageJson from "@workspace/examples/enskit-react-example/package.json"; +import enssdkExamplePackageJson from "@workspace/examples/enssdk-example/package.json"; +import enskitPackageJson from "@workspace/packages/enskit/package.json"; +import enssdkPackageJson from "@workspace/packages/enssdk/package.json"; + +import { resolveMonorepoSpecifier, resolvePeerSpecifier } from "./resolveMonorepoSpecifier"; +import type { PlaygroundPackageManifest } from "./types"; + +function resolveDependencyBlock(block: Record): Record { + const resolved: Record = {}; + for (const [name, specifier] of Object.entries(block)) { + resolved[name] = resolveMonorepoSpecifier(name, specifier); + } + return resolved; +} + +function addPeerDependencies( + manifest: PlaygroundPackageManifest, + packages: Array<{ peerDependencies: Record }>, +): void { + for (const pkg of packages) { + for (const [name, specifier] of Object.entries(pkg.peerDependencies)) { + if (name in manifest.dependencies || name in manifest.devDependencies) { + continue; + } + manifest.devDependencies[name] = resolvePeerSpecifier(name, specifier, [ + enssdkPackageJson, + enskitPackageJson, + ]); + } + } +} + +/** + * StackBlitz manifest aligned with `examples/enssdk-example/package.json`. + */ +export function resolveEnssdkExamplePackageManifest(): PlaygroundPackageManifest { + const dependencies = resolveDependencyBlock(enssdkExamplePackageJson.dependencies); + const devDependencies = resolveDependencyBlock(enssdkExamplePackageJson.devDependencies); + + const manifest: PlaygroundPackageManifest = { dependencies, devDependencies }; + addPeerDependencies(manifest, [enssdkPackageJson]); + + return manifest; +} + +/** + * StackBlitz manifest aligned with `examples/enskit-react-example/package.json`. + */ +export function resolveEnskitExamplePackageManifest(): PlaygroundPackageManifest { + const dependencies = resolveDependencyBlock(enskitExamplePackageJson.dependencies); + const devDependencies = resolveDependencyBlock(enskitExamplePackageJson.devDependencies); + + const manifest: PlaygroundPackageManifest = { dependencies, devDependencies }; + addPeerDependencies(manifest, [enskitPackageJson, enssdkPackageJson]); + + return manifest; +} diff --git a/docs/ensnode.io/src/lib/playground/example-project/types.ts b/docs/ensnode.io/src/lib/playground/example-project/types.ts new file mode 100644 index 0000000000..9744a3fe6e --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/example-project/types.ts @@ -0,0 +1,52 @@ +/** Files as read from the monorepo (paths relative to the example root). */ +export type RawExampleProject = { + files: Record; +}; + +/** After text transforms, before StackBlitz-specific assembly. */ +export type TransformedExampleProject = RawExampleProject; + +/** How the playground runs after `npm install` (always on StackBlitz `template: "node"`). */ +export type PlaygroundRuntime = "node-tsx" | "node-vite"; + +export type PlaygroundView = "editor" | "preview" | "both"; + +/** npm `package.json` dependency blocks for the StackBlitz embed. */ +export type PlaygroundPackageManifest = { + dependencies: Record; + devDependencies: Record; +}; + +/** What {@link import("@components/molecules/CodePlayground").default} consumes. */ +export type PlaygroundProject = { + title: string; + description?: string; + runtime: PlaygroundRuntime; + files: Record; + dependencies: Record; + devDependencies: Record; + entryFileName: string; + openFile?: string; + view?: PlaygroundView; + tsconfig?: string; +}; + +export type EnvReplacement = { + pattern: RegExp; + replacement: string; +}; + +export type ExampleProjectConfig = { + title: string; + description?: string; + runtime: PlaygroundRuntime; + view?: PlaygroundView; + entryFileName: string; + openFile?: string; + fetchRaw: () => RawExampleProject; + envReplacements: EnvReplacement[]; + resolvePackageManifest: () => PlaygroundPackageManifest; + buildTsconfig?: () => string; + /** Merged into project files after assembly (e.g. `.env` for Vite). */ + extraFiles?: Record; +}; diff --git a/docs/ensnode.io/src/lib/playground/loadEnskitExampleProject.test.ts b/docs/ensnode.io/src/lib/playground/loadEnskitExampleProject.test.ts new file mode 100644 index 0000000000..8183f3fa85 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/loadEnskitExampleProject.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; + +import enskitExamplePackageJson from "@workspace/examples/enskit-react-example/package.json"; + +import { ENSNODE_URL } from "./constants"; +import { loadEnskitExampleProject } from "./loadEnskitExampleProject"; + +describe("loadEnskitExampleProject", () => { + it("loads enskit-react-example with vite preview layout and example-shaped manifest", () => { + const project = loadEnskitExampleProject(); + + expect(project.files["index.html"]).toContain('id="root"'); + expect(project.files["vite.config.ts"]).toContain("@vitejs/plugin-react"); + expect(project.files["src/App.tsx"]).toContain("OmnigraphProvider"); + expect(project.files[".env"]).toBe(`VITE_ENSNODE_URL=${ENSNODE_URL}\n`); + + expect(Object.keys(project.dependencies).sort()).toEqual( + Object.keys(enskitExamplePackageJson.dependencies).sort(), + ); + for (const name of Object.keys(enskitExamplePackageJson.devDependencies)) { + expect(project.devDependencies[name]).toBeDefined(); + } + + expect(project.dependencies.enskit).toMatch(/^\d+\.\d+\.\d+/); + expect(project.dependencies.enssdk).toMatch(/^\d+\.\d+\.\d+/); + expect(project.runtime).toBe("node-vite"); + expect(project.view).toBe("both"); + expect(project.entryFileName).toBe("index.html"); + expect(project.openFile).toBe("src/App.tsx"); + }); +}); diff --git a/docs/ensnode.io/src/lib/playground/loadEnskitExampleProject.ts b/docs/ensnode.io/src/lib/playground/loadEnskitExampleProject.ts new file mode 100644 index 0000000000..6fef3fce7c --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/loadEnskitExampleProject.ts @@ -0,0 +1,43 @@ +import { ENSNODE_URL } from "./constants"; +import { + fetchRawExampleProjectFromGlob, + mergeRawExampleProjects, +} from "./example-project/fetchRawExampleProject"; +import { buildViteReactPlaygroundTsconfig } from "./example-project/buildPlaygroundTsconfig"; +import { loadExampleProject } from "./example-project/loadExampleProject"; +import { resolveEnskitExamplePackageManifest } from "./example-project/resolvePinnedDependencies"; +import type { PlaygroundProject } from "./example-project/types"; + +const enskitExampleSourceModules = import.meta.glob( + "../../../../../examples/enskit-react-example/src/**/*.{ts,tsx}", + { query: "?raw", import: "default", eager: true }, +) as Record; + +const enskitExampleRootModules = import.meta.glob( + "../../../../../examples/enskit-react-example/{index.html,vite.config.ts}", + { query: "?raw", import: "default", eager: true }, +) as Record; + +const EXAMPLE_PATH_PREFIX = "examples/enskit-react-example"; + +export function loadEnskitExampleProject(): PlaygroundProject { + return loadExampleProject({ + title: "enskit-react-example", + description: "React + Vite demo app for enskit.", + runtime: "node-vite", + view: "both", + entryFileName: "index.html", + openFile: "src/App.tsx", + fetchRaw: () => + mergeRawExampleProjects( + fetchRawExampleProjectFromGlob(enskitExampleSourceModules, EXAMPLE_PATH_PREFIX), + fetchRawExampleProjectFromGlob(enskitExampleRootModules, EXAMPLE_PATH_PREFIX), + ), + envReplacements: [], + resolvePackageManifest: resolveEnskitExamplePackageManifest, + buildTsconfig: buildViteReactPlaygroundTsconfig, + extraFiles: { + ".env": `VITE_ENSNODE_URL=${ENSNODE_URL}\n`, + }, + }); +} diff --git a/docs/ensnode.io/src/lib/playground/loadEnssdkExampleProject.test.ts b/docs/ensnode.io/src/lib/playground/loadEnssdkExampleProject.test.ts new file mode 100644 index 0000000000..99c34a28e6 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/loadEnssdkExampleProject.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; + +import enssdkExamplePackageJson from "@workspace/examples/enssdk-example/package.json"; + +import { loadEnssdkExampleProject } from "./loadEnssdkExampleProject"; + +describe("loadEnssdkExampleProject", () => { + it("loads enssdk-example source with env substituted and example-shaped package manifest", () => { + const project = loadEnssdkExampleProject(); + const indexSource = project.files["src/index.ts"]; + + expect(indexSource).toBeDefined(); + expect(indexSource).toContain("HelloWorldQuery"); + expect(indexSource).not.toContain("process.env.ENSNODE_URL"); + + expect(Object.keys(project.dependencies).sort()).toEqual( + Object.keys(enssdkExamplePackageJson.dependencies).sort(), + ); + for (const name of Object.keys(enssdkExamplePackageJson.devDependencies)) { + expect(project.devDependencies[name]).toBeDefined(); + } + + expect(project.dependencies.enssdk).toMatch(/^\d+\.\d+\.\d+/); + expect(project.devDependencies["@types/node"]).toBe("24.10.9"); + expect(project.devDependencies["gql.tada"]).toBe("^1.8.10"); + expect(project.devDependencies.tsx).toBe("^4.7.1"); + expect(project.devDependencies.graphql).toBeDefined(); + expect(project.devDependencies.viem).toBeDefined(); + + expect(project.entryFileName).toBe("src/index.ts"); + expect(project.runtime).toBe("node-tsx"); + }); +}); diff --git a/docs/ensnode.io/src/lib/playground/loadEnssdkExampleProject.ts b/docs/ensnode.io/src/lib/playground/loadEnssdkExampleProject.ts new file mode 100644 index 0000000000..d7e335421a --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/loadEnssdkExampleProject.ts @@ -0,0 +1,33 @@ +import { ENSNODE_URL } from "./constants"; +import { fetchRawExampleProjectFromGlob } from "./example-project/fetchRawExampleProject"; +import { loadExampleProject } from "./example-project/loadExampleProject"; +import { resolveEnssdkExamplePackageManifest } from "./example-project/resolvePinnedDependencies"; +import type { PlaygroundProject } from "./example-project/types"; + +const enssdkExampleSourceModules = import.meta.glob( + "../../../../../examples/enssdk-example/src/**/*.{ts,tsx}", + { + query: "?raw", + import: "default", + eager: true, + }, +) as Record; + +export function loadEnssdkExampleProject(): PlaygroundProject { + return loadExampleProject({ + title: "enssdk-example", + description: "Query the eth domain and its subdomains with enssdk.", + runtime: "node-tsx", + view: "editor", + entryFileName: "src/index.ts", + fetchRaw: () => + fetchRawExampleProjectFromGlob(enssdkExampleSourceModules, "examples/enssdk-example"), + envReplacements: [ + { + pattern: /const ENSNODE_URL = process\.env\.ENSNODE_URL!;/, + replacement: `const ENSNODE_URL = ${JSON.stringify(ENSNODE_URL)};`, + }, + ], + resolvePackageManifest: resolveEnssdkExamplePackageManifest, + }); +} diff --git a/docs/ensnode.io/src/lib/playground/types.ts b/docs/ensnode.io/src/lib/playground/types.ts new file mode 100644 index 0000000000..6fd463fe76 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/types.ts @@ -0,0 +1,17 @@ +import { z } from "astro/zod"; + +/** Arbitrary JSON object (validated as a plain object tree, not a string). */ +const jsonObjectSchema = z.record(z.string(), z.unknown()); + +export const OmnigraphExampleQuerySchema = z.object({ + id: z.string(), + name: z.string(), + description: z.string(), + category: z.string(), + query: z.string(), + variables: jsonObjectSchema, + response: jsonObjectSchema.optional(), + connection: z.string(), +}); + +export type OmnigraphExampleQuery = z.infer; diff --git a/docs/ensnode.io/src/lib/playground/utils.ts b/docs/ensnode.io/src/lib/playground/utils.ts new file mode 100644 index 0000000000..7282c7add4 --- /dev/null +++ b/docs/ensnode.io/src/lib/playground/utils.ts @@ -0,0 +1,68 @@ +/** Pretty-print JSON for Starlight `Code` blocks and ENSAdmin query params. */ +export function stringifyJsonForDocs(value: unknown): string { + return JSON.stringify(value, null, 2); +} + +export function getNiceHeightForCodeSnippet(snippet: string): number { + const linesCount = snippet.split("\n").length; + const lineHeight = 18; + const headerHeight = 38; + const footerHeight = 32; + const height = linesCount * lineHeight + headerHeight + footerHeight; + + const terminalHeightPercentage = 0.35; + + return Math.ceil(height / (1 - terminalHeightPercentage)); +} + +/** + * Build a curl example that POSTs the same JSON body as enssdk's Omnigraph module + * (`POST {baseUrl}/api/omnigraph` with `{ query, variables }`). + */ +export function buildOmnigraphCurlExample(params: { + connectionBaseUrl: string; + query: string; + variables: Record; +}): string { + const base = params.connectionBaseUrl.replace(/\/+$/, ""); + const url = `${base}/api/omnigraph`; + const compactQuery = params.query.replace(/\s+/g, " ").trim(); + const body = JSON.stringify( + { + query: compactQuery, + variables: params.variables, + }, + null, + 2, + ); + return [ + `# POST JSON to your ENSNode Omnigraph endpoint (same path enssdk uses).`, + `curl -sS -X POST "${url}" \\`, + ` -H "Content-Type: application/json" \\`, + ` -d @- <<'EOF'`, + body, + `EOF`, + ].join("\n"); +} + +/** Docs path for the hosted ENSNode instances catalog. */ +export const HOSTED_INSTANCES_DOC_PATH = "/docs/integrate/hosted-instances" as const; + +/** Link to a hosted instance section (Starlight heading anchor on the hosted instances page). */ +export function getHostedEnsNodeInstanceDocUrl(headingAnchor: string): string { + return `${HOSTED_INSTANCES_DOC_PATH}#${headingAnchor}`; +} + +/** ENSAdmin Omnigraph playground deep link (opens in browser). */ +export function buildEnsadminOmnigraphUrl(params: { + ensadminBaseUrl: string; + query: string; + connection: string; + variables: Record; +}): string { + const url = new URL("/api/omnigraph", params.ensadminBaseUrl); + url.searchParams.set("query", params.query); + url.searchParams.set("connection", params.connection); + url.searchParams.set("variables", stringifyJsonForDocs(params.variables)); + return url.toString(); +} diff --git a/docs/ensnode.io/src/pages/404.astro b/docs/ensnode.io/src/pages/404.astro index 6983c7aa04..35b9525f3d 100644 --- a/docs/ensnode.io/src/pages/404.astro +++ b/docs/ensnode.io/src/pages/404.astro @@ -11,7 +11,7 @@ import { Balancer } from "react-wrap-balancer"; import error_code from "../assets/404.svg"; import skies_image from "../assets/404_clouds_image.png"; import mobile_skies_image from "../assets/404_mobile_clouds_image.png"; -import StaticHeader from "../components/molecules/StaticHeader.astro"; +import StaticHeader from "@components/molecules/StaticHeader.astro"; const verticalAlignmentBase = "flex flex-col flex-nowrap justify-center items-center"; --- diff --git a/docs/ensnode.io/src/pages/examples.astro b/docs/ensnode.io/src/pages/examples.astro index a632477544..7bb7fd044b 100644 --- a/docs/ensnode.io/src/pages/examples.astro +++ b/docs/ensnode.io/src/pages/examples.astro @@ -1,8 +1,8 @@ --- import { getCollection } from "astro:content"; import { Icon } from "astro-icon/components"; -import StaticHeader from "../components/molecules/StaticHeader.astro"; -import ExampleCard from "../components/organisms/ExampleCard.astro"; +import StaticHeader from "@components/molecules/StaticHeader.astro"; +import ExampleCard from "@components/organisms/ExampleCard.astro"; import Layout from "../layouts/Layout.astro"; const examples = await getCollection("examples"); diff --git a/docs/ensnode.io/src/pages/index.astro b/docs/ensnode.io/src/pages/index.astro index 34d8988b06..3df0e00be2 100644 --- a/docs/ensnode.io/src/pages/index.astro +++ b/docs/ensnode.io/src/pages/index.astro @@ -1,7 +1,8 @@ --- import { ENSADMIN_URL } from "astro:env/client"; -import Header from "../components/organisms/Header.astro"; -import Hero from "../components/overrides/Hero.astro"; + +import Header from "@components/organisms/Header.astro"; +import Hero from "@components/overrides/Hero.astro"; import Layout from "../layouts/Layout.astro"; import JoinTelegram from "../components/molecules/JoinTelegram"; --- diff --git a/docs/ensnode.io/src/styles/starlight.css b/docs/ensnode.io/src/styles/starlight.css index 2d839e3337..b0d9bd00d8 100644 --- a/docs/ensnode.io/src/styles/starlight.css +++ b/docs/ensnode.io/src/styles/starlight.css @@ -79,6 +79,64 @@ --sl-shadow-sm: var(--shadow-sm); } +/* Docked sidebar: narrow peek strip on desktop; expands on hover or keyboard focus within nav */ +@media (min-width: 50rem) { + .page[data-sidebar-docked] { + --sl-sidebar-docked-peek: 1rem; + /* Match Starlight splash / no-sidebar content width (default with sidebar is 45rem). */ + --sl-content-width: 67.5rem; + } + + .page[data-sidebar-docked] .main-frame { + padding-inline-start: calc(var(--sl-sidebar-docked-peek) + var(--sl-content-pad-x)); + } + + .page[data-sidebar-docked] nav.sidebar { + position: fixed; + inset-block: var(--sl-nav-height) 0; + inset-inline-start: 0; + z-index: var(--sl-z-index-menu); + width: var(--sl-sidebar-docked-peek); + overflow: hidden; + transition: width 0.2s ease; + background-color: var(--sl-color-bg-sidebar); + border-inline-end: 1px solid var(--sl-color-hairline-shade); + } + + .page[data-sidebar-docked] nav.sidebar:hover, + .page[data-sidebar-docked] nav.sidebar:focus-within { + width: var(--sl-sidebar-width); + box-shadow: var(--sl-shadow-md); + } + + .page[data-sidebar-docked] nav.sidebar .sidebar-pane { + position: absolute; + inset: 0; + width: var(--sl-sidebar-width); + border-inline-end: none; + } + + .page[data-sidebar-docked] nav.sidebar::after { + content: "›"; + position: absolute; + inset-block: 0; + inset-inline-end: 0; + width: var(--sl-sidebar-docked-peek); + display: flex; + align-items: center; + justify-content: center; + color: var(--sl-color-gray-3); + font-size: 1.125rem; + line-height: 1; + pointer-events: none; + } + + .page[data-sidebar-docked] nav.sidebar:hover::after, + .page[data-sidebar-docked] nav.sidebar:focus-within::after { + opacity: 0; + } +} + /* Starlight-specific component styles */ @media (min-width: 1484px) { .page { @@ -87,7 +145,7 @@ margin-inline: auto; } - nav.sidebar .sidebar-pane { + .page:not([data-sidebar-docked]) nav.sidebar .sidebar-pane { position: sticky; height: calc(100vh - var(--sl-nav-height)); scrollbar-gutter: auto; diff --git a/docs/ensnode.io/tsconfig.json b/docs/ensnode.io/tsconfig.json index 706c9a487c..93be417df4 100644 --- a/docs/ensnode.io/tsconfig.json +++ b/docs/ensnode.io/tsconfig.json @@ -5,8 +5,16 @@ "compilerOptions": { "baseUrl": ".", "paths": { + "@assets/*": ["./src/assets/*"], + "@components/*": ["./src/components/*"], + "@content/*": ["./src/content/*"], + "@data/*": ["./src/data/*"], + "@lib/*": ["./src/lib/*"], + "@scripts/*": ["./src/scripts/*"], + "@styles/*": ["./src/styles/*"], "@workspace/*": ["../../*"] }, - "jsx": "react-jsx" + "jsx": "react-jsx", + "resolveJsonModule": true } } diff --git a/docs/ensnode.io/vitest.config.ts b/docs/ensnode.io/vitest.config.ts new file mode 100644 index 0000000000..3891afe7c8 --- /dev/null +++ b/docs/ensnode.io/vitest.config.ts @@ -0,0 +1,26 @@ +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { defineConfig } from "vitest/config"; + +const docsRoot = fileURLToPath(new URL(".", import.meta.url)); + +export default defineConfig({ + resolve: { + alias: { + "@assets": resolve(docsRoot, "src/assets"), + "@components": resolve(docsRoot, "src/components"), + "@content": resolve(docsRoot, "src/content"), + "@data": resolve(docsRoot, "src/data"), + "@lib": resolve(docsRoot, "src/lib"), + "@scripts": resolve(docsRoot, "src/scripts"), + "@styles": resolve(docsRoot, "src/styles"), + "@workspace": resolve(docsRoot, "../.."), + }, + }, + test: { + name: "@docs/ensnode", + environment: "node", + include: ["src/**/*.test.ts"], + }, +}); diff --git a/examples/enskit-react-example/package.json b/examples/enskit-react-example/package.json index 08c1bc1c97..e7f9b0f1f8 100644 --- a/examples/enskit-react-example/package.json +++ b/examples/enskit-react-example/package.json @@ -12,8 +12,8 @@ "generate:gqlschema": "gql.tada generate-output" }, "dependencies": { - "enskit": "workspace:*", - "enssdk": "workspace:*", + "enskit": "0.0.0-preview-fix-sha-89c022b-20260518142147", + "enssdk": "0.0.0-preview-fix-sha-89c022b-20260518142147", "react": "catalog:", "react-dom": "catalog:", "react-router": "^7.6.1" diff --git a/examples/enskit-react-example/src/AccountView.tsx b/examples/enskit-react-example/src/AccountView.tsx index fa95547f6a..895902d796 100644 --- a/examples/enskit-react-example/src/AccountView.tsx +++ b/examples/enskit-react-example/src/AccountView.tsx @@ -15,7 +15,9 @@ const AccountDomainsQuery = graphql(` domains(first: $first, after: $after) { totalCount edges { - node { __typename id canonical { name { interpreted } } } + # # TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + # node { __typename id canonical { name { interpreted } } } + node { __typename id name } } pageInfo { hasNextPage endCursor } } @@ -60,9 +62,15 @@ function RenderAccount({ address }: { address: NormalizedAddress }) {
    {domains.edges.map((edge) => (
  • + {/* + TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: {edge.node.canonical ? ( {beautifyInterpretedName(edge.node.canonical.name.interpreted)} + */} + {edge.node.name ? ( + + {beautifyInterpretedName(edge.node.name)} ) : ( non-canonical domain diff --git a/examples/enskit-react-example/src/DomainView.tsx b/examples/enskit-react-example/src/DomainView.tsx index fd5a2fe30d..3ea47cef1b 100644 --- a/examples/enskit-react-example/src/DomainView.tsx +++ b/examples/enskit-react-example/src/DomainView.tsx @@ -8,7 +8,9 @@ const DomainFragment = graphql(` fragment DomainFragment on Domain { __typename id - canonical { name { interpreted } } + # # TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + # canonical { name { interpreted } } + name owner { id address } } `); @@ -18,7 +20,9 @@ const DomainByNameQuery = graphql( query DomainByName($name: InterpretedName!, $first: Int!, $after: String) { domain(by: { name: $name }) { ...DomainFragment - parent { canonical { name { interpreted } } } + # # TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + # parent { canonical { name { interpreted } } } + parent { name } subdomains(first: $first, after: $after) { edges { node { @@ -43,10 +47,13 @@ function SubdomainLink({ data }: { data: FragmentOf }) { return (
  • - {domain.canonical ? ( - - {beautifyInterpretedName(domain.canonical.name.interpreted)} - + {domain.name ? ( + {beautifyInterpretedName(domain.name)} + // TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + // {domain.canonical ? ( + // + // {beautifyInterpretedName(domain.canonical.name.interpreted)} + // ) : ( non-canonical domain )}{" "} @@ -81,7 +88,11 @@ function RenderDomain({ name }: { name: InterpretedName }) { return (
    + {/* + TODO: after upgrading v2-sepolia to have materialized canonical name, update this to:

    {beautifyInterpretedName(domain.canonical?.name.interpreted ?? name)}

    + */} +

    {beautifyInterpretedName(domain.name ?? name)}

    Owner:{" "} {domain.owner ? ( @@ -94,10 +105,18 @@ function RenderDomain({ name }: { name: InterpretedName }) {

    Version: {domain.__typename}

    + {/* + TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: {data.domain.parent?.canonical && ( ← {beautifyInterpretedName(data.domain.parent.canonical.name.interpreted)} + )} + */} + {data.domain.parent?.name && ( + + ← {beautifyInterpretedName(data.domain.parent.name)} + )}

    Subdomains

    diff --git a/examples/enskit-react-example/src/SearchView.tsx b/examples/enskit-react-example/src/SearchView.tsx index 9cb54642b7..a3b64219c7 100644 --- a/examples/enskit-react-example/src/SearchView.tsx +++ b/examples/enskit-react-example/src/SearchView.tsx @@ -5,9 +5,11 @@ import { Link, useSearchParams } from "react-router"; const DomainsByNameQuery = graphql(` query DomainsByName($name: String!, $first: Int!, $after: String) { - domains(where: { name: { starts_with: $name } }, first: $first, after: $after) { + domains(where: { name: $name }, first: $first, after: $after) { edges { - node { __typename id canonical { name { interpreted } } } + # # TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + # node { __typename id canonical { name { interpreted } } } + node { __typename id name } } pageInfo { hasNextPage @@ -67,9 +69,9 @@ export function SearchView() {

    Domain Search

    - Showcases live querying via Query.domains(where: {"{ name: { starts_with } }"}) - . Only Canonical Domains are rendered. Input is debounced by {DEBOUNCE_MS}ms and - synced to the URL as ?query=. + Showcases live querying via Query.domains(where: {"{ name }"}). Only{" "} + Canonical Domains are rendered. Input is debounced by {DEBOUNCE_MS}ms and synced to + the URL as ?query=.

    Loading...

    }
      {data?.domains?.edges.map((edge) => { - if (!edge.node.canonical) return null; + if (!edge.node.name) return null; return (
    • ({edge.node.__typename === "ENSv1Domain" ? "v1" : "v2"}){" "} + + {beautifyInterpretedName(edge.node.name)} + {/* + TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: {beautifyInterpretedName(edge.node.canonical.name.interpreted)} + */}
    • ); diff --git a/examples/enssdk-example/package.json b/examples/enssdk-example/package.json index 72c39de7a7..c6c97ca5e7 100644 --- a/examples/enssdk-example/package.json +++ b/examples/enssdk-example/package.json @@ -10,7 +10,7 @@ "generate:gqlschema": "gql.tada generate-output" }, "dependencies": { - "enssdk": "workspace:*" + "enssdk": "0.0.0-preview-fix-sha-89c022b-20260518142147" }, "devDependencies": { "@types/node": "catalog:", diff --git a/examples/enssdk-example/src/index.ts b/examples/enssdk-example/src/index.ts index 4dd88e3629..1edfca0bf9 100644 --- a/examples/enssdk-example/src/index.ts +++ b/examples/enssdk-example/src/index.ts @@ -13,7 +13,9 @@ const client = createEnsNodeClient({ url: ENSNODE_URL }).extend(omnigraph); const DomainFragment = graphql(` fragment DomainFragment on Domain { __typename - canonical { name { interpreted } } + # # TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + # canonical { name { interpreted } } + name owner { address } } `); @@ -36,14 +38,17 @@ const HelloWorldQuery = graphql( function formatDomain(data: FragmentOf): string { // type-safe access to fragment data! const domain = readFragment(DomainFragment, data); - const name = domain.canonical - ? beautifyInterpretedName(domain.canonical.name.interpreted) - : ""; - const owner = domain.owner?.address ?? "0x0"; + // TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + // const name = domain.canonical + // ? beautifyInterpretedName(domain.canonical.name.interpreted) + // : ""; + const name = domain.name ? beautifyInterpretedName(domain.name) : ""; + const owner = domain.owner?.address ?? "0x0 (means reserved for ENSv2)"; return `${name} (${domain.__typename}) — Owner ${owner}`; } async function main() { + console.log(`Querying ENSNode at ${ENSNODE_URL}...`); const name = asInterpretedName("eth"); const result = await client.omnigraph.query({ diff --git a/examples/enssdk-example/tsconfig.json b/examples/enssdk-example/tsconfig.json index 1a1b0a5a12..6d4a304de0 100644 --- a/examples/enssdk-example/tsconfig.json +++ b/examples/enssdk-example/tsconfig.json @@ -6,7 +6,7 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "lib": ["ESNext"], + "lib": ["ESNext", "DOM"], "plugins": [ // here we enable the gql.tada typescript plugin { diff --git a/examples/omnigraph-graphql-example/src/index.ts b/examples/omnigraph-graphql-example/src/index.ts index d60d4c272b..5ba09e0fe7 100644 --- a/examples/omnigraph-graphql-example/src/index.ts +++ b/examples/omnigraph-graphql-example/src/index.ts @@ -9,11 +9,15 @@ const HELLO_WORLD_QUERY = /* GraphQL */ ` query HelloWorld($name: InterpretedName!) { domain(by: { name: $name }) { __typename - canonical { name { interpreted } } + # # TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + # canonical { name { interpreted } } + name owner { address } subdomains(first: 20) { totalCount - edges { node { __typename canonical { name { interpreted } } owner { address } } } + # # TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + # edges { node { __typename canonical { name { interpreted } } owner { address } } } + edges { node { __typename name owner { address } } } } } } @@ -21,7 +25,9 @@ const HELLO_WORLD_QUERY = /* GraphQL */ ` interface Domain { __typename: "ENSv1Domain" | "ENSv2Domain"; - canonical: { name: { interpreted: string } } | null; + // TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + // canonical: { name: { interpreted: string } } | null; + name: string; owner: { address: string } | null; } @@ -40,12 +46,15 @@ interface QueryResult { } function formatDomain(domain: Domain): string { - const name = domain.canonical?.name.interpreted ?? ""; + // TODO: after upgrading v2-sepolia to have materialized canonical name, update this to: + // const name = domain.canonical?.name.interpreted ?? ""; + const name = domain.name ?? ""; const owner = domain.owner?.address ?? "0x0"; return `${name} (${domain.__typename}) — Owner ${owner}`; } async function main() { + console.log(`Querying ENSNode at ${ENSNODE_URL}...`); const response = await fetch(new URL("/api/omnigraph", ENSNODE_URL), { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/examples/omnigraph-graphql-example/tsconfig.json b/examples/omnigraph-graphql-example/tsconfig.json index 96b780876d..96348768a7 100644 --- a/examples/omnigraph-graphql-example/tsconfig.json +++ b/examples/omnigraph-graphql-example/tsconfig.json @@ -6,7 +6,7 @@ "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "lib": ["ESNext"] + "lib": ["ESNext", "DOM"] }, "include": ["src"] } diff --git a/package.json b/package.json index 4b4007b3d0..a59a495094 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,8 @@ "patchedDependencies": { "@opentelemetry/api": "patches/@opentelemetry__api.patch", "@opentelemetry/otlp-exporter-base": "patches/@opentelemetry__otlp-exporter-base.patch", - "@changesets/assemble-release-plan@6.0.9": "patches/@changesets__assemble-release-plan@6.0.9.patch" + "@changesets/assemble-release-plan@6.0.9": "patches/@changesets__assemble-release-plan@6.0.9.patch", + "starlight-llms-txt@0.10.0": "patches/starlight-llms-txt@0.10.0.patch" } } } diff --git a/packages/ensnode-sdk/package.json b/packages/ensnode-sdk/package.json index f1b5e3b45a..5eb0581ed3 100644 --- a/packages/ensnode-sdk/package.json +++ b/packages/ensnode-sdk/package.json @@ -19,7 +19,8 @@ ], "exports": { ".": "./src/index.ts", - "./internal": "./src/internal.ts" + "./internal": "./src/internal.ts", + "./omnigraph-api/example-queries": "./src/omnigraph-api/example-queries.ts" }, "sideEffects": false, "publishConfig": { @@ -34,6 +35,16 @@ "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } + }, + "./internal": { + "import": { + "types": "./dist/internal.d.ts", + "default": "./dist/internal.js" + }, + "require": { + "types": "./dist/internal.d.cts", + "default": "./dist/internal.cjs" + } } }, "main": "./dist/index.cjs", diff --git a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts index f2eb207333..c34e3f17ef 100644 --- a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts +++ b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts @@ -33,20 +33,38 @@ const ENS_TEST_ENV_V2_ETH_REGISTRAR = maybeGetDatasourceContract( const VITALIK_ADDRESS = toNormalizedAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); // owns sfmonicdeb*.eth (mix of v1 + v2) on sepolia-v2 and holds v2 ETHRegistry permissions -const SEPOLIA_V2_USER_ADDRESS = toNormalizedAddress("0x2f8e8b1126e75fde0b7f731e7cb5847eba2d2574"); +const _SEPOLIA_V2_USER_ADDRESS = toNormalizedAddress("0x2f8e8b1126e75fde0b7f731e7cb5847eba2d2574"); + +const SEPOLIA_V2_ADDRESS_WITH_LOT_OF_NAMES = toNormalizedAddress( + "0x205d2686da3bf33f64c17f21462c51b5ead462cf", +); const DEVNET_NAME_WITH_OWNED_RESOLVER = asInterpretedName("example.eth"); const SEPOLIA_V2_NAME_WITH_OWNED_RESOLVER = asInterpretedName("sfmonicdebmig.eth"); -export const GRAPHQL_API_EXAMPLE_QUERIES: Array<{ +const SEPOLIA_V2_TEST_NAME = asInterpretedName("test-name.eth"); + +export type GraphqlApiExampleQuery = { + id: string; query: string; variables: NamespaceSpecificValue>; -}> = [ +}; + +export function getGraphqlApiExampleQueryById(id: string): GraphqlApiExampleQuery { + const found = graphqlApiExampleQueryById.get(id); + if (!found) { + throw new Error(`Unknown GraphQL API example query id: ${id}`); + } + return found; +} + +export const GRAPHQL_API_EXAMPLE_QUERIES: GraphqlApiExampleQuery[] = [ //////////////// // Hello World //////////////// { + id: "hello-world", query: `# # Welcome to this interactive playground for # ENSNode's GraphQL API! @@ -65,6 +83,7 @@ query HelloWorld { // Find Domains ///////////////// { + id: "find-domains", query: ` query FindDomains( $name: DomainsNameFilter! @@ -94,7 +113,7 @@ query FindDomains( order: { by: "NAME", dir: "DESC" }, }, [ENSNamespaceIds.SepoliaV2]: { - name: { starts_with: "sfmonic" }, + name: { starts_with: "test-na" }, order: { by: "NAME", dir: "DESC" }, }, }, @@ -104,6 +123,7 @@ query FindDomains( // Domain By Name /////////////////// { + id: "domain-by-name", query: ` query DomainByName($name: InterpretedName!) { domain(by: {name: $name}) { @@ -121,7 +141,7 @@ query DomainByName($name: InterpretedName!) { }`, variables: { default: { name: "eth" }, - [ENSNamespaceIds.SepoliaV2]: { name: "sfmonicdebmig.eth" }, + [ENSNamespaceIds.SepoliaV2]: { name: SEPOLIA_V2_TEST_NAME }, }, }, @@ -129,6 +149,7 @@ query DomainByName($name: InterpretedName!) { // Domain Subdomains ////////////////////// { + id: "domain-subdomains", query: ` query DomainSubdomains($name: InterpretedName!) { domain(by: {name: $name}) { @@ -149,6 +170,7 @@ query DomainSubdomains($name: InterpretedName!) { // Domain Events ///////////////// { + id: "domain-events", query: ` query DomainEvents($name: InterpretedName!) { domain(by: {name: $name}) { @@ -177,6 +199,7 @@ query DomainEvents($name: InterpretedName!) { // Account Domains //////////////////// { + id: "domains-by-address", query: ` query AccountDomains( $address: Address! @@ -195,7 +218,7 @@ query AccountDomains( variables: { default: { address: VITALIK_ADDRESS }, [ENSNamespaceIds.EnsTestEnv]: { address: accounts.owner.address }, - [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_USER_ADDRESS }, + [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_ADDRESS_WITH_LOT_OF_NAMES }, }, }, @@ -203,6 +226,7 @@ query AccountDomains( // Account Events //////////////////// { + id: "account-events", query: ` query AccountEvents( $address: Address! @@ -214,7 +238,7 @@ query AccountEvents( variables: { default: { address: VITALIK_ADDRESS }, [ENSNamespaceIds.EnsTestEnv]: { address: accounts.deployer.address }, - [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_USER_ADDRESS }, + [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_ADDRESS_WITH_LOT_OF_NAMES }, }, }, @@ -222,6 +246,7 @@ query AccountEvents( // Registry Domains ///////////////////// { + id: "registry-domains", query: ` query RegistryDomains( $registry: AccountIdInput! @@ -248,6 +273,7 @@ query RegistryDomains( // Permissions By Contract //////////////////////////// { + id: "permissions-by-contract", query: ` query PermissionsByContract( $contract: AccountIdInput! @@ -275,6 +301,7 @@ query PermissionsByContract( variables: { // TODO: same as above default: { contract: ENS_TEST_ENV_V2_ETH_REGISTRAR }, + // TODO: example response is empty for this address on Sepolia V2 [ENSNamespaceIds.SepoliaV2]: { contract: SEPOLIA_V2_V2_ETH_REGISTRAR }, }, }, @@ -283,6 +310,7 @@ query PermissionsByContract( // Permissions By User //////////////////////// { + id: "permissions-by-user", query: ` query PermissionsByUser($address: Address!) { account(by: { address: $address }) { @@ -298,7 +326,8 @@ query PermissionsByUser($address: Address!) { }`, variables: { default: { address: accounts.deployer.address }, - [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_USER_ADDRESS }, + // TODO: example response is empty for this address on Sepolia V2 + [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_ADDRESS_WITH_LOT_OF_NAMES }, }, }, @@ -306,6 +335,7 @@ query PermissionsByUser($address: Address!) { // Account Resolver Permissions ////////////////////////////////// { + id: "account-resolver-permissions", query: ` query AccountResolverPermissions($address: Address!) { account(by: { address: $address }) { @@ -324,7 +354,7 @@ query AccountResolverPermissions($address: Address!) { }`, variables: { default: { address: accounts.deployer.address }, - [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_USER_ADDRESS }, + [ENSNamespaceIds.SepoliaV2]: { address: SEPOLIA_V2_ADDRESS_WITH_LOT_OF_NAMES }, }, }, @@ -332,6 +362,7 @@ query AccountResolverPermissions($address: Address!) { // Domain's Assigned Resolver ////////////////////////////// { + id: "domain-resolver", query: ` query DomainResolver($name: InterpretedName!) { domain(by: { name: $name }) { @@ -355,6 +386,7 @@ query DomainResolver($name: InterpretedName!) { // Namegraph ////////////// { + id: "namegraph", query: ` query Namegraph { root { @@ -387,3 +419,7 @@ query Namegraph { variables: { default: {} }, }, ]; + +const graphqlApiExampleQueryById = new Map( + GRAPHQL_API_EXAMPLE_QUERIES.map((entry) => [entry.id, entry]), +); diff --git a/packages/ensnode-sdk/src/shared/datasource-contract.ts b/packages/ensnode-sdk/src/shared/datasource-contract.ts index ab5224c9f2..e76d1a359d 100644 --- a/packages/ensnode-sdk/src/shared/datasource-contract.ts +++ b/packages/ensnode-sdk/src/shared/datasource-contract.ts @@ -6,7 +6,8 @@ import { type ENSNamespaceId, maybeGetDatasource, } from "@ensnode/datasources"; -import { accountIdEqual } from "@ensnode/ensnode-sdk"; + +import { accountIdEqual } from "./account-id"; /** * Gets the AccountId for the contract in the specified namespace, datasource, and diff --git a/packages/ensnode-sdk/tsup.config.ts b/packages/ensnode-sdk/tsup.config.ts index f855a15e5e..102aa82abd 100644 --- a/packages/ensnode-sdk/tsup.config.ts +++ b/packages/ensnode-sdk/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["src/index.ts"], + entry: ["src/index.ts", "src/internal.ts"], platform: "neutral", format: ["esm", "cjs"], target: "es2022", diff --git a/packages/integration-test-env/src/enssdk-example.integration.test.ts b/packages/integration-test-env/src/enssdk-example.integration.test.ts index 0ba6c15b9e..dc1456dc5c 100644 --- a/packages/integration-test-env/src/enssdk-example.integration.test.ts +++ b/packages/integration-test-env/src/enssdk-example.integration.test.ts @@ -13,7 +13,9 @@ const EXAMPLE_DIR = join( "enssdk-example", ); -describe("enssdk-example", () => { +// TODO: uncomment when v2-sepolia (and other hosted instances) expose materialized +// `canonical { name { interpreted } }` so examples can use the local orchestrator ENSApi. +describe.skip("enssdk-example", () => { it("smoke test: completes against the configured ENSNode with exit code 0", () => { const result = spawnSync("pnpm", ["start"], { cwd: EXAMPLE_DIR, diff --git a/packages/integration-test-env/src/omnigraph-graphql-example.integration.test.ts b/packages/integration-test-env/src/omnigraph-graphql-example.integration.test.ts index 212ba1dd64..0ce78335cd 100644 --- a/packages/integration-test-env/src/omnigraph-graphql-example.integration.test.ts +++ b/packages/integration-test-env/src/omnigraph-graphql-example.integration.test.ts @@ -13,7 +13,9 @@ const EXAMPLE_DIR = join( "omnigraph-graphql-example", ); -describe("omnigraph-graphql-example", () => { +// TODO: uncomment when v2-sepolia (and other hosted instances) expose materialized +// `canonical { name { interpreted } }` so examples can use the local orchestrator ENSApi. +describe.skip("omnigraph-graphql-example", () => { it("smoke test: completes against the configured ENSNode with exit code 0", () => { const result = spawnSync("pnpm", ["start"], { cwd: EXAMPLE_DIR, diff --git a/patches/starlight-llms-txt@0.10.0.patch b/patches/starlight-llms-txt@0.10.0.patch new file mode 100644 index 0000000000..b59049c062 --- /dev/null +++ b/patches/starlight-llms-txt@0.10.0.patch @@ -0,0 +1,18 @@ +diff --git a/llms-full.txt.ts b/llms-full.txt.ts +index cdc3a34c0555c4a556224f63d937d976c669eef0..45d60f54994240e93277021cb649237c4874f4ce 100644 +--- a/llms-full.txt.ts ++++ b/llms-full.txt.ts +@@ -1,4 +1,5 @@ + import type { APIRoute } from 'astro'; ++import { starlightLllmsTxtContext } from 'virtual:starlight-llms-txt/context'; + import { generateLlmsTxt } from './generator'; + import { getSiteTitle } from './utils'; + +@@ -9,6 +10,7 @@ export const GET: APIRoute = async (context) => { + const body = await generateLlmsTxt(context, { + minify: false, + description: `This is the full developer documentation for ${getSiteTitle()}`, ++ exclude: starlightLllmsTxtContext.exclude, + }); + return new Response(body); + }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be5cff76ec..bb18d4271d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,7 +77,7 @@ catalogs: version: 19.2.1 tailwind-merge: specifier: ^3.4.0 - version: 3.5.0 + version: 3.4.0 tailwindcss: specifier: ^4.3.0 version: 4.3.0 @@ -149,6 +149,9 @@ patchedDependencies: '@opentelemetry/otlp-exporter-base': hash: 1e5f3f5fa82375d902525a5d0be6b280f242f01ac4a6e52952bd8d6841f81171 path: patches/@opentelemetry__otlp-exporter-base.patch + starlight-llms-txt@0.10.0: + hash: b0be40873b81b978ff243b51b244d99ddad85f5b0ff444cc9890cb6a1566277a + path: patches/starlight-llms-txt@0.10.0.patch importers: @@ -185,8 +188,8 @@ importers: specifier: workspace:* version: link:../../packages/datasources '@ensnode/ensnode-sdk': - specifier: workspace:* - version: link:../../packages/ensnode-sdk + specifier: 0.0.0-preview-fix-sha-89c022b-20260518142147 + version: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(typescript@5.9.3)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) '@ensnode/scalar-react': specifier: workspace:* version: link:../../packages/scalar-react @@ -266,8 +269,8 @@ importers: specifier: 'catalog:' version: 4.1.0 enssdk: - specifier: workspace:* - version: link:../../packages/enssdk + specifier: 0.0.0-preview-fix-sha-89c022b-20260518142147 + version: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) graphiql: specifier: 5.2.0 version: 5.2.0(@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-dom@19.2.1(react@19.2.1))(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) @@ -300,7 +303,7 @@ importers: version: 2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tailwind-merge: specifier: 'catalog:' - version: 3.5.0 + version: 3.4.0 tailwindcss-animate: specifier: 'catalog:' version: 1.0.7(tailwindcss@4.3.0) @@ -690,9 +693,18 @@ importers: '@astrojs/starlight-tailwind': specifier: ^5.0.0 version: 5.0.0(@astrojs/starlight@0.39.2(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3))(typescript@5.9.3))(tailwindcss@4.3.0) + '@ensnode/ensnode-sdk': + specifier: 0.0.0-preview-fix-sha-89c022b-20260518142147 + version: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(typescript@5.9.3)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) '@fontsource/inter': specifier: ^5.2.5 version: 5.2.8 + '@graphiql/plugin-doc-explorer': + specifier: 0.4.1 + version: 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)) + '@graphiql/react': + specifier: 0.37.1 + version: 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)) '@headlessui/react': specifier: ^2.2.0 version: 2.2.9(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -708,6 +720,9 @@ importers: '@scalar/astro': specifier: ^0.2.16 version: 0.2.16(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3)) + '@stackblitz/sdk': + specifier: ^1.11.0 + version: 1.11.0 '@tailwindcss/vite': specifier: 'catalog:' version: 4.3.0(vite@7.3.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3)) @@ -729,9 +744,15 @@ importers: classcat: specifier: 5.0.5 version: 5.0.5 + enssdk: + specifier: 0.0.0-preview-fix-sha-89c022b-20260518142147 + version: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) fuse.js: specifier: ^7.1.0 version: 7.1.0 + graphql: + specifier: ^16.10.0 + version: 16.11.0 mermaid: specifier: ^11.15.0 version: 11.15.0 @@ -749,13 +770,16 @@ importers: version: 0.33.5 starlight-llms-txt: specifier: ^0.10.0 - version: 0.10.0(@astrojs/starlight@0.39.2(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3))(typescript@5.9.3))(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.10.0(patch_hash=b0be40873b81b978ff243b51b244d99ddad85f5b0ff444cc9890cb6a1566277a)(@astrojs/starlight@0.39.2(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3))(typescript@5.9.3))(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3)) starlight-sidebar-topics: specifier: ^0.7.1 version: 0.7.1(@astrojs/starlight@0.39.2(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3))(typescript@5.9.3)) tailwindcss: specifier: 'catalog:' version: 4.3.0 + yaml: + specifier: ^2.8.3 + version: 2.8.3 devDependencies: '@types/react': specifier: 'catalog:' @@ -763,6 +787,12 @@ importers: '@types/react-dom': specifier: 'catalog:' version: 19.2.3(@types/react@19.2.7) + tsx: + specifier: ^4.19.3 + version: 4.21.0 + 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.12))(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3) docs/ensrainbow.io: dependencies: @@ -813,11 +843,11 @@ importers: examples/enskit-react-example: dependencies: enskit: - specifier: workspace:* - version: link:../../packages/enskit + specifier: 0.0.0-preview-fix-sha-89c022b-20260518142147 + version: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(react@19.2.1)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) enssdk: - specifier: workspace:* - version: link:../../packages/enssdk + specifier: 0.0.0-preview-fix-sha-89c022b-20260518142147 + version: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) react: specifier: 'catalog:' version: 19.2.1 @@ -850,8 +880,8 @@ importers: examples/enssdk-example: dependencies: enssdk: - specifier: workspace:* - version: link:../../packages/enssdk + specifier: 0.0.0-preview-fix-sha-89c022b-20260518142147 + version: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) devDependencies: '@types/node': specifier: 'catalog:' @@ -1202,7 +1232,7 @@ importers: version: 2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tailwind-merge: specifier: 'catalog:' - version: 3.5.0 + version: 3.4.0 tailwindcss-animate: specifier: 'catalog:' version: 1.0.7(tailwindcss@4.3.0) @@ -2019,6 +2049,16 @@ packages: peerDependencies: viem: ^2.9.2 + '@ensnode/datasources@0.0.0-preview-fix-sha-89c022b-20260518142147': + resolution: {integrity: sha512-yLAOKqRgZvlgu09tXWKJrMCmobjST6PigF8vFB8AmaOLfwZTfOtdTySdR05/e5Rci8UbTZAgYt1qimOqi7Ol5Q==} + peerDependencies: + viem: ^2.22.13 + + '@ensnode/ensnode-sdk@0.0.0-preview-fix-sha-89c022b-20260518142147': + resolution: {integrity: sha512-iQrDVCB5c11G1BtiKGt6RRNmRWSaCiikKPdV+7OrBxsG+BwN3Nqz5P+/DMgbtIPvl2KW+Wbgv5jHcCbaMIJbIw==} + peerDependencies: + viem: ^2.22.13 + '@envelop/core@5.3.2': resolution: {integrity: sha512-06Mu7fmyKzk09P2i2kHpGfItqLLgCq7uO5/nX4fc/iHMplWPNuAx4iYR+WXUQoFHDnP6EUbceQNQ5iyeMz9f3g==} engines: {node: '>=18.0.0'} @@ -4554,6 +4594,12 @@ packages: resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} engines: {node: '>=18.0.0'} + '@stackblitz/sdk@1.11.0': + resolution: {integrity: sha512-DFQGANNkEZRzFk1/rDP6TcFdM82ycHE+zfl9C/M/jXlH68jiqHWHFMQURLELoD8koxvu/eW5uhg94NSAZlYrUQ==} + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -5064,7 +5110,6 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unhead/vue@2.1.13': resolution: {integrity: sha512-HYy0shaHRnLNW9r85gppO8IiGz0ONWVV3zGdlT8CQ0tbTwixznJCIiyqV4BSV1aIF1jJIye0pd1p/k6Eab8Z/A==} @@ -6482,6 +6527,21 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + enskit@0.0.0-preview-fix-sha-89c022b-20260518142147: + resolution: {integrity: sha512-aw/W8BXUT7C9oh/KptTntJwUqTamFXqGlfxT9wRGfkq/fG7ANn67MkoSn+l+AJFzRxFVhFqurDgwAh+jJbc5zA==} + peerDependencies: + gql.tada: ^1.8.10 + graphql: ^16 + react: ^18.0.0 || ^19.0.0 + viem: ^2 + + enssdk@0.0.0-preview-fix-sha-89c022b-20260518142147: + resolution: {integrity: sha512-bbvfXXGs+r5da87xJ9lnY7OwEArjuapg3Asaw3O9FSR43lrklWzxv30nR1URiE7P7Nicb0XMFLzDfTxkAM527Q==} + peerDependencies: + gql.tada: ^1.8.10 + graphql: ^16 + viem: ^2 + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -8182,8 +8242,8 @@ packages: resolution: {integrity: sha512-jdZ10MCxavHoIHwJ5oweOtYy6ElPixEHaMkz0AuaEMovR1MRpVvYFzIEHRxgMEpXYzNpRVByFAniAzwmd1/uug==} engines: {node: '>=20'} - p-queue@9.2.0: - resolution: {integrity: sha512-dWgLE8AH0HjQ9fe74pUkKkvzzYT18Inp4zra3lKHnnwqGvcfcUBrvF2EAVX+envufDNBOzpPq/IBUONDbI7+3g==} + p-queue@9.3.0: + resolution: {integrity: sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==} engines: {node: '>=20'} p-retry@7.1.1: @@ -9252,6 +9312,9 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} @@ -9344,6 +9407,10 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyexec@1.1.2: resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} engines: {node: '>=18'} @@ -10241,7 +10308,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.5.0 - tinyexec: 1.1.2 + tinyexec: 1.0.2 '@antfu/utils@8.1.1': {} @@ -11407,6 +11474,30 @@ snapshots: - typescript - zod + '@ensnode/datasources@0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(typescript@5.9.3)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6))': + dependencies: + '@ponder/utils': 0.2.18(typescript@5.9.3)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) + enssdk: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) + viem: 2.38.5(typescript@5.9.3)(zod@4.3.6) + transitivePeerDependencies: + - gql.tada + - graphql + - typescript + + '@ensnode/ensnode-sdk@0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(typescript@5.9.3)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6))': + dependencies: + '@ensdomains/address-encoder': 1.1.4 + '@ensnode/datasources': 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(typescript@5.9.3)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) + caip: 1.1.1 + date-fns: 4.1.0 + enssdk: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) + viem: 2.38.5(typescript@5.9.3)(zod@4.3.6) + zod: 4.3.6 + transitivePeerDependencies: + - gql.tada + - graphql + - typescript + '@envelop/core@5.3.2': dependencies: '@envelop/instrumentation': 1.0.0 @@ -14174,6 +14265,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@stackblitz/sdk@1.11.0': {} + + '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} @@ -14727,13 +14822,21 @@ snapshots: '@vitest/expect@4.0.5': dependencies: - '@standard-schema/spec': 1.1.0 + '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.3 '@vitest/spy': 4.0.5 '@vitest/utils': 4.0.5 chai: 6.2.0 tinyrainbow: 3.0.3 + '@vitest/mocker@4.0.5(vite@6.4.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.20.6)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.0.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.20.6)(yaml@2.8.3) + '@vitest/mocker@4.0.5(vite@6.4.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.0.5 @@ -15232,7 +15335,7 @@ snapshots: neotraverse: 0.6.18 obug: 2.1.1 p-limit: 7.3.0 - p-queue: 9.2.0 + p-queue: 9.3.0 package-manager-detector: 1.6.0 piccolore: 0.1.3 picomatch: 4.0.4 @@ -15243,7 +15346,7 @@ snapshots: svgo: 4.0.1 tinyclip: 0.1.12 tinyexec: 1.1.2 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 ultrahtml: 1.6.0 unifont: 0.7.4 unist-util-visit: 5.1.0 @@ -15658,7 +15761,7 @@ snapshots: dot-prop: 8.0.2 env-paths: 3.0.0 json-schema-typed: 8.0.1 - semver: 7.8.0 + semver: 7.7.3 uint8array-extras: 0.3.0 confbox@0.1.8: {} @@ -16165,6 +16268,26 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + enskit@0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(react@19.2.1)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)): + dependencies: + '@urql/core': 6.0.1(graphql@16.11.0) + '@urql/exchange-graphcache': 9.0.0(@urql/core@6.0.1(graphql@16.11.0))(graphql@16.11.0) + enssdk: 0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)) + gql.tada: 1.9.1(graphql@16.11.0)(typescript@5.9.3) + graphql: 16.11.0 + react: 19.2.1 + urql: 5.0.1(@urql/core@6.0.1(graphql@16.11.0))(react@19.2.1) + viem: 2.38.5(typescript@5.9.3)(zod@4.3.6) + + enssdk@0.0.0-preview-fix-sha-89c022b-20260518142147(gql.tada@1.9.1(graphql@16.11.0)(typescript@5.9.3))(graphql@16.11.0)(viem@2.38.5(typescript@5.9.3)(zod@4.3.6)): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@ensdomains/address-encoder': 1.1.4 + caip: 1.1.1 + gql.tada: 1.9.1(graphql@16.11.0)(typescript@5.9.3) + graphql: 16.11.0 + viem: 2.38.5(typescript@5.9.3)(zod@4.3.6) + entities@4.5.0: {} entities@6.0.1: {} @@ -18274,7 +18397,7 @@ snapshots: mimic-function: 5.0.1 type-fest: 4.41.0 - p-queue@9.2.0: + p-queue@9.3.0: dependencies: eventemitter3: 5.0.4 p-timeout: 7.0.1 @@ -18562,7 +18685,7 @@ snapshots: picocolors: 1.1.1 pino: 8.21.0 prom-client: 15.1.3 - semver: 7.8.0 + semver: 7.7.3 stacktrace-parser: 0.1.11 superjson: 2.2.6 terminal-size: 4.0.0 @@ -19442,7 +19565,7 @@ snapshots: dependencies: type-fest: 0.7.1 - starlight-llms-txt@0.10.0(@astrojs/starlight@0.39.2(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3))(typescript@5.9.3))(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3)): + starlight-llms-txt@0.10.0(patch_hash=b0be40873b81b978ff243b51b244d99ddad85f5b0ff444cc9890cb6a1566277a)(@astrojs/starlight@0.39.2(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3))(typescript@5.9.3))(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@astrojs/mdx': 5.0.6(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3)) '@astrojs/starlight': 0.39.2(astro@6.3.3(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.59.0)(tsx@4.21.0)(yaml@2.8.3))(typescript@5.9.3) @@ -19614,6 +19737,8 @@ snapshots: tagged-tag@1.0.0: {} + tailwind-merge@3.4.0: {} + tailwind-merge@3.5.0: {} tailwindcss-animate@1.0.7(tailwindcss@4.3.0): @@ -19748,6 +19873,8 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.2: {} + tinyexec@1.1.2: {} tinyglobby@0.2.15: @@ -19876,7 +20003,7 @@ snapshots: typescript-auto-import-cache@0.3.6: dependencies: - semver: 7.8.0 + semver: 7.7.3 typescript-logging@1.0.1: dependencies: @@ -20123,7 +20250,7 @@ snapshots: picomatch: 4.0.4 postcss: 8.5.12 rollup: 4.59.0 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.9 fsevents: 2.3.3 @@ -20139,7 +20266,7 @@ snapshots: picomatch: 4.0.4 postcss: 8.5.12 rollup: 4.59.0 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.9 fsevents: 2.3.3 @@ -20155,7 +20282,7 @@ snapshots: picomatch: 4.0.4 postcss: 8.5.12 rollup: 4.59.0 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.9 fsevents: 2.3.3 @@ -20171,7 +20298,7 @@ snapshots: vitest@4.0.5(@types/debug@4.1.12)(@types/node@24.10.9)(jiti@2.6.1)(jsdom@27.0.1(postcss@8.5.12))(lightningcss@1.32.0)(tsx@4.20.6)(yaml@2.8.3): dependencies: '@vitest/expect': 4.0.5 - '@vitest/mocker': 4.0.5(vite@6.4.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.0.5(vite@6.4.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.20.6)(yaml@2.8.3)) '@vitest/pretty-format': 4.0.5 '@vitest/runner': 4.0.5 '@vitest/snapshot': 4.0.5 @@ -20186,7 +20313,7 @@ snapshots: std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 tinyrainbow: 3.0.3 vite: 6.4.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.20.6)(yaml@2.8.3) why-is-node-running: 2.3.0 @@ -20226,7 +20353,7 @@ snapshots: std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 tinyrainbow: 3.0.3 vite: 6.4.2(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 @@ -20289,7 +20416,7 @@ snapshots: volar-service-typescript@0.0.70(@volar/language-service@2.4.28): dependencies: path-browserify: 1.0.1 - semver: 7.8.0 + semver: 7.7.3 typescript-auto-import-cache: 0.3.6 vscode-languageserver-textdocument: 1.0.12 vscode-nls: 5.2.0 @@ -20366,7 +20493,7 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.4 scule: 1.3.0 - tinyglobby: 0.2.16 + tinyglobby: 0.2.15 unplugin: 3.0.0 unplugin-utils: 0.3.1 vue: 3.5.32(typescript@5.9.3) diff --git a/vitest.config.ts b/vitest.config.ts index 54b43ff44a..40bb62ea89 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,7 +3,11 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { environment: "node", - projects: ["apps/*/vitest.config.ts", "packages/*/vitest.config.ts"], + projects: [ + "apps/*/vitest.config.ts", + "packages/*/vitest.config.ts", + "docs/ensnode.io/vitest.config.ts", + ], // we place LOG_LEVEL here at the root such that running vitest within a specific project continues // to print logs at the default log level env: {