Skip to content

Commit 97c5302

Browse files
committed
Improve ENS Unigraph examples
1 parent d55072a commit 97c5302

20 files changed

Lines changed: 698 additions & 444 deletions

docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export const integrateSidebarTopic = {
119119
label: "Overview",
120120
link: "/docs/integrate/unigraph",
121121
},
122+
{
123+
label: "Core Concepts",
124+
link: "/docs/integrate/unigraph/concepts",
125+
},
122126
{
123127
label: "Examples",
124128
collapsed: true,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
import { Aside } from "@astrojs/starlight/components"
3+
---
4+
5+
<Aside type="tip" title="What is the ENS Unigraph?">
6+
Explore an <a href="/docs/integrate/unigraph">overview</a> and <a
7+
href="/docs/integrate/unigraph/concepts">core concepts</a
8+
> to learn about the ENS Unigraph.
9+
</Aside>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: ENS Unigraph Core Concepts
3+
description: The core concepts behind the ENS Unigraph indexed data model.
4+
---
5+
6+
import { CardGrid, LinkCard, Aside } from "@astrojs/starlight/components"
7+
8+
## Indexed Data Model
9+
10+
The ENS Unigraph indexed data model is available only when the [`unigraph` plugin](/docs/integrate/integration-options/ensnode-plugins#existing-plugins) is activated in your ENSNode instance.
11+
12+
:::caution[`unigraph` plugin required]
13+
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration) about activating [ENSNode Plugins](/docs/integrate/integration-options/ensnode-plugins).
14+
:::
15+
16+
## Canonical Nametree
17+
18+
The **Canonical Nametree** is the set of all Domains that have an inferrable **Canonical Name** — materialized from the namegraph. For every Domain in it, the canonical fields are populated — `canonical_name`, `canonical_path`, `canonical_node`, and `canonical_depth` — across both ENSv1 and ENSv2. That means you can look a name up by `canonical_name = 'vitalik.eth'`, order by `canonical_depth`, or walk a name's path **without branching by `type` on protocol version** and without traversing the namegraph yourself.
19+
20+
Multichain coverage is part of the same model: Basenames (`.base.eth`), Lineanames (`.linea.eth`), and 3DNS names (`.box`) are materialized into the same Unigraph as mainnet `.eth`, so a single query spans every indexable name.
21+
22+
:::danger[Not a substitute for ENS Resolution]
23+
The Unigraph mirrors onchain state; ENS's resolution-time behavior is applied _on top_ of it by
24+
[ENSApi](/docs/services/ensapi). A direct SQL read of resolver records is therefore **not**
25+
ENSIP-10 / CCIP-Read compliant and **cannot** be used as a source of truth for name resolution.
26+
For correct resolution, use the [ENS Omnigraph API](/docs/integrate/omnigraph) (which adds ENS
27+
Protocol Acceleration on top of the Unigraph). Use Unigraph SQL for analytics, discovery, and
28+
custom indexing.
29+
:::
30+
31+
## Query optimizations
32+
33+
### Domains
34+
35+
A `canonical_name` can be very long, as it's the full, correct name. Due to limitations in Postgres btree indexes, the `domains` table has the `canonical_name` column and also the `__canonical_name_prefix` column (the first 64 code points of `canonical_name`, backed by a GIN trigram index).
36+
37+
:::tip[Searching vs. displaying]
38+
Always **select and display `canonical_name`**. When you need to **search** by prefix (`ILIKE 'vit%'`, case-insensitive to match the Omnigraph `starts_with` filter), match against the materialized `__canonical_name_prefix` column (the first 64 code points of `canonical_name`, backed by a GIN trigram index) so the `ILIKE` filter is index-backed:
39+
40+
```sql
41+
SELECT id, type, canonical_name, canonical_node, owner_id
42+
FROM ensindexer_0.domains
43+
WHERE __canonical_name_prefix ILIKE 'vit%'
44+
ORDER BY __canonical_name_prefix
45+
LIMIT 10;
46+
```
47+
48+
The `SELECT` still returns `canonical_name`; only the `ILIKE` / `ORDER BY` use the prefix. The GIN trigram index backs the `ILIKE` filter; the `ORDER BY` then sorts the matched set (cheap under a small `LIMIT`) — scope the query by `registry_id` to use the `(registry_id, __canonical_name_prefix, id)` btree for fully index-backed ordering. For exact matches, use `canonical_name` directly (`canonical_name = 'vitalik.eth'`).
49+
:::

docs/ensnode.io/src/content/docs/docs/integrate/unigraph/examples/account-domains.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import EnsDbWriterSchemaIntro from "@components/molecules/EnsDbWriterSchemaIntro
1010
import EnsDbReaderIntro from "@components/molecules/EnsDbReaderIntro.astro";
1111
import { exampleAccountDomains } from "@data/unigraph-examples/account-domains";
1212

13-
:::caution[`unigraph` plugin required]
14-
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration)
15-
:::
16-
1713
Fetch the Domains owned by an address (across both ENSv1 and ENSv2), including each Domain's `type` (`ENSv1Domain` vs `ENSv2Domain`). See [Connect](/docs/integrate/unigraph/examples) for setup.
1814

1915
<UnigraphStaticExample

docs/ensnode.io/src/content/docs/docs/integrate/unigraph/examples/domain-by-name.mdx

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,8 @@ import EnsDbWriterSchemaIntro from "@components/molecules/EnsDbWriterSchemaIntro
1010
import EnsDbReaderIntro from "@components/molecules/EnsDbReaderIntro.astro";
1111
import { exampleDomainByName } from "@data/unigraph-examples/domain-by-name";
1212

13-
:::caution[`unigraph` plugin required]
14-
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration)
15-
:::
16-
1713
Fetch a Domain by its canonical name. Because `canonical_name` is materialized across both ENSv1 and ENSv2, the same lookup works regardless of protocol version. See [Connect](/docs/integrate/unigraph/examples) for setup.
1814

19-
:::tip[Searching vs. displaying]
20-
A `canonical_name` can be very long, but it's the full, correct name — always **select and display `canonical_name`**. When you need to **search** by prefix (`ILIKE 'vit%'`, case-insensitive to match the Omnigraph `starts_with` filter), match against the materialized `__canonical_name_prefix` column (the first 64 code points of `canonical_name`, backed by a GIN trigram index) so the `ILIKE` filter is index-backed:
21-
22-
```sql
23-
SELECT id, type, canonical_name, canonical_node, owner_id
24-
FROM ensindexer_0.domains
25-
WHERE __canonical_name_prefix ILIKE 'vit%'
26-
ORDER BY __canonical_name_prefix
27-
LIMIT 10;
28-
```
29-
30-
The `SELECT` still returns `canonical_name`; only the `ILIKE` / `ORDER BY` use the prefix. The GIN trigram index backs the `ILIKE` filter; the `ORDER BY` then sorts the matched set (cheap under a small `LIMIT`) — scope the query by `registry_id` to use the `(registry_id, __canonical_name_prefix, id)` btree for fully index-backed ordering. For exact matches, use `canonical_name` directly (`canonical_name = 'vitalik.eth'`).
31-
:::
32-
33-
:::note[Canonical fields]
34-
Canonical fields are populated on every Domain reachable from the canonical root, across both ENSv1 and ENSv2 — query them uniformly without branching by `type`. In SQL, these columns are `canonical_name`, `canonical_path`, `canonical_node`, and `canonical_depth`; in `ensdb-sdk`, the corresponding fields are `canonicalName`, `canonicalPath`, `canonicalNode`, and `canonicalDepth`.
35-
:::
36-
3715
<UnigraphStaticExample
3816
sql={exampleDomainByName.sql}
3917
ensDbSdk={exampleDomainByName.sdk}

docs/ensnode.io/src/content/docs/docs/integrate/unigraph/examples/domain-events.mdx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@ import EnsDbWriterSchemaIntro from "@components/molecules/EnsDbWriterSchemaIntro
1010
import EnsDbReaderIntro from "@components/molecules/EnsDbReaderIntro.astro";
1111
import { exampleDomainEvents } from "@data/unigraph-examples/domain-events";
1212

13-
:::caution[`unigraph` plugin required]
14-
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration)
15-
:::
13+
Fetch recent events for a Domain by its canonical name. This example joins the `events`, `domain_events`, and `domains` tables to retrieve domain events associated with the `vitalik.eth` name. See [Connect](/docs/integrate/unigraph/examples) for setup.
1614

17-
Fetch recent events for a Domain by its canonical name. This example joins the `events`, `domain_events`, and `domains` tables to retrieve domain events associated with the `wrapnation.eth` name. See [Connect](/docs/integrate/unigraph/examples) for setup.
15+
:::caution[Raw event data]
16+
The `events` table contains low-level onchain event data, which may require additional processing to interpret. For example, the `data` field is a JSON blob containing event-specific information that may need to be parsed based on the event type (see `selector` field) to extract meaningful details about the event.
1817

19-
:::note[Canonical fields]
20-
Canonical fields are populated on every Domain reachable from the canonical root, across both ENSv1 and ENSv2 — query them uniformly without branching by `type`. In SQL, these columns are `canonical_name`, `canonical_path`, `canonical_node`, and `canonical_depth`; in `ensdb-sdk`, the corresponding fields are `canonicalName`, `canonicalPath`, `canonicalNode`, and `canonicalDepth`.
18+
In the future, the ENS Unigraph may include data referencing higher-level event types (e.g., `DomainRegistered`, `DomainTransferred`, etc.) to simplify querying for common events without needing to parse raw event data. For now, use `selector`, `topics`, and `data` fields in the `events` table to filter and interpret events based on your app's needs.
2119
:::
2220

2321
<UnigraphStaticExample

docs/ensnode.io/src/content/docs/docs/integrate/unigraph/examples/domains-fuzzy-search-by-name.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import EnsDbWriterSchemaIntro from "@components/molecules/EnsDbWriterSchemaIntro
1010
import EnsDbReaderIntro from "@components/molecules/EnsDbReaderIntro.astro";
1111
import { exampleDomainsFuzzySearchByName } from "@data/unigraph-examples/domains-fuzzy-search-by-name";
1212

13-
:::caution[`unigraph` plugin required]
14-
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration)
15-
:::
16-
1713
Fetch Domains with names similar to a query string, ranked by similarity. This example uses PostgreSQL's `pg_trgm` extension, which provides the `%` operator for fuzzy matching and the `similarity()` function for ranking results. See [Connect](/docs/integrate/unigraph/examples) for setup.
1814

1915
<UnigraphStaticExample

docs/ensnode.io/src/content/docs/docs/integrate/unigraph/examples/expiring-registrations.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import EnsDbWriterSchemaIntro from "@components/molecules/EnsDbWriterSchemaIntro
1010
import EnsDbReaderIntro from "@components/molecules/EnsDbReaderIntro.astro";
1111
import { exampleExpiringRegistrations } from "@data/unigraph-examples/expiring-registrations";
1212

13-
:::caution[`unigraph` plugin required]
14-
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration)
15-
:::
16-
1713
Fetch Domains with registrations expiring within a certain timeframe. This example uses a simple `WHERE` clause to filter for Domains with `expiry` between the current time and a specified future time (e.g., 3 days from now). See [Connect](/docs/integrate/unigraph/examples) for setup.
1814

1915
<UnigraphStaticExample

docs/ensnode.io/src/content/docs/docs/integrate/unigraph/examples/index.mdx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ sidebar:
55
label: Overview
66
---
77

8-
import { LinkCard, Aside } from "@astrojs/starlight/components";
8+
import { CardGrid, LinkCard, Aside } from "@astrojs/starlight/components";
99
import Pill from "@components/atoms/Pill.astro";
1010
import SqlResultTable from "@components/molecules/SqlResultTable.astro";
11+
import UnigraphIntro from "@components/molecules/EnsUnigraphIntro.astro";
1112
import UnigraphExampleWrapper from "@components/molecules/unigraph-static-example/UnigraphExampleWrapper.astro";
1213
import UnigraphExampleSQLTab from "@components/molecules/unigraph-static-example/UnigraphExampleSQLTab.astro";
1314
import UnigraphExampleEnsDbSdkTab from "@components/molecules/unigraph-static-example/UnigraphExampleEnsDbSdkTab.astro";
@@ -46,15 +47,11 @@ export const bashSdkInstallSnippet = `npm install @ensnode/ensdb-sdk`;
4647

4748
These examples show the same queries two ways: in **raw SQL** (any PostgreSQL client, any language) and with the typed **[`@ensnode/ensdb-sdk`](https://www.npmjs.com/package/@ensnode/ensdb-sdk)** for TypeScript projects.
4849

49-
SQL access requires connecting to your own [self-hosted](/docs/self-host) ENSDb instance.
50-
51-
:::caution[`unigraph` plugin required]
52-
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration)
53-
:::
50+
<UnigraphIntro />
5451

5552
## Connect
5653

57-
The Unigraph lives in [ENSDb](/docs/services/ensdb), a PostgreSQL database. Each [ENSDb Writer](/docs/services/ensdb/concepts/glossary#ensdb-writer) instance writes to its own **ENSDb Writer Schema** (e.g. `ensindexer_0`). Each instance of [ENSDb Metadata Writer](/docs/services/ensdb/concepts/glossary#ensdb-metadata-writer) writes to a shared operational [metadata table](/docs/services/ensdb/concepts/glossary#ensnode-metadata-table) in the [`ensnode` schema](/docs/services/ensdb/concepts/glossary#ensnode-schema).
54+
The ENS Unigraph lives in [ENSDb](/docs/services/ensdb), a PostgreSQL database. Each [ENSDb Writer](/docs/services/ensdb/concepts/glossary#ensdb-writer) instance writes to its own **ENSDb Writer Schema** (e.g. `ensindexer_0`). Each instance of [ENSDb Metadata Writer](/docs/services/ensdb/concepts/glossary#ensdb-metadata-writer) writes to a shared operational [metadata table](/docs/services/ensdb/concepts/glossary#ensnode-metadata-table) in the [`ensnode` schema](/docs/services/ensdb/concepts/glossary#ensnode-schema).
5855

5956
<UnigraphExampleWrapper>
6057
<UnigraphExampleSQLTab>
@@ -103,6 +100,13 @@ The Unigraph lives in [ENSDb](/docs/services/ensdb), a PostgreSQL database. Each
103100

104101
## Examples
105102

106-
<LinkCard title="Domain by Name" href="/docs/integrate/unigraph/examples/domain-by-name" />
107-
<LinkCard title="Account Domains" href="/docs/integrate/unigraph/examples/account-domains" />
108-
<LinkCard title="Indexing Status" href="/docs/integrate/unigraph/examples/indexing-status" />
103+
Here are some example queries to get you started. Each example is available in raw SQL and with the `ensdb-sdk` for TypeScript projects.
104+
105+
See each example's page for details on the query and how to run it against your ENSDb instance.
106+
107+
<CardGrid>
108+
<LinkCard title="Domain by Name" href="/docs/integrate/unigraph/examples/domain-by-name" />
109+
<LinkCard title="Subdomains" href="/docs/integrate/unigraph/examples/subdomains" />
110+
<LinkCard title="Expiring Registrations" href="/docs/integrate/unigraph/examples/expiring-registrations" />
111+
<LinkCard title="Indexing Status" href="/docs/integrate/unigraph/examples/indexing-status" />
112+
</CardGrid>

docs/ensnode.io/src/content/docs/docs/integrate/unigraph/examples/indexing-status.mdx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import EnsNodeSchemaIntro from "@components/molecules/EnsNodeSchemaIntro.astro";
1010
import EnsDbReaderIntro from "@components/molecules/EnsDbReaderIntro.astro";
1111
import { exampleIndexingStatus } from "@data/unigraph-examples/indexing-status";
1212

13-
:::caution[`unigraph` plugin required]
14-
Performing SQL queries on the ENS Unigraph requires that you have the `unigraph` plugin activated in your ENSNode instance. [Learn more](/docs/services/ensindexer/usage/configuration)
15-
:::
16-
1713
Read the indexing status snapshot for an ENSDb Writer instance from the shared `ensnode.metadata` table. See [Connect](/docs/integrate/unigraph/examples) for setup.
1814

1915
<UnigraphStaticExample

0 commit comments

Comments
 (0)