Skip to content

Unigraph Schema Reference

This is the canonical reference for the Unigraph schema — the unified, polymorphic data model that the unigraph plugin materializes into ENSDb. It models ENSv1 and ENSv2 with shared, polymorphic entities (Domains, Registries, Registrations, Renewals, Resolvers), so the same query shape works across both protocol versions.

Each ENSIndexer instance owns a dedicated database schema (e.g. ensindexer_0) holding all of its indexed Unigraph data, fully isolated from other instances.

Defined in unigraph.schema.ts.

RegistryType

Value
ENSv1Registry
ENSv1VirtualRegistry
ENSv2Registry

DomainType

Value
ENSv1Domain
ENSv2Domain

RegistrationType

Value
NameWrapper
BaseRegistrar
ThreeDNS
ENSv2RegistryRegistration
ENSv2RegistryReservation
ColumnTypeNullableDescription
idtextnoPonder’s event ID. Primary key.
chain_idbigintnoChain the event was emitted on.
block_numbernumeric(78)noBlock number.
block_hashtextnoBlock hash.
timestampnumeric(78)noBlock timestamp.
transaction_hashtextnoTransaction hash.
transaction_indexintegernoIndex of the transaction within the block.
fromtextnoTransaction sender address (tx.from). Never HCA-aware — always the EOA/relayer that submitted the transaction. Use sender for the HCA-aware actor.
sendertextnoThe HCA account address if used, otherwise Transaction.from. For ENSv2 events that emit an explicit sender / owner / account argument, this is set from that argument. For all other events (and all ENSv1 events), this falls back to from (i.e. tx.from).
totextyesTransaction recipient address. A null value means this was a contract-deployment transaction.
addresstextnoAddress of the contract that emitted the log.
log_indexintegernoIndex of the log within the transaction.
selectortextnoEvent topic[0] (the event signature hash).
topicstext[]noAll log topics.
datatextnoLog data.

Indexes: selector, from, sender, timestamp.

Join table linking a domains record to its associated events.

ColumnTypeNullable
domain_idtextno
event_idtextno

Primary key: (domain_id, event_id).

Join table linking a resolvers record to its associated events.

ColumnTypeNullable
resolver_idtextno
event_idtextno

Primary key: (resolver_id, event_id).

Join table linking a permissions record to its associated events.

ColumnTypeNullable
permissions_idtextno
event_idtextno

Primary key: (permissions_id, event_id).

Join table linking a permissions_users record to its associated events — i.e. the per-(contract, resource, user) history of role grants, revokes, and bitmap mutations.

ColumnTypeNullable
permissions_user_idtextno
event_idtextno

Primary key: (permissions_user_id, event_id).

ColumnTypeNullableDescription
idtextnoEthereum address. Primary key.

Relations: has many registrations (as registrant), has many domains, has many permissions_users.

For ENSv1, each domain that has children implicitly owns a “virtual” Registry (ENSv1VirtualRegistry) whose sole parent is that domain. Children of the parent then point their registry_id at the virtual registry. Concrete ENSv1Registry rows (e.g. the mainnet ENS Registry, the Basenames Registry, the Lineanames Registry) sit at the top. ENSv2 namegraphs are rooted in a single ENSv2Registry RootRegistry.

ColumnTypeNullableDescription
idtextnoSee RegistryId for guarantees. Primary key.
typeRegistryTypenoRegistry type.
chain_idbigintnoChain the registry contract is deployed on.
addresstextnoAddress of the registry contract.
nodetextyesIf this is an ENSv1VirtualRegistry, the namehash of the parent ENSv1 domain that owns it, otherwise null.
canonical_domain_idtextyesThe Registry’s declared Canonical Domain (unidirectional).
canonicalbooleannoWhether this Registry is part of the canonical nametree. This encodes bi-directional agreement between domains.subregistry_id and registries.canonical_domain_id, so traversal of the canonical nametree filtered to domains/registries where canonical=true is safe and doesn’t require edge-authenticating oneself (i.e. don’t need to compare domains.subregistry_id and registries.canonical_domain_id in the query, can just WHERE canonical = true). Default false.
has_childrenbooleannoInternal bookkeeping field. Synthetic monotonic sentinel flipped to true the first time a child Domain is registered under this Registry. Used to optimize canonicality cascades. Default false.

Indexes: (chain_id, address) — non-unique, because multiple rows can share (chain_id, address) across virtual registries.

Relations: has many domains (as parent registry), has many domains (as subregistry), has one permissions via (chain_id, address).

The domains.owner_id for ENSv1 Domains is the materialized effective owner. ENSv1 includes a diverse number of ways to ‘own’ a domain, including the ENSv1 Registry, various Registrars, and the NameWrapper. The ENSv1 indexing logic materializes the effective owner to simplify this aspect of ENS and enable efficient queries against domains.owner_id.

ColumnTypeNullableDescription
idtextnoENSv1DomainId: {ENSv1RegistryId}/{node}. ENSv2DomainId: CAIP-19 asset identifier. Primary key.
typeDomainTypenoENSv1Domain or ENSv2Domain.
registry_idtextnoThe registry this domain belongs to.
subregistry_idtextyesThe registry that manages subdomains of this domain, if any.
token_idnumeric(78)yesENSv2 only: the TokenId within the ENSv2Registry. null for ENSv1 domains.
nodetextyesENSv1 only: the domain’s namehash. null for ENSv2 domains.
label_hashtextnoRepresents a labelHash. References labels.label_hash.
owner_idtextyesIf ENSv1Domain, the materialized effective owner address. If ENSv2Domain, the on-chain owner address (the HCA account address if used).
root_registry_owner_idtextyesENSv1 only: the owner recorded in the root ENSv1 registry. null for ENSv2 domains.
canonicalbooleannoWhether this Domain is part of the canonical nametree. This encodes bi-directional agreement between domains.subregistry_id and registries.canonical_domain_id, so traversal of the canonical nametree filtered to domains/registries where canonical=true is safe and doesn’t require edge-authenticating oneself (i.e. don’t need to compare domains.subregistry_id and registries.canonical_domain_id in the query, can just WHERE canonical = true). Mirrors the parent Registry’s flag. Default false.
canonical_nametextyesMaterialized Canonical Name, NULL iff canonical = false. Maintained by canonicality-db-helpers.ts. Example: "vitalik.eth".
canonical_label_hash_pathtext[]yesMaterialized Canonical LabelHashPath, NULL iff canonical = false. Head-first (root → leaf), i.e. [labelhash("eth"), labelhash("vitalik")] for "vitalik.eth". Maintained by canonicality-db-helpers.ts.
canonical_pathtext[]yesMaterialized Canonical Domain Path, NULL iff canonical = false. Head-first (root → leaf), i.e. ["eth"’s DomainId, "vitalik"’s DomainId] for "vitalik.eth". Maintained by canonicality-db-helpers.ts.
canonical_depthintegeryesMaterialized Canonical Depth, NULL iff canonical = false. The depth of this Domain in the Canonical Nametree, i.e. the number of Labels in its Canonical Name (e.g. "eth" depth 1, "vitalik.eth" depth 2). Maintained by canonicality-db-helpers.ts.
canonical_nodetextyesMaterialized Canonical Node, NULL iff canonical = false. The computed Node (via namehash) of this Domain’s Canonical Name. Maintained by canonicality-db-helpers.ts.

Indexes: type, subregistry_id (partial: non-null only), owner_id, label_hash, (registry_id, label_hash) (composite; leading-column prefix also serves WHERE registry_id = X lookups, so no separate registry_id index is needed), (registry_id, left(canonical_name, 256), id) (composite expression index for registry-scoped WHERE registry_id = X ORDER BY canonical_name LIMIT N — the Domain.subdomains shape; the 256-char prefix bounds the index tuple under btree’s per-tuple max, and NAME-ordered queries must sort by the same left(...) expression for the planner to use this index for ordered scan), canonical_name (hash, exact match — avoids the btree 8191-byte row-size hazard for spam names), canonical_name (GIN trigram for substring / similarity queries), canonical_label_hash_path (GIN containment for cascadeLabelHeal’s canonical_label_hash_path @> ARRAY[lh] lookup), canonical_node (hash, for resolver-record → canonical-domain joins), canonical_depth (btree, for ORDER BY canonical_depth — typeahead and depth-ordered browse).

Relations: belongs to one registries record, belongs to one registries record (as subregistry), has one accounts record (owner), has one accounts record (rootRegistryOwner), has one labels record, has many registrations records.

Internal rainbow table mapping a label_hash to its interpreted label string. Domains reference labels by hash; names are healed at resolution-time.

ColumnTypeNullableDescription
label_hashtextnokeccak256 of the label. Primary key.
interpretedtextnoThe interpreted label string.

Indexes: interpreted (hash index for exact match), interpreted (GIN trigram index for prefix/substring LIKE)

Relations: has many domains.

A registration is keyed by id.

ColumnTypeNullableDescription
idtextnoA key derived from (domain_id, registration_index). Primary key.
domain_idtextnoThe registered domain.
registration_indexintegernoMonotonically increasing index per domain.
typeRegistrationTypenoThe mechanism through which this registration was made.
startnumeric(78)noUnix timestamp of registration start.
expirynumeric(78)yesUnix timestamp of expiry, if applicable.
grace_periodnumeric(78)yesGrace period duration in seconds. BaseRegistrar only.
registrar_chain_idbigintnoChain of the registrar contract.
registrar_addresstextnoAddress of the registrar contract.
registrant_idtextyesAccount that initiated the registration. For ENSv2 Registrations, the protocol-emitted registrant address (the HCA account address if used).
unregistrant_idtextyesAccount that triggered an unregistration, if applicable. For ENSv2 Registrations, the protocol-emitted unregistrant address (the HCA account address if used).
referrertextyesEncoded referrer value emitted at registration time.
fusesintegeryesFuse bitmap. NameWrapper and wrapped BaseRegistrar only.
basenumeric(78)yesBase registration cost in wei. BaseRegistrar and ENSv2Registrar only.
premiumnumeric(78)yesPremium cost in wei above base. BaseRegistrar only.
wrappedbooleannoWhether the registration is currently wrapped by the NameWrapper. Default false.
event_idtextnoThe event that created this registration record.

Indexes: unique on (domain_id, registration_index).

Relations: belongs to one domains record, has one accounts record (registrant), has one accounts record (unregistrant), has many renewals, has one events record.

Tracks the highest registration_index seen for each domain. Used to sequence registrations.

ColumnTypeNullable
domain_idtextno
registration_indexintegerno

Primary key: domain_id.

A renewal is keyed by id and belongs to a specific registration.

ColumnTypeNullableDescription
idtextnoA key derived from (domain_id, registration_index, renewal_index). Primary key.
domain_idtextnoThe renewed domain.
registration_indexintegernoIndex of the parent registration.
renewal_indexintegernoMonotonically increasing index per registration.
durationnumeric(78)noDuration added by this renewal, in seconds.
referrertextyesEncoded referrer value emitted at renewal time.
basenumeric(78)yesBase renewal cost in wei.
premiumnumeric(78)yesPremium cost in wei above base. ENSv1 RegistrarControllers only.
event_idtextnoThe event that created this renewal record.

Indexes: unique on (domain_id, registration_index, renewal_index).

Relations: belongs to one registrations record via (domain_id, registration_index), has one events record via (event_id).

Tracks the highest renewal_index seen for each registration. Used to sequence renewals.

ColumnTypeNullable
domain_idtextno
registration_indexintegerno
renewal_indexintegerno

Primary key: (domain_id, registration_index).

An ENSv2 permissions contract instance.

ColumnTypeNullableDescription
idtextnoPrimary key.
chain_idbigintnoChain the permissions contract is deployed on.
addresstextnoAddress of the permissions contract.

Indexes: unique on (chain_id, address).

Relations: has many permissions_resources, has many permissions_users.

A resource managed by a permissions contract.

ColumnTypeNullableDescription
idtextnoPrimary key.
chain_idbigintnoChain of the parent permissions contract.
addresstextnoAddress of the parent permissions contract.
resourcenumeric(78)noResource identifier (a uint256 token ID or similar).

Indexes: unique on (chain_id, address, resource).

Relations: belongs to one permissions via (chain_id, address).

A user’s role bitmap for a specific resource within a permissions contract.

ColumnTypeNullableDescription
idtextnoPrimary key.
chain_idbigintnoChain of the parent permissions contract.
addresstextnoAddress of the parent permissions contract.
resourcenumeric(78)noResource identifier.
usertextnoThe user/grantee address this Permission is granted to (the HCA account address if used).
rolesnumeric(78)noRoles bitmap for this user on this resource.

Indexes: unique on (chain_id, address, resource, user).

Relations: has one accounts record (user), belongs to one permissions record via (chain_id, address), belongs to one permissions_resource record via (chain_id, address, resource).