diff --git a/docs/ensnode.io/src/content/docs/docs/reference/contributing/prs.mdx b/docs/ensnode.io/src/content/docs/docs/reference/contributing/prs.mdx index 0326301763..97219dac4a 100644 --- a/docs/ensnode.io/src/content/docs/docs/reference/contributing/prs.mdx +++ b/docs/ensnode.io/src/content/docs/docs/reference/contributing/prs.mdx @@ -8,22 +8,23 @@ sidebar: # Pull Request Templates We use pull request templates to help guide contributors through the process of creating a pull request. + - [Lite PR](https://github.com/namehash/ensnode/blob/main/.github/pull_request_template.md) - - *The default PR template for non-significant or low-risk changes.* + - _The default PR template for non-significant or low-risk changes._ - [Substantial PR](https://github.com/namehash/ensnode/blob/main/.github/PULL_REQUEST_TEMPLATE/substantial.md) - - *Template for significant changes or PRs that are complex or higher-risk to review.* + - _Template for significant changes or PRs that are complex or higher-risk to review._ # Standard PR Process 1. Apply the appropriate [Pull Request Template](#pull-request-templates) based on the nature of the changes. 2. Create your PR as a "Draft" first until it is ready for review. 3. Complete all of your work, self-reviews, and PR description updates on the PR. - - If your PR aims to close an issue, [link the PR to the issue through the PR description](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword). + - If your PR aims to close an issue, [link the PR to the issue through the PR description](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword). 4. Complete all checkboxes on the PR description. 5. Convert the PR from "Draft" to "Ready for Review". 6. Wait for automated PR reviews from Greptile and GitHub Copilot. Resolve any of their suggestions as appropriate. - - After applying updates, if you want Greptile to make additional follow-up reviews, add a comment to the PR with the phrase "@greptile". - - GitHub Copilot will automatically re-review each push to the PR. + - After applying updates, if you want Greptile to make additional follow-up reviews, add a comment to the PR with the phrase "@greptile". + - GitHub Copilot will automatically re-review each push to the PR. 7. Send a message to notify the ENSNode team that the PR is ready for manual-review. # Changesets @@ -39,6 +40,7 @@ A "logical change" is a cohesive set of related modifications that share a commo Changesets should not be created robotically for every "logical change" in a PR. We create changesets specifically for the purposes of managing version bumps and autogenerated release notes. Only the following types of "logical changes" should be documented in a changeset: + - Changes that should bump versions. - Changes that are relevant to communicate to the ENS ecosystem in release notes. @@ -49,6 +51,7 @@ If a PR does not introduce any "logical changes" meeting the above criteria then Ensure that each "logical change" described in a changeset is appropriately mapped to the relevant package/app. Example: assume changes A, B, and C in a PR pass the criteria for changesets, with the following applicability: change A applies only to package/app X, change B applies to both package/app X and package/app Y, and change C applies only to package/app Y. This means multiple distinct changesets should be created as follows: + - Changeset 1: Documenting change A for package/app X - Changeset 2: Documenting change B for package/app X and package/app Y - Changeset 3: Documenting change C for package/app Y @@ -62,15 +65,16 @@ When you open a PR you should include relevant changeset files that document you 1. Run `pnpm changeset` in the monorepo root. 2. Select the packages/apps that the "logical change" affects using the interactive prompt. 3. Choose the version bump type: - - *NOTE: At this time, we are NOT using [semantic versioning](https://semver.org/). Breaking changes are classified as "minor" rather than "major".* - - "major" - currently reserved only for specially approved cases - - "minor" - breaking changes (note: ENSNode doesn't follow standard semver where breaking = major) - - "patch" - non-breaking changes + - _NOTE: At this time, we are NOT using [semantic versioning](https://semver.org/). Breaking changes are classified as "minor" rather than "major"._ + - "major" - currently reserved only for specially approved cases + - "minor" - breaking changes (note: ENSNode doesn't follow standard semver where breaking = major) + - "patch" - non-breaking changes 4. Write a clear description of the "logical change" - this will appear in the next autogenerated release notes. The `changesets/bot` will automatically comment on your PR to either remind you to add a changeset or confirm the version changes that will happen when your PR is merged. **Important notes:** + - Changesets should optimize for the narrative of the next [autogenerated release notes](https://github.com/namehash/ensnode/releases). Optimize for how the resulting release notes will read to a developer in the ENS Ecosystem. Communicate all ideas with a positive frame by emphasizing benefits to users, describing fixes in terms of what now works, and using active, solution‑oriented language rather than focusing on past problems. - All apps and packages in the ENSNode monorepo use "fixed" versioning - they all share the same version number regardless of which app or package triggered the version bump. - An exception to the above "fixed" versioning is the "fallback-ensapi" app. This is a Lambda containing logic specific to NameHash deployments of ENSNode and is therefore versioned independently. diff --git a/docs/ensnode.io/src/content/docs/docs/reference/terminology.mdx b/docs/ensnode.io/src/content/docs/docs/reference/terminology.mdx index 6dae368d28..d272eb8e78 100644 --- a/docs/ensnode.io/src/content/docs/docs/reference/terminology.mdx +++ b/docs/ensnode.io/src/content/docs/docs/reference/terminology.mdx @@ -16,15 +16,15 @@ A **Subregistry** is any data structure outside of the [Registry](https://docs.e Some specific implementations of subregistries include: -* The [BaseRegistrar](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ethregistrar/BaseRegistrarImplementation.sol) that holds supplemental state for direct subnames of .eth. This includes state for ERC721 NFTs and expiry times. -* The [NameWrapper](https://docs.ens.domains/terminology#name-wrapper), which serves as a subregistry for the entire ENS root (all ENS names). This includes state for ERC1155 NFTs, expiry times, and fuses. - * Note how direct subnames of .eth are an example of multiple subregistries potentially holding supplemental state for a name outside the Registry. -* The contracts on Base that manage supplemental state for direct subnames of [base.eth](https://www.base.org/names). -* The contracts on Linea that manage supplemental state for direct subnames of [linea.eth](https://names.linea.build/). -* The contracts on Base / Optimism that manage supplemental state for DNS names managed by [3DNS](https://3dns.box/). -* The offchain databases that manage supplemental state for direct subnames of [uni.eth](https://blog.uniswap.org/introducing-uni-eth-your-unique-web3-username). -* The offchain databases that manage supplemental state for direct subnames of [cb.id](https://help.coinbase.com/en/wallet/managing-account/coinbase-ens-support). -* DNS nameservers for (essentially) all DNS names. Since ENS is a superset of DNS, (essentially) any DNS name is an ENS name. Therefore, whenever supplemental state associated with a DNS name is updated in a DNS nameserver, a subregistry is being updated. +- The [BaseRegistrar](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ethregistrar/BaseRegistrarImplementation.sol) that holds supplemental state for direct subnames of .eth. This includes state for ERC721 NFTs and expiry times. +- The [NameWrapper](https://docs.ens.domains/terminology#name-wrapper), which serves as a subregistry for the entire ENS root (all ENS names). This includes state for ERC1155 NFTs, expiry times, and fuses. + - Note how direct subnames of .eth are an example of multiple subregistries potentially holding supplemental state for a name outside the Registry. +- The contracts on Base that manage supplemental state for direct subnames of [base.eth](https://www.base.org/names). +- The contracts on Linea that manage supplemental state for direct subnames of [linea.eth](https://names.linea.build/). +- The contracts on Base / Optimism that manage supplemental state for DNS names managed by [3DNS](https://3dns.box/). +- The offchain databases that manage supplemental state for direct subnames of [uni.eth](https://blog.uniswap.org/introducing-uni-eth-your-unique-web3-username). +- The offchain databases that manage supplemental state for direct subnames of [cb.id](https://help.coinbase.com/en/wallet/managing-account/coinbase-ens-support). +- DNS nameservers for (essentially) all DNS names. Since ENS is a superset of DNS, (essentially) any DNS name is an ENS name. Therefore, whenever supplemental state associated with a DNS name is updated in a DNS nameserver, a subregistry is being updated. ### Subregistrar @@ -32,15 +32,16 @@ A **Subregistrar** is any system that is a [Registrar](https://docs.ens.domains/ This definition expands the definition of Registrar to include cases such as: -* The [ETHRegistrarController](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ethregistrar/ETHRegistrarController.sol) that writes to BaseRegistrar (the owner of the "eth" TLD). Note how the definition of "Registrar" in the official ENS glossary only includes contracts that are pointed to by the owner field of the Registry. Therefore, the BaseRegistrar is a Registrar (and a Subregistry), while the ETHRegistrarController is a Subregistrar. -* The contracts on Base that write to the Subregistry for direct subnames of base.eth. These contracts live on Base, therefore they cannot meet the definition of Registrar because they can't be set as the owner in the Registry on Ethereum mainnet. -* The offchain systems that write to the offchain databases associated with direct subnames of uni.eth and cb.id. -* Any NFT marketplace that supports the exchange of an NFT representing ownership of an ENS name. Each time a NFT is exchanged, state about that NFT must be updated within a related subregistry. Therefore the marketplace enabling that trade is a Subregistrar. -* Any DNS registrar, as ENS is a superset of DNS. +- The [ETHRegistrarController](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ethregistrar/ETHRegistrarController.sol) that writes to BaseRegistrar (the owner of the "eth" TLD). Note how the definition of "Registrar" in the official ENS glossary only includes contracts that are pointed to by the owner field of the Registry. Therefore, the BaseRegistrar is a Registrar (and a Subregistry), while the ETHRegistrarController is a Subregistrar. +- The contracts on Base that write to the Subregistry for direct subnames of base.eth. These contracts live on Base, therefore they cannot meet the definition of Registrar because they can't be set as the owner in the Registry on Ethereum mainnet. +- The offchain systems that write to the offchain databases associated with direct subnames of uni.eth and cb.id. +- Any NFT marketplace that supports the exchange of an NFT representing ownership of an ENS name. Each time a NFT is exchanged, state about that NFT must be updated within a related subregistry. Therefore the marketplace enabling that trade is a Subregistrar. +- Any DNS registrar, as ENS is a superset of DNS. ### Shadow Registry A **Shadow Registry** is a Subregistry meeting ALL of the following constraints: + 1. Not the Registry; 2. Implemented as a smart contract exposing the same interface as the Registry; 3. Used as part of the source of truth for a CCIP-Read Gateway Server for ENSIP-10 (wildcard resolution) powered subnames. @@ -94,7 +95,7 @@ In this terminology reference, we say that the **LabelHash** of a **Label** is t That is, `0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc` is the **LabelHash** of `vitalik`, which is the result of calling the **`labelhash` function** like so: ```ts -import { labelhashInterpretedLabel, asInterpretedLabel } from 'enssdk'; +import { labelhashInterpretedLabel, asInterpretedLabel } from "enssdk"; const labelHash = labelhashInterpretedLabel(asInterpretedLabel("vitalik")); ``` @@ -161,31 +162,39 @@ ENSNode's indexing pipeline transforms raw blockchain data into user-friendly fo When processing ENS data, it's important to distinguish between the literal data read from blockchain sources and the interpreted form used in applications: ### Literal Label + A **Literal Label** is the raw label data as it exists in onchain storage or event data, before any processing or interpretation. This represents the exact bytes or string as emitted by contracts. ### Interpreted Label + An **Interpreted Label** is a **Label** that is either: + - (if normalized): a normalized **Literal Label** - (if not normalized or **Unknown**): an **Encoded LabelHash** Apps building on ENSNode should take special note to align their implementation of **ENS Normalize** with the **ENS Normalize** used by the ENSNode they use. The **ENS Normalize** version used by each ENSNode can be referenced in the ENSIndexer Config API. ### Literal Name + A **Literal Name** is a Name exclusively composed of 0 or more **Literal Labels**. ### Interpreted Name + An **Interpreted Name** is a Name exclusively composed of 0 or more **Interpreted Labels**. ## Subgraph Indexability & Label/Name Interpretation ### Subgraph-Indexable Labels / Subgraph-Unindexable Labels + The legacy ENS Subgraph specifies that **Unknown Labels** and labels containing certain UTF-8 characters are "invalid". We refer to this concept as `subgraph-indexable` and `subgraph-unindexable`. A **Literal Label** is `subgraph-unindexable` if it: - - is an **Unknown Label**, or - - contains any of the following prohibited UTF-8 characters. + +- is an **Unknown Label**, or +- contains any of the following prohibited UTF-8 characters. The `subgraph-unindexable` UTF-8 characters are: + 1. `\0` (null byte) - PostgreSQL does not allow storing this character in text fields 2. `.` (period) - Conflicts with ENS label separator logic 3. `[` (left square bracket) - Conflicts with "unknown label" representations @@ -194,9 +203,12 @@ The `subgraph-unindexable` UTF-8 characters are: A `subgraph-indexable` **Literal Label** is a **Known Literal Label** that does NOT contain any of the prohibited UTF-8 characters. ### Subgraph Interpreted Label + A **Subgraph Interpreted Label** is a **Label** that is either: + - (if `subgraph-indexable`): a **Literal Label** guaranteed to not contain any of the `subgraph-unindexable` UTF-8 characters, or - (if `subgraph-unindexable`): an Encoded LabelHash. ### Subgraph Interpreted Name + A **Subgraph Interpreted Name** is a name exclusively composed of 0 or more **Subgraph Interpreted Labels**. diff --git a/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx b/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx index 6e483a45d4..bb0572e838 100644 --- a/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx +++ b/docs/ensnode.io/src/content/docs/docs/services/ensdb/concepts/database-schemas.mdx @@ -4,11 +4,10 @@ description: Detailed explanation of all database schemas that make up ENSDb. sidebar: label: Database Schemas order: 4 -keywords: [ensdb, database schemas, ponder schema, ensnode schema, ensindexer schema] +keywords: + [ensdb, database schemas, ponder schema, ensnode schema, ensindexer schema] --- - - This page explains the different database schemas in ENSDb, including the ENSNode Schema, and the modular ENSIndexer Schema. ## Overview of ENSDb Schemas @@ -27,18 +26,18 @@ ENSDb supports multiple ENSIndexer instances coexisting in the same database, ea Possible key-value pairs are defined by the `EnsNodeMetadata` union type. As of now, it includes: `EnsNodeMetadataIndexingMetadataContext`. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `ens_indexer_schema_name` | `text` | no | References the name of the ENSIndexer Schema that the metadata record belongs to. This allows multi-tenancy where multiple ENSIndexer instances can write to the same ENSNode Metadata table. | -| `key` | `text` | no | Allowed keys: `indexing_metadata_context`. | -| `value` | `jsonb` | no | Guaranteed to be a serialized representation of a JSON object. | +| Column | Type | Nullable | Description | +| ------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ens_indexer_schema_name` | `text` | no | References the name of the ENSIndexer Schema that the metadata record belongs to. This allows multi-tenancy where multiple ENSIndexer instances can write to the same ENSNode Metadata table. | +| `key` | `text` | no | Allowed keys: `indexing_metadata_context`. | +| `value` | `jsonb` | no | Guaranteed to be a serialized representation of a JSON object. | **Primary key:** `(ens_indexer_schema_name, key)` — ensures that there is only one record for each key per ENSIndexer instance. #### Known keys -| Key | TypeScript type | Description | -|-----|-----------------|-------------| +| Key | TypeScript type | Description | +| --------------------------- | ------------------------------------ | ------------------------------------------------------------------ | | `indexing_metadata_context` | `IndexingMetadataContextInitialized` | Stores indexing status and stack info for the ENSIndexer instance. | --- @@ -55,7 +54,6 @@ The ENSIndexer Schema is modular, composed of multiple logical database sub-sche Defined in [`ensv2.schema.ts`](https://github.com/namehash/ensnode/blob/main/packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts). - :::note[Design principles] While the initial approach was a highly materialized view of the ENS protocol, abstracting away as many on-chain details as possible, in practice—due to the sheer complexity of the protocol at @@ -104,8 +102,8 @@ pointers agree (`registries.canonical_domain_id = domains.id` ↔ `domains.subre Note also that the Protocol Acceleration plugin is a hard requirement for the ENSv2 plugin. This allows us to rely on the shared logic for indexing: - a) ENSv1RegistryOld -> ENSv1Registry migration status - b) Domain-Resolver Relations for both ENSv1 and ENSv2 Domains +a) ENSv1RegistryOld -> ENSv1Registry migration status +b) Domain-Resolver Relations for both ENSv1 and ENSv2 Domains As such, none of that information is present in this `ensv2.schema.ts` file. In general, entities are keyed by a nominally-typed `id` that uniquely references them. This @@ -125,53 +123,52 @@ A Registration references the event that initiated the Registration. A Renewal, the Event responsible for its existence. ::: - #### Enums **`RegistryType`** -| Value | -|-------| -| `ENSv1Registry` | +| Value | +| ---------------------- | +| `ENSv1Registry` | | `ENSv1VirtualRegistry` | -| `ENSv2Registry` | +| `ENSv2Registry` | **`DomainType`** -| Value | -|-------| +| Value | +| ------------- | | `ENSv1Domain` | | `ENSv2Domain` | **`RegistrationType`** -| Value | -|-------| -| `NameWrapper` | -| `BaseRegistrar` | -| `ThreeDNS` | +| Value | +| --------------------------- | +| `NameWrapper` | +| `BaseRegistrar` | +| `ThreeDNS` | | `ENSv2RegistryRegistration` | -| `ENSv2RegistryReservation` | +| `ENSv2RegistryReservation` | #### `events` -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Ponder's event ID. Primary key. | -| `chain_id` | `bigint` | no | Chain the event was emitted on. | -| `block_number` | `numeric(78)` | no | Block number. | -| `block_hash` | `text` | no | Block hash. | -| `timestamp` | `numeric(78)` | no | Block timestamp. | -| `transaction_hash` | `text` | no | Transaction hash. | -| `transaction_index` | `integer` | no | Index of the transaction within the block. | -| `from` | `text` | no | Transaction sender address (`tx.from`). Never HCA-aware — always the EOA/relayer that submitted the transaction. Use `sender` for the HCA-aware actor. | -| `sender` | `text` | no | The 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`). | -| `to` | `text` | yes | Transaction recipient address. A `null` value means this was a contract-deployment transaction. | -| `address` | `text` | no | Address of the contract that emitted the log. | -| `log_index` | `integer` | no | Index of the log within the transaction. | -| `selector` | `text` | no | Event topic[0] (the event signature hash). | -| `topics` | `text[]` | no | All log topics. | -| `data` | `text` | no | Log data. | +| Column | Type | Nullable | Description | +| ------------------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | Ponder's event ID. Primary key. | +| `chain_id` | `bigint` | no | Chain the event was emitted on. | +| `block_number` | `numeric(78)` | no | Block number. | +| `block_hash` | `text` | no | Block hash. | +| `timestamp` | `numeric(78)` | no | Block timestamp. | +| `transaction_hash` | `text` | no | Transaction hash. | +| `transaction_index` | `integer` | no | Index of the transaction within the block. | +| `from` | `text` | no | Transaction sender address (`tx.from`). Never HCA-aware — always the EOA/relayer that submitted the transaction. Use `sender` for the HCA-aware actor. | +| `sender` | `text` | no | The 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`). | +| `to` | `text` | yes | Transaction recipient address. A `null` value means this was a contract-deployment transaction. | +| `address` | `text` | no | Address of the contract that emitted the log. | +| `log_index` | `integer` | no | Index of the log within the transaction. | +| `selector` | `text` | no | Event topic[0] (the event signature hash). | +| `topics` | `text[]` | no | All log topics. | +| `data` | `text` | no | Log data. | **Indexes:** `selector`, `from`, `sender`, `timestamp`. @@ -179,10 +176,10 @@ the Event responsible for its existence. Join table linking a `domains` record to its associated `events`. -| Column | Type | Nullable | -|--------|------|----------| -| `domain_id` | `text` | no | -| `event_id` | `text` | no | +| Column | Type | Nullable | +| ----------- | ------ | -------- | +| `domain_id` | `text` | no | +| `event_id` | `text` | no | **Primary key:** `(domain_id, event_id)`. @@ -190,10 +187,10 @@ Join table linking a `domains` record to its associated `events`. Join table linking a `resolvers` record to its associated `events`. -| Column | Type | Nullable | -|--------|------|----------| -| `resolver_id` | `text` | no | -| `event_id` | `text` | no | +| Column | Type | Nullable | +| ------------- | ------ | -------- | +| `resolver_id` | `text` | no | +| `event_id` | `text` | no | **Primary key:** `(resolver_id, event_id)`. @@ -201,10 +198,10 @@ Join table linking a `resolvers` record to its associated `events`. Join table linking a `permissions` record to its associated `events`. -| Column | Type | Nullable | -|--------|------|----------| -| `permissions_id` | `text` | no | -| `event_id` | `text` | no | +| Column | Type | Nullable | +| ---------------- | ------ | -------- | +| `permissions_id` | `text` | no | +| `event_id` | `text` | no | **Primary key:** `(permissions_id, event_id)`. @@ -212,18 +209,18 @@ Join table linking a `permissions` record to its associated `events`. 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. -| Column | Type | Nullable | -|--------|------|----------| -| `permissions_user_id` | `text` | no | -| `event_id` | `text` | no | +| Column | Type | Nullable | +| --------------------- | ------ | -------- | +| `permissions_user_id` | `text` | no | +| `event_id` | `text` | no | **Primary key:** `(permissions_user_id, event_id)`. #### `accounts` -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Ethereum address. Primary key. | +| Column | Type | Nullable | Description | +| ------ | ------ | -------- | ------------------------------ | +| `id` | `text` | no | Ethereum address. Primary key. | **Relations:** has many `registrations` (as registrant), has many `domains`, has many `permissions_users`. @@ -231,16 +228,16 @@ Join table linking a `permissions_users` record to its associated `events` — i 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. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | See `RegistryId` for guarantees. Primary key. | -| `type` | `RegistryType` | no | Registry type. | -| `chain_id` | `bigint` | no | Chain the registry contract is deployed on. | -| `address` | `text` | no | Address of the registry contract. | -| `node` | `text` | yes | If this is an `ENSv1VirtualRegistry`, the namehash of the parent ENSv1 domain that owns it, otherwise `null`. | -| `canonical_domain_id` | `text` | yes | The Registry's declared Canonical Domain (unidirectional). | -| `canonical` | `boolean` | no | Whether 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_children` | `boolean` | no | Internal 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`. | +| Column | Type | Nullable | Description | +| --------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | See `RegistryId` for guarantees. Primary key. | +| `type` | `RegistryType` | no | Registry type. | +| `chain_id` | `bigint` | no | Chain the registry contract is deployed on. | +| `address` | `text` | no | Address of the registry contract. | +| `node` | `text` | yes | If this is an `ENSv1VirtualRegistry`, the namehash of the parent ENSv1 domain that owns it, otherwise `null`. | +| `canonical_domain_id` | `text` | yes | The Registry's declared Canonical Domain (unidirectional). | +| `canonical` | `boolean` | no | Whether 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_children` | `boolean` | no | Internal 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. @@ -250,18 +247,22 @@ For ENSv1, each domain that has children implicitly owns a "virtual" Registry (` 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`. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | ENSv1DomainId: `{ENSv1RegistryId}/{node}`. ENSv2DomainId: CAIP-19 asset identifier. Primary key. | -| `type` | `DomainType` | no | `ENSv1Domain` or `ENSv2Domain`. | -| `registry_id` | `text` | no | The registry this domain belongs to. | -| `subregistry_id` | `text` | yes | The registry that manages subdomains of this domain, if any. | -| `token_id` | `numeric(78)` | yes | ENSv2 only: the TokenId within the ENSv2Registry. `null` for ENSv1 domains. | -| `node` | `text` | yes | ENSv1 only: the domain's namehash. `null` for ENSv2 domains. | -| `label_hash` | `text` | no | Represents a labelHash. References `labels.label_hash`. | -| `owner_id` | `text` | yes | If `ENSv1Domain`, the materialized effective owner address. If `ENSv2Domain`, the on-chain owner address (the HCA account address if used). | -| `root_registry_owner_id` | `text` | yes | ENSv1 only: the owner recorded in the root ENSv1 registry. `null` for ENSv2 domains. | -| `canonical` | `boolean` | no | Whether 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`. | +:::note +Domain-Resolver relations are tracked via the Protocol Acceleration plugin, not stored on the domain row. The parent domain is derived via `registry_canonical_domains`, not stored on the domain row. +::: + +| Column | Type | Nullable | Description | +| ------------------------ | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | ENSv1DomainId: `{ENSv1RegistryId}/{node}`. ENSv2DomainId: CAIP-19 asset identifier. Primary key. | +| `type` | `DomainType` | no | `ENSv1Domain` or `ENSv2Domain`. | +| `registry_id` | `text` | no | The registry this domain belongs to. | +| `subregistry_id` | `text` | yes | The registry that manages subdomains of this domain, if any. | +| `token_id` | `numeric(78)` | yes | ENSv2 only: the TokenId within the ENSv2Registry. `null` for ENSv1 domains. | +| `node` | `text` | yes | ENSv1 only: the domain's namehash. `null` for ENSv2 domains. | +| `label_hash` | `text` | no | Represents a labelHash. References `labels.label_hash`. | +| `owner_id` | `text` | yes | If `ENSv1Domain`, the materialized effective owner address. If `ENSv2Domain`, the on-chain owner address (the HCA account address if used). | +| `root_registry_owner_id` | `text` | yes | ENSv1 only: the owner recorded in the root ENSv1 registry. `null` for ENSv2 domains. | +| `canonical` | `boolean` | no | Whether 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`. | **Indexes:** `type`, `registry_id`, `subregistry_id` (partial: non-null only), `owner_id`, `label_hash`. @@ -271,10 +272,10 @@ The `domains.owner_id` for ENSv1 Domains is the materialized effective owner. EN Internal rainbow table mapping a `label_hash` to its interpreted label string. Domains reference labels by hash; names are healed at resolution-time. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `label_hash` | `text` | no | `keccak256` of the label. Primary key. | -| `interpreted` | `text` | no | The interpreted label string. | +| Column | Type | Nullable | Description | +| -------------- | ------ | -------- | -------------------------------------- | +| `label_hash` | `text` | no | `keccak256` of the label. Primary key. | +| `interpreted` | `text` | no | The interpreted label string. | **Indexes:** `interpreted` (hash index for exact match), `interpreted` (GIN trigram index for prefix/substring `LIKE`) @@ -284,25 +285,25 @@ Internal rainbow table mapping a `label_hash` to its interpreted label string. D A registration is keyed by `id`. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | A key derived from `(domain_id, registration_index)`. Primary key. | -| `domain_id` | `text` | no | The registered domain. | -| `registration_index` | `integer` | no | Monotonically increasing index per domain. | -| `type` | `RegistrationType` | no | The mechanism through which this registration was made. | -| `start` | `numeric(78)` | no | Unix timestamp of registration start. | -| `expiry` | `numeric(78)` | yes | Unix timestamp of expiry, if applicable. | -| `grace_period` | `numeric(78)` | yes | Grace period duration in seconds. `BaseRegistrar` only. | -| `registrar_chain_id` | `bigint` | no | Chain of the registrar contract. | -| `registrar_address` | `text` | no | Address of the registrar contract. | -| `registrant_id` | `text` | yes | Account that initiated the registration. For ENSv2 Registrations, the protocol-emitted registrant address (the HCA account address if used). | -| `unregistrant_id` | `text` | yes | Account that triggered an unregistration, if applicable. For ENSv2 Registrations, the protocol-emitted unregistrant address (the HCA account address if used). | -| `referrer` | `text` | yes | Encoded referrer value emitted at registration time. | -| `fuses` | `integer` | yes | Fuse bitmap. `NameWrapper` and wrapped `BaseRegistrar` only. | -| `base` | `numeric(78)` | yes | Base registration cost in wei. `BaseRegistrar` and `ENSv2Registrar` only. | -| `premium` | `numeric(78)` | yes | Premium cost in wei above base. `BaseRegistrar` only. | -| `wrapped` | `boolean` | no | Whether the registration is currently wrapped by the NameWrapper. Default `false`. | -| `event_id` | `text` | no | The event that created this registration record. | +| Column | Type | Nullable | Description | +| --------------------- | ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | A key derived from `(domain_id, registration_index)`. Primary key. | +| `domain_id` | `text` | no | The registered domain. | +| `registration_index` | `integer` | no | Monotonically increasing index per domain. | +| `type` | `RegistrationType` | no | The mechanism through which this registration was made. | +| `start` | `numeric(78)` | no | Unix timestamp of registration start. | +| `expiry` | `numeric(78)` | yes | Unix timestamp of expiry, if applicable. | +| `grace_period` | `numeric(78)` | yes | Grace period duration in seconds. `BaseRegistrar` only. | +| `registrar_chain_id` | `bigint` | no | Chain of the registrar contract. | +| `registrar_address` | `text` | no | Address of the registrar contract. | +| `registrant_id` | `text` | yes | Account that initiated the registration. For ENSv2 Registrations, the protocol-emitted registrant address (the HCA account address if used). | +| `unregistrant_id` | `text` | yes | Account that triggered an unregistration, if applicable. For ENSv2 Registrations, the protocol-emitted unregistrant address (the HCA account address if used). | +| `referrer` | `text` | yes | Encoded referrer value emitted at registration time. | +| `fuses` | `integer` | yes | Fuse bitmap. `NameWrapper` and wrapped `BaseRegistrar` only. | +| `base` | `numeric(78)` | yes | Base registration cost in wei. `BaseRegistrar` and `ENSv2Registrar` only. | +| `premium` | `numeric(78)` | yes | Premium cost in wei above base. `BaseRegistrar` only. | +| `wrapped` | `boolean` | no | Whether the registration is currently wrapped by the NameWrapper. Default `false`. | +| `event_id` | `text` | no | The event that created this registration record. | **Indexes:** unique on `(domain_id, registration_index)`. @@ -312,10 +313,10 @@ A registration is keyed by `id`. Tracks the highest `registration_index` seen for each domain. Used to sequence registrations. -| Column | Type | Nullable | -|--------|------|----------| -| `domain_id` | `text` | no | -| `registration_index` | `integer` | no | +| Column | Type | Nullable | +| -------------------- | --------- | -------- | +| `domain_id` | `text` | no | +| `registration_index` | `integer` | no | **Primary key:** `domain_id`. @@ -323,17 +324,17 @@ Tracks the highest `registration_index` seen for each domain. Used to sequence r A renewal is keyed by `id` and belongs to a specific registration. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | A key derived from `(domain_id, registration_index, renewal_index)`. Primary key. | -| `domain_id` | `text` | no | The renewed domain. | -| `registration_index` | `integer` | no | Index of the parent registration. | -| `renewal_index` | `integer` | no | Monotonically increasing index per registration. | -| `duration` | `numeric(78)` | no | Duration added by this renewal, in seconds. | -| `referrer` | `text` | yes | Encoded referrer value emitted at renewal time. | -| `base` | `numeric(78)` | yes | Base renewal cost in wei. | -| `premium` | `numeric(78)` | yes | Premium cost in wei above base. ENSv1 `RegistrarControllers` only. | -| `event_id` | `text` | no | The event that created this renewal record. | +| Column | Type | Nullable | Description | +| -------------------- | ------------- | -------- | ------------------------------------------------------------------------------ | +| `id` | `text` | no | A key derived from `(domain_id, registration_index, renewal_index)`. Primary key. | +| `domain_id` | `text` | no | The renewed domain. | +| `registration_index` | `integer` | no | Index of the parent registration. | +| `renewal_index` | `integer` | no | Monotonically increasing index per registration. | +| `duration` | `numeric(78)` | no | Duration added by this renewal, in seconds. | +| `referrer` | `text` | yes | Encoded referrer value emitted at renewal time. | +| `base` | `numeric(78)` | yes | Base renewal cost in wei. | +| `premium` | `numeric(78)` | yes | Premium cost in wei above base. ENSv1 `RegistrarControllers` only. | +| `event_id` | `text` | no | The event that created this renewal record. | **Indexes:** unique on `(domain_id, registration_index, renewal_index)`. @@ -343,11 +344,11 @@ A renewal is keyed by `id` and belongs to a specific registration. Tracks the highest `renewal_index` seen for each registration. Used to sequence renewals. -| Column | Type | Nullable | -|--------|------|----------| -| `domain_id` | `text` | no | -| `registration_index` | `integer` | no | -| `renewal_index` | `integer` | no | +| Column | Type | Nullable | +| -------------------- | --------- | -------- | +| `domain_id` | `text` | no | +| `registration_index` | `integer` | no | +| `renewal_index` | `integer` | no | **Primary key:** `(domain_id, registration_index)`. @@ -355,11 +356,11 @@ Tracks the highest `renewal_index` seen for each registration. Used to sequence An ENSv2 permissions contract instance. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Primary key. | -| `chain_id` | `bigint` | no | Chain the permissions contract is deployed on. | -| `address` | `text` | no | Address of the permissions contract. | +| Column | Type | Nullable | Description | +| ---------- | --------- | -------- | ---------------------------------------------- | +| `id` | `text` | no | Primary key. | +| `chain_id` | `bigint` | no | Chain the permissions contract is deployed on. | +| `address` | `text` | no | Address of the permissions contract. | **Indexes:** unique on `(chain_id, address)`. @@ -369,12 +370,12 @@ An ENSv2 permissions contract instance. A resource managed by a `permissions` contract. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Primary key. | -| `chain_id` | `bigint` | no | Chain of the parent permissions contract. | -| `address` | `text` | no | Address of the parent permissions contract. | -| `resource` | `numeric(78)` | no | Resource identifier (a `uint256` token ID or similar). | +| Column | Type | Nullable | Description | +| ----------- | ------------- | -------- | ------------------------------------------------------ | +| `id` | `text` | no | Primary key. | +| `chain_id` | `bigint` | no | Chain of the parent permissions contract. | +| `address` | `text` | no | Address of the parent permissions contract. | +| `resource` | `numeric(78)` | no | Resource identifier (a `uint256` token ID or similar). | **Indexes:** unique on `(chain_id, address, resource)`. @@ -384,14 +385,14 @@ A resource managed by a `permissions` contract. A user's role bitmap for a specific resource within a `permissions` contract. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Primary key. | -| `chain_id` | `bigint` | no | Chain of the parent permissions contract. | -| `address` | `text` | no | Address of the parent permissions contract. | -| `resource` | `numeric(78)` | no | Resource identifier. | -| `user` | `text` | no | The user/grantee address this Permission is granted to (the HCA account address if used). | -| `roles` | `numeric(78)` | no | Roles bitmap for this user on this resource. | +| Column | Type | Nullable | Description | +| ----------- | ------------- | -------- | ----------------------------------------------------------------------------------------- | +| `id` | `text` | no | Primary key. | +| `chain_id` | `bigint` | no | Chain of the parent permissions contract. | +| `address` | `text` | no | Address of the parent permissions contract. | +| `resource` | `numeric(78)` | no | Resource identifier. | +| `user` | `text` | no | The user/grantee address this Permission is granted to (the HCA account address if used). | +| `roles` | `numeric(78)` | no | Roles bitmap for this user on this resource. | **Indexes:** unique on `(chain_id, address, resource, user)`. @@ -411,6 +412,7 @@ Tracks an Account's ENSIP-19 Reverse Name Records by CoinType. :::caution[Not a source of truth for Primary Names] This is **not** a cohesive, materialized index of all of an account's Primary Names. It is **only** an index of its ENSIP-19 Reverse Name Records stored by a `StandaloneReverseRegistrar`: + - `default.reverse` - `[coinType].reverse` - **Not** `*.addr.reverse` @@ -418,11 +420,11 @@ This is **not** a cohesive, materialized index of all of an account's Primary Na These records **cannot** be queried directly and used as a source of truth — you **must** perform Forward Resolution to resolve a consistent set of an Account's ENSIP-19 Primary Names. These records are used to power Protocol Acceleration for those ReverseResolvers backed by a `StandaloneReverseRegistrar`. ::: -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `address` | `text` | no | The account address. Part of primary key. | -| `coin_type` | `numeric(78)` | no | ENSIP-19 coin type. Part of primary key. | -| `value` | `text` | no | Represents the ENSIP-19 Reverse Name Record for a given `(address, coin_type)`. Guaranteed to be a non-empty `InterpretedName`. | +| Column | Type | Nullable | Description | +| ----------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `address` | `text` | no | The account address. Part of primary key. | +| `coin_type` | `numeric(78)` | no | ENSIP-19 coin type. Part of primary key. | +| `value` | `text` | no | Represents the ENSIP-19 Reverse Name Record for a given `(address, coin_type)`. Guaranteed to be a non-empty `InterpretedName`. | **Primary key:** `(address, coin_type)`. @@ -432,12 +434,12 @@ Tracks Domain-Resolver relationships. This powers: (1) Domain-Resolver relations It is keyed by `(chain_id, address, domain_id)` to match the on-chain data model of Registry / (shadow)Registry Domain-Resolver relationships. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `chain_id` | `bigint` | no | Keyed by `(chain_id, address, domain_id)`. Part of primary key. | -| `address` | `text` | no | The Registry (`ENSv1Registry` or `ENSv2Registry`)'s AccountId. Part of primary key. | -| `domain_id` | `text` | no | Part of primary key. | -| `resolver` | `text` | no | The Domain's assigned Resolver's address. Always scoped to `chain_id`. | +| Column | Type | Nullable | Description | +| ---------- | --------- | -------- | ----------------------------------------------------------------------------------- | +| `chain_id` | `bigint` | no | Keyed by `(chain_id, address, domain_id)`. Part of primary key. | +| `address` | `text` | no | The Registry (`ENSv1Registry` or `ENSv2Registry`)'s AccountId. Part of primary key. | +| `domain_id` | `text` | no | Part of primary key. | +| `resolver` | `text` | no | The Domain's assigned Resolver's address. Always scoped to `chain_id`. | **Primary key:** `(chain_id, address, domain_id)`. @@ -447,11 +449,11 @@ It is keyed by `(chain_id, address, domain_id)` to match the on-chain data model Represents an individual `IResolver` contract that has emitted at least one event. Note that Resolver contracts can exist on-chain but not emit any events and still function properly, so checks against a Resolver's existence and metadata must be done at runtime. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Keyed by `(chain_id, address)`. Primary key. | -| `chain_id` | `bigint` | no | Chain the resolver contract is deployed on. | -| `address` | `text` | no | Address of the resolver contract. | +| Column | Type | Nullable | Description | +| ---------- | --------- | -------- | -------------------------------------------- | +| `id` | `text` | no | Keyed by `(chain_id, address)`. Primary key. | +| `chain_id` | `bigint` | no | Chain the resolver contract is deployed on. | +| `address` | `text` | no | Address of the resolver contract. | **Indexes:** unique on `(chain_id, address)`. @@ -467,18 +469,18 @@ Represents one `name` resolution record (see ENSIP-3), has many `resolver_addres These record values do **not** allow the caller to confidently resolve records for names without following Forward Resolution according to the ENS protocol. A direct query to the database for a record's value is not ENSIP-10 nor CCIP-Read compliant. ::: -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Keyed by `(chain_id, resolver, node)`. Primary key. | -| `chain_id` | `bigint` | no | Part of the composite key. | -| `address` | `text` | no | Resolver contract address. Part of the composite key. | -| `node` | `text` | no | The name's namehash. Part of the composite key. | -| `name` | `text` | yes | The reverse-resolution (ENSIP-3) `name()` record, used for Reverse Resolution. If present, guaranteed to be a non-empty `InterpretedName`. | -| `contenthash` | `text` | yes | ENSIP-7 contenthash raw bytes, or `null` if not set. | -| `pubkeyX` | `text` | yes | PubkeyResolver X coordinate. Invariant: both `pubkeyX` and `pubkeyY` are either both `null` or both set. | -| `pubkeyY` | `text` | yes | PubkeyResolver Y coordinate. Invariant: both `pubkeyX` and `pubkeyY` are either both `null` or both set. | -| `dnszonehash` | `text` | yes | `IDNSZoneResolver` zone hash, or `null` if not set. | -| `version` | `numeric(78)` | yes | `IVersionableResolver` version. `null` when no `VersionChanged` event has been seen for this `(chain_id, address, node)` — the resolver may not implement `IVersionableResolver`, or simply may never have been version-bumped. Consumers should treat `null` as "unknown" rather than `0`. | +| Column | Type | Nullable | Description | +| ------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `id` | `text` | no | Keyed by `(chain_id, resolver, node)`. Primary key. | +| `chain_id` | `bigint` | no | Part of the composite key. | +| `address` | `text` | no | Resolver contract address. Part of the composite key. | +| `node` | `text` | no | The name's namehash. Part of the composite key. | +| `name` | `text` | yes | The reverse-resolution (ENSIP-3) `name()` record, used for Reverse Resolution. If present, guaranteed to be a non-empty `InterpretedName`. | +| `contenthash` | `text` | yes | ENSIP-7 contenthash raw bytes, or `null` if not set. | +| `pubkeyX` | `text` | yes | PubkeyResolver X coordinate. Invariant: both `pubkeyX` and `pubkeyY` are either both `null` or both set. | +| `pubkeyY` | `text` | yes | PubkeyResolver Y coordinate. Invariant: both `pubkeyX` and `pubkeyY` are either both `null` or both set. | +| `dnszonehash` | `text` | yes | `IDNSZoneResolver` zone hash, or `null` if not set. | +| `version` | `numeric(78)` | yes | `IVersionableResolver` version. `null` when no `VersionChanged` event has been seen for this `(chain_id, address, node)` — the resolver may not implement `IVersionableResolver`, or simply may never have been version-bumped. Consumers should treat `null` as "unknown" rather than `0`. | **Indexes:** unique on `(chain_id, address, node)`. @@ -490,13 +492,13 @@ Tracks address records for a `node` by `coin_type` within a `resolver` on `chain Keyed by `(chain_id, resolver, node, coin_type)`, where the composite key segment `(chain_id, resolver, node)` describes a `resolver_records` entity. A `resolver_address_record` is then additionally keyed by `coin_type`. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `chain_id` | `bigint` | no | Part of primary key. | -| `address` | `text` | no | Resolver contract address. Part of primary key. | -| `node` | `text` | no | Name namehash. Part of primary key. | -| `coin_type` | `numeric(78)` | no | All well-known CoinTypes fit into a JavaScript number but NOT a Postgres `integer`, and must be stored as `bigint`. Part of primary key. | -| `value` | `text` | no | The value of the Address Record specified by `((chain_id, resolver, node), coin_type)`. Interpreted by `interpretAddressRecordValue` — see its implementation for additional context and specific guarantees. | +| Column | Type | Nullable | Description | +| ----------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `chain_id` | `bigint` | no | Part of primary key. | +| `address` | `text` | no | Resolver contract address. Part of primary key. | +| `node` | `text` | no | Name namehash. Part of primary key. | +| `coin_type` | `numeric(78)` | no | All well-known CoinTypes fit into a JavaScript number but NOT a Postgres `integer`, and must be stored as `bigint`. Part of primary key. | +| `value` | `text` | no | The value of the Address Record specified by `((chain_id, resolver, node), coin_type)`. Interpreted by `interpretAddressRecordValue` — see its implementation for additional context and specific guarantees. | **Primary key:** `(chain_id, address, node, coin_type)`. @@ -508,13 +510,13 @@ Tracks text records for a `node` by `key` within a `resolver` on `chain_id`. Keyed by `(chain_id, resolver, node, key)`, where the composite key segment `(chain_id, resolver, node)` describes a `resolver_records` entity. A `resolver_text_record` is then additionally keyed by `key`. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `chain_id` | `bigint` | no | Part of primary key. | -| `address` | `text` | no | Resolver contract address. Part of primary key. | -| `node` | `text` | no | Name namehash. Part of primary key. | -| `key` | `text` | no | Text record key. Part of primary key. | -| `value` | `text` | no | The value of the Text Record specified by `((chain_id, resolver, node), key)`. Interpreted by `interpretTextRecordValue` — see its implementation for additional context and specific guarantees. | +| Column | Type | Nullable | Description | +| ---------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `chain_id` | `bigint` | no | Part of primary key. | +| `address` | `text` | no | Resolver contract address. Part of primary key. | +| `node` | `text` | no | Name namehash. Part of primary key. | +| `key` | `text` | no | Text record key. Part of primary key. | +| `value` | `text` | no | The value of the Text Record specified by `((chain_id, resolver, node), key)`. Interpreted by `interpretTextRecordValue` — see its implementation for additional context and specific guarantees. | **Primary key:** `(chain_id, address, node, key)`. @@ -532,10 +534,10 @@ This logic is only necessary for the ENS Root Chain — the only chain that incl The composite key is chosen so that Ponder's profile-pattern matcher can decompose it from event args directly, keeping the read on the indexing-cache prefetch hot-path. -| Column | Type | Nullable | -|--------|------|----------| -| `parent_node` | `text` | no | -| `label_hash` | `text` | no | +| Column | Type | Nullable | +| ------------- | ------ | -------- | +| `parent_node` | `text` | no | +| `label_hash` | `text` | no | **Primary key:** `(parent_node, label_hash)`. @@ -543,9 +545,9 @@ The composite key is chosen so that Ponder's profile-pattern matcher can decompo Sibling lookup-by-namehash table for `migrated_nodes_by_parent`, keyed by `node`. The three `RegistryOld` handlers (`Transfer` / `NewTTL` / `NewResolver`) emit only the post-namehash `node` and cannot reconstruct the `(parent_node, label_hash)` pair without an unprofileable reverse lookup. Existence in this table is equivalent to existence in `migrated_nodes_by_parent`; both rows are written together by the migration helper. See [`protocol-acceleration/migrated-node-db-helpers.ts`](https://github.com/namehash/ensnode/blob/main/apps/ensindexer/src/lib/protocol-acceleration/migrated-node-db-helpers.ts) for the full rationale. -| Column | Type | Nullable | -|--------|------|----------| -| `node` | `text` | no | +| Column | Type | Nullable | +| ------ | ------ | -------- | +| `node` | `text` | no | **Primary key:** `node`. @@ -561,25 +563,26 @@ Models the lifecycle of ENS name registrations and renewals as logical actions, **`registrar_action_type`** — Types of "logical registrar action". -| Value | -|-------| +| Value | +| -------------- | | `registration` | -| `renewal` | +| `renewal` | #### `subregistries` A "subregistry" represents a smart contract that manages the subnames of a given parent name. The following simplifying assumptions are currently in place: + 1. No two subregistries hold state for the same node. 2. The subregistry associated with name X in the ENS root registry exclusively holds state for subnames of X. These assumptions hold for the current scope of indexing logic but may not hold as indexing expands to handle more complex scenarios. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `subregistry_id` | `text` | no | Identifies the chainId and address of the smart contract associated with the subregistry. Guaranteed to be a fully lowercase string formatted according to the CAIP-10 standard. Primary key. | -| `node` | `text` | no | The node (namehash) of the name the subregistry manages subnames of. Examples: `eth`, `base.eth`, `linea.eth`. Guaranteed to be a fully lowercase hex string representation of 32 bytes. | +| Column | Type | Nullable | Description | +| ---------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `subregistry_id` | `text` | no | Identifies the chainId and address of the smart contract associated with the subregistry. Guaranteed to be a fully lowercase string formatted according to the CAIP-10 standard. Primary key. | +| `node` | `text` | no | The node (namehash) of the name the subregistry manages subnames of. Examples: `eth`, `base.eth`, `linea.eth`. Guaranteed to be a fully lowercase hex string representation of 32 bytes. | **Indexes:** unique on `node`. @@ -593,11 +596,11 @@ A "registration lifecycle" represents a single cycle of a name being registered This data model only tracks the **most recently created** registration lifecycle record for a name, and does not track all registration lifecycle records for a name across time. Therefore, if a name goes through multiple cycles of (registration → expiry → release), this data model only stores data for the most recently created registration lifecycle. ::: -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `node` | `text` | no | The node (namehash) of the FQDN of the domain the registration lifecycle is associated with. Guaranteed to be a subname of the node of the subregistry identified by `subregistry_id`. Guaranteed to be a fully lowercase hex string representation of 32 bytes. Primary key. | -| `subregistry_id` | `text` | no | Identifies the chainId and address of the subregistry smart contract that manages the registration lifecycle. Guaranteed to be a fully lowercase CAIP-10 string. | -| `expires_at` | `numeric(78)` | no | Unix timestamp when the Registration Lifecycle is scheduled to expire. | +| Column | Type | Nullable | Description | +| ---------------- | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `node` | `text` | no | The node (namehash) of the FQDN of the domain the registration lifecycle is associated with. Guaranteed to be a subname of the node of the subregistry identified by `subregistry_id`. Guaranteed to be a fully lowercase hex string representation of 32 bytes. Primary key. | +| `subregistry_id` | `text` | no | Identifies the chainId and address of the subregistry smart contract that manages the registration lifecycle. Guaranteed to be a fully lowercase CAIP-10 string. | +| `expires_at` | `numeric(78)` | no | Unix timestamp when the Registration Lifecycle is scheduled to expire. | **Indexes:** `subregistry_id`. @@ -608,28 +611,29 @@ This data model only tracks the **most recently created** registration lifecycle Models "logical actions" rather than "events" because a single logical action, such as a single registration or renewal, may emit multiple on-chain events from multiple contracts where each individual event may only provide a subset of the data about the full logical action. Each logical action in this table is associated with a single transaction. A single transaction may perform any number of logical actions. For example, consider the logical registrar action of registering a direct subname of `.eth`. This logical action spans interactions across multiple contracts: + 1. The `EthBaseRegistrar` contract emits a `NameRegistered` event, enabling tracking of `node`, `incrementalDuration`, and `registrant`. 2. A `RegistrarController` contract emits its own `NameRegistered` event, enabling tracking of `baseCost`, `premium`, `total`, and `encodedReferrer`. The state from both events is aggregated into a single logical registrar action. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Deterministic and globally unique identifier for the logical registrar action. Represents the *initial* on-chain event associated with the action. Guaranteed to be the first element in `eventIds`. Primary key. See note below about the ID format. | -| `type` | `registrar_action_type` | no | `registration` or `renewal`. | -| `subregistry_id` | `text` | no | The ID of the subregistry the action was taken on. Identifies the chainId and address of the associated subregistry smart contract. Guaranteed to be a fully lowercase CAIP-10 string. | -| `node` | `text` | no | The node (namehash) of the FQDN of the domain associated with the action. Guaranteed to be a fully lowercase hex string representation of 32 bytes. | -| `incremental_duration` | `numeric(78)` | no | Duration added to the registration by this action, in seconds. May be `0`. See detailed description below. | -| `base_cost` | `numeric(78)` | yes | Base cost in wei. Guaranteed to be `null` if and only if `total` is `null`. Otherwise a non-negative value. | -| `premium` | `numeric(78)` | yes | Premium cost in wei above `base_cost`. Guaranteed to be `null` if and only if `total` is `null`. Guaranteed to be zero when `type` is `renewal`. | -| `total` | `numeric(78)` | yes | Total cost in wei, equal to the sum of `base_cost` and `premium`. Guaranteed to be `null` if and only if both `base_cost` and `premium` are `null`. | -| `registrant` | `text` | no | Identifies the address that initiated the action and is paying `total` (if applicable). May not be the owner of the name — there are no restrictions on who may renew a name, and the initial owner may be distinct from the registrant. Guaranteed to be a fully lowercase address. | -| `encoded_referrer` | `text` | yes | The raw 32-byte referrer value emitted on-chain. `null` if no referrer information was present in the indexed events. | -| `decoded_referrer` | `text` | yes | The referrer address decoded from `encoded_referrer` using strict left-zero-padding validation. `null` if `encoded_referrer` is `null`. May be the zero address to represent that an `encoded_referrer` is defined but interpreted as no referrer. Guaranteed to be a fully lowercase address. | -| `block_number` | `numeric(78)` | no | Block number that includes the action. The chainId of this block is the same as is referenced in `subregistry_id`. | -| `timestamp` | `numeric(78)` | no | Unix timestamp of the block referenced by `block_number`. | -| `transaction_hash` | `text` | no | Transaction hash of the action. The chainId of this transaction is the same as referenced in `subregistry_id`. Note that a single transaction may be associated with any number of logical registrar actions. | -| `event_ids` | `text[]` | no | Array of Ponder event IDs that contributed to this record. Guarantees: at least 1 element; ordered chronologically by `log_index` within `block_number`; the first element equals the `id` of this record. | +| Column | Type | Nullable | Description | +| ---------------------- | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | Deterministic and globally unique identifier for the logical registrar action. Represents the _initial_ on-chain event associated with the action. Guaranteed to be the first element in `eventIds`. Primary key. See note below about the ID format. | +| `type` | `registrar_action_type` | no | `registration` or `renewal`. | +| `subregistry_id` | `text` | no | The ID of the subregistry the action was taken on. Identifies the chainId and address of the associated subregistry smart contract. Guaranteed to be a fully lowercase CAIP-10 string. | +| `node` | `text` | no | The node (namehash) of the FQDN of the domain associated with the action. Guaranteed to be a fully lowercase hex string representation of 32 bytes. | +| `incremental_duration` | `numeric(78)` | no | Duration added to the registration by this action, in seconds. May be `0`. See detailed description below. | +| `base_cost` | `numeric(78)` | yes | Base cost in wei. Guaranteed to be `null` if and only if `total` is `null`. Otherwise a non-negative value. | +| `premium` | `numeric(78)` | yes | Premium cost in wei above `base_cost`. Guaranteed to be `null` if and only if `total` is `null`. Guaranteed to be zero when `type` is `renewal`. | +| `total` | `numeric(78)` | yes | Total cost in wei, equal to the sum of `base_cost` and `premium`. Guaranteed to be `null` if and only if both `base_cost` and `premium` are `null`. | +| `registrant` | `text` | no | Identifies the address that initiated the action and is paying `total` (if applicable). May not be the owner of the name — there are no restrictions on who may renew a name, and the initial owner may be distinct from the registrant. Guaranteed to be a fully lowercase address. | +| `encoded_referrer` | `text` | yes | The raw 32-byte referrer value emitted on-chain. `null` if no referrer information was present in the indexed events. | +| `decoded_referrer` | `text` | yes | The referrer address decoded from `encoded_referrer` using strict left-zero-padding validation. `null` if `encoded_referrer` is `null`. May be the zero address to represent that an `encoded_referrer` is defined but interpreted as no referrer. Guaranteed to be a fully lowercase address. | +| `block_number` | `numeric(78)` | no | Block number that includes the action. The chainId of this block is the same as is referenced in `subregistry_id`. | +| `timestamp` | `numeric(78)` | no | Unix timestamp of the block referenced by `block_number`. | +| `transaction_hash` | `text` | no | Transaction hash of the action. The chainId of this transaction is the same as referenced in `subregistry_id`. Note that a single transaction may be associated with any number of logical registrar actions. | +| `event_ids` | `text[]` | no | Array of Ponder event IDs that contributed to this record. Guarantees: at least 1 element; ordered chronologically by `log_index` within `block_number`; the first element equals the `id` of this record. | **Indexes:** `decoded_referrer`, `timestamp`. @@ -639,14 +643,14 @@ The state from both events is aggregated into a single logical registrar action. The `id` value is a Ponder checkpoint string — a fixed-length decimal string encoding the following fields (left to right, most to least significant): -| Field | Width (digits) | Description | -|-------|----------------|-------------| -| `block_timestamp` | 10 | Unix seconds timestamp of the block | -| `chain_id` | 16 | EIP-155 chain ID | -| `block_number` | 16 | Block number | -| `transaction_index` | 16 | Index of the transaction within the block | -| `event_type` | 1 | Internal Ponder event type (always `5`) | -| `event_index` | 16 | Index of the event within the transaction | +| Field | Width (digits) | Description | +| ------------------- | -------------- | ----------------------------------------- | +| `block_timestamp` | 10 | Unix seconds timestamp of the block | +| `chain_id` | 16 | EIP-155 chain ID | +| `block_number` | 16 | Block number | +| `transaction_index` | 16 | Index of the transaction within the block | +| `event_type` | 1 | Internal Ponder event type (always `5`) | +| `event_index` | 16 | Index of the event within the transaction | All fields are zero-padded to their fixed widths, so the string has constant length and lexicographic order equals chronological order. Because all registrar actions originate from Ponder log (smart-contract event) handlers, every `id` shares the same `event_type` digit (`5`), making direct lexicographic or bigint comparison safe for establishing total chronological order. @@ -673,15 +677,15 @@ Multiple logical registrar actions may be taken on the same `node` in the same ` **`_ensindexer_registrar_action_metadata_type`** enum: -| Value | -|-------| +| Value | +| ---------------------------------- | | `CURRENT_LOGICAL_REGISTRAR_ACTION` | -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `metadata_type` | `_ensindexer_registrar_action_metadata_type` | no | The type of internal registrar action metadata being stored. Primary key. | -| `logical_event_key` | `text` | no | A fully lowercase string formatted as `{domain_id}:{transaction_hash}`. | -| `logical_event_id` | `text` | no | Holds the `id` value of the existing `registrar_actions` record currently being built as an aggregation of on-chain events. Used by subsequent event handlers to identify which logical registrar action to aggregate additional indexed state into. | +| Column | Type | Nullable | Description | +| ------------------- | -------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `metadata_type` | `_ensindexer_registrar_action_metadata_type` | no | The type of internal registrar action metadata being stored. Primary key. | +| `logical_event_key` | `text` | no | A fully lowercase string formatted as `{domain_id}:{transaction_hash}`. | +| `logical_event_id` | `text` | no | Holds the `id` value of the existing `registrar_actions` record currently being built as an aggregation of on-chain events. Used by subsequent event handlers to identify which logical registrar action to aggregate additional indexed state into. | --- @@ -693,25 +697,26 @@ A complete re-implementation of the legacy ENS Subgraph data model. When the `su #### `subgraph_domains` -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | The namehash of the name. Primary key. | -| `name` | `text` | yes | The ENS name that this Domain represents. In subgraph-compatible mode: `null` for the root node, or a Subgraph Interpreted Name. Otherwise: an Interpreted Name (normalized, or consisting entirely of Interpreted Labels). The root node's name is `''` (empty string) rather than `null` in practice. | -| `label_name` | `text` | yes | The label associated with the Domain. In subgraph-compatible mode: `null` for the root node or a subgraph-unindexable label; otherwise a Subgraph Interpreted Label. In non-compatible mode: `null` exclusively for the root node; otherwise a normalized label, or an Encoded LabelHash for unknown or unnormalized labels. | -| `labelhash` | `text` | yes | `keccak256(label_name)`. | -| `parent_id` | `text` | yes | The namehash (`id`) of the parent name. | -| `subdomain_count` | `integer` | no | The number of subdomains. Default `0`. | -| `resolved_address_id` | `text` | yes | Address logged from the current resolver, if any. | -| `resolver_id` | `text` | yes | The resolver that controls the domain's settings. | -| `ttl` | `numeric(78)` | yes | The time-to-live (TTL) value of the domain's records. | -| `is_migrated` | `boolean` | no | Indicates whether the domain has been migrated to a new registrar. Default `false`. | -| `created_at` | `numeric(78)` | no | The time when the domain was created. | -| `owner_id` | `text` | no | The account that owns the domain. | -| `registrant_id` | `text` | yes | The account that owns the ERC721 NFT for the domain. | -| `wrapped_owner_id` | `text` | yes | The account that owns the wrapped domain. | -| `expiry_date` | `numeric(78)` | yes | The expiry date for the domain, from either the registration or the wrapped domain if PCC is burned. | +| Column | Type | Nullable | Description | +| --------------------- | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | The namehash of the name. Primary key. | +| `name` | `text` | yes | The ENS name that this Domain represents. In subgraph-compatible mode: `null` for the root node, or a Subgraph Interpreted Name. Otherwise: an Interpreted Name (normalized, or consisting entirely of Interpreted Labels). The root node's name is `''` (empty string) rather than `null` in practice. | +| `label_name` | `text` | yes | The label associated with the Domain. In subgraph-compatible mode: `null` for the root node or a subgraph-unindexable label; otherwise a Subgraph Interpreted Label. In non-compatible mode: `null` exclusively for the root node; otherwise a normalized label, or an Encoded LabelHash for unknown or unnormalized labels. | +| `labelhash` | `text` | yes | `keccak256(label_name)`. | +| `parent_id` | `text` | yes | The namehash (`id`) of the parent name. | +| `subdomain_count` | `integer` | no | The number of subdomains. Default `0`. | +| `resolved_address_id` | `text` | yes | Address logged from the current resolver, if any. | +| `resolver_id` | `text` | yes | The resolver that controls the domain's settings. | +| `ttl` | `numeric(78)` | yes | The time-to-live (TTL) value of the domain's records. | +| `is_migrated` | `boolean` | no | Indicates whether the domain has been migrated to a new registrar. Default `false`. | +| `created_at` | `numeric(78)` | no | The time when the domain was created. | +| `owner_id` | `text` | no | The account that owns the domain. | +| `registrant_id` | `text` | yes | The account that owns the ERC721 NFT for the domain. | +| `wrapped_owner_id` | `text` | yes | The account that owns the wrapped domain. | +| `expiry_date` | `numeric(78)` | yes | The expiry date for the domain, from either the registration or the wrapped domain if PCC is burned. | **Indexes:** + - `name` — hash index, because some `name` values exceed the btree max row size (8191 bytes). - `name` — GIN trigram index for partial-match filters (`_contains`, `_starts_with`, `_ends_with`). - `labelhash`, `parent_id`, `owner_id`, `registrant_id`, `wrapped_owner_id`, `resolved_address_id`. @@ -720,23 +725,23 @@ A complete re-implementation of the legacy ENS Subgraph data model. When the `su #### `subgraph_accounts` -| Column | Type | Nullable | -|--------|------|----------| -| `id` | `text` | no | +| Column | Type | Nullable | +| ------ | ------ | -------- | +| `id` | `text` | no | **Relations:** has many `subgraph_domains` records, has many `subgraph_wrapped_domains` records, has many `subgraph_registrations` records. #### `subgraph_resolvers` -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Unique identifier: concatenation of the domain namehash and the resolver address. Primary key. | -| `domain_id` | `text` | no | The domain that this resolver is associated with. | -| `address` | `text` | no | The address of the resolver contract. | -| `addr_id` | `text` | yes | The current value of the `addr` record for this resolver, as determined by the associated events. | -| `content_hash` | `text` | yes | The content hash for this resolver, in binary format. | -| `texts` | `text[]` | yes | The set of observed text record keys for this resolver. Nullable (not defaulting to `[]`) to match subgraph behavior. | -| `coin_types` | `numeric(78)[]` | yes | The set of observed SLIP-44 coin types for this resolver. Nullable (not defaulting to `[]`) to match subgraph behavior. | +| Column | Type | Nullable | Description | +| -------------- | --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | Unique identifier: concatenation of the domain namehash and the resolver address. Primary key. | +| `domain_id` | `text` | no | The domain that this resolver is associated with. | +| `address` | `text` | no | The address of the resolver contract. | +| `addr_id` | `text` | yes | The current value of the `addr` record for this resolver, as determined by the associated events. | +| `content_hash` | `text` | yes | The content hash for this resolver, in binary format. | +| `texts` | `text[]` | yes | The set of observed text record keys for this resolver. Nullable (not defaulting to `[]`) to match subgraph behavior. | +| `coin_types` | `numeric(78)[]` | yes | The set of observed SLIP-44 coin types for this resolver. Nullable (not defaulting to `[]`) to match subgraph behavior. | **Indexes:** `domain_id`. @@ -744,15 +749,15 @@ A complete re-implementation of the legacy ENS Subgraph data model. When the `su #### `subgraph_registrations` -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | The unique identifier of the registration (namehash). Primary key. | -| `domain_id` | `text` | no | The domain name associated with the registration. | -| `registration_date` | `numeric(78)` | no | The registration date of the domain. | -| `expiry_date` | `numeric(78)` | no | The expiry date of the domain. | -| `cost` | `numeric(78)` | yes | The cost associated with the domain registration. | -| `registrant_id` | `text` | no | The account that registered the domain. | -| `label_name` | `text` | yes | The label associated with the domain registration. In subgraph-compatible mode: `null` for a subgraph-unindexable label; otherwise a Subgraph Interpreted Label. In non-compatible mode: a normalized label, or an Encoded LabelHash for unnormalized labels. `null` is not expected in practice because there is no Registration entity for the root node (the only node with a null label_name). | +| Column | Type | Nullable | Description | +| ------------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | The unique identifier of the registration (namehash). Primary key. | +| `domain_id` | `text` | no | The domain name associated with the registration. | +| `registration_date` | `numeric(78)` | no | The registration date of the domain. | +| `expiry_date` | `numeric(78)` | no | The expiry date of the domain. | +| `cost` | `numeric(78)` | yes | The cost associated with the domain registration. | +| `registrant_id` | `text` | no | The account that registered the domain. | +| `label_name` | `text` | yes | The label associated with the domain registration. In subgraph-compatible mode: `null` for a subgraph-unindexable label; otherwise a Subgraph Interpreted Label. In non-compatible mode: a normalized label, or an Encoded LabelHash for unnormalized labels. `null` is not expected in practice because there is no Registration entity for the root node (the only node with a null label_name). | **Indexes:** `domain_id`, `registration_date`, `expiry_date`. @@ -760,14 +765,14 @@ A complete re-implementation of the legacy ENS Subgraph data model. When the `su #### `subgraph_wrapped_domains` -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | The unique identifier for each instance of the WrappedDomain entity. Primary key. | -| `domain_id` | `text` | no | The domain that is wrapped by this WrappedDomain. | -| `expiry_date` | `numeric(78)` | no | The expiry date of the wrapped domain. | -| `fuses` | `integer` | no | The number of fuses remaining on the wrapped domain. | -| `owner_id` | `text` | no | The account that owns this WrappedDomain. | -| `name` | `text` | yes | The name that this WrappedDomain represents. Names are emitted by the NameWrapper contract as DNS-Encoded Names which may be malformed, resulting in `null`. In subgraph-compatible mode: `null` for malformed or subgraph-unindexable labels; otherwise a Subgraph Interpreted Label. In non-compatible mode: `null` for a malformed DNS-Encoded Name; otherwise an Interpreted Name. | +| Column | Type | Nullable | Description | +| ------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | The unique identifier for each instance of the WrappedDomain entity. Primary key. | +| `domain_id` | `text` | no | The domain that is wrapped by this WrappedDomain. | +| `expiry_date` | `numeric(78)` | no | The expiry date of the wrapped domain. | +| `fuses` | `integer` | no | The number of fuses remaining on the wrapped domain. | +| `owner_id` | `text` | no | The account that owns this WrappedDomain. | +| `name` | `text` | yes | The name that this WrappedDomain represents. Names are emitted by the NameWrapper contract as DNS-Encoded Names which may be malformed, resulting in `null`. In subgraph-compatible mode: `null` for malformed or subgraph-unindexable labels; otherwise a Subgraph Interpreted Label. In non-compatible mode: `null` for a malformed DNS-Encoded Name; otherwise an Interpreted Name. | **Indexes:** `domain_id`. @@ -779,40 +784,40 @@ All event tables share the base columns `id` (primary key), `block_number`, and **Domain event tables** -| Table | Additional columns | -|-------|--------------------| -| `subgraph_transfers` | `owner_id` | -| `subgraph_new_owners` | `owner_id`, `parent_domain_id` | -| `subgraph_new_resolvers` | `resolver_id` | -| `subgraph_new_ttls` | `ttl` | -| `subgraph_wrapped_transfers` | `owner_id` | -| `subgraph_name_wrapped` | `name`, `fuses`, `owner_id`, `expiry_date` | -| `subgraph_name_unwrapped` | `owner_id` | -| `subgraph_fuses_set` | `fuses` | -| `subgraph_expiry_extended` | `expiry_date` | +| Table | Additional columns | +| ---------------------------- | ------------------------------------------ | +| `subgraph_transfers` | `owner_id` | +| `subgraph_new_owners` | `owner_id`, `parent_domain_id` | +| `subgraph_new_resolvers` | `resolver_id` | +| `subgraph_new_ttls` | `ttl` | +| `subgraph_wrapped_transfers` | `owner_id` | +| `subgraph_name_wrapped` | `name`, `fuses`, `owner_id`, `expiry_date` | +| `subgraph_name_unwrapped` | `owner_id` | +| `subgraph_fuses_set` | `fuses` | +| `subgraph_expiry_extended` | `expiry_date` | **Registration event tables** -| Table | Additional columns | -|-------|--------------------| -| `subgraph_name_registered` | `registrant_id`, `expiry_date` | -| `subgraph_name_renewed` | `expiry_date` | -| `subgraph_name_transferred` | `new_owner_id` | +| Table | Additional columns | +| --------------------------- | ------------------------------ | +| `subgraph_name_registered` | `registrant_id`, `expiry_date` | +| `subgraph_name_renewed` | `expiry_date` | +| `subgraph_name_transferred` | `new_owner_id` | **Resolver event tables** -| Table | Additional columns | -|-------|--------------------| -| `subgraph_addr_changed` | `addr_id` | -| `subgraph_multicoin_addr_changed` | `coin_type`, `addr` | -| `subgraph_name_changed` | `name` | -| `subgraph_abi_changed` | `content_type` | -| `subgraph_pubkey_changed` | `x`, `y` | -| `subgraph_text_changed` | `key`, `value` | -| `subgraph_contenthash_changed` | `hash` | -| `subgraph_interface_changed` | `interface_id`, `implementer` | -| `subgraph_authorisation_changed` | `owner`, `target`, `is_authorized` | -| `subgraph_version_changed` | `version` | +| Table | Additional columns | +| --------------------------------- | ---------------------------------- | +| `subgraph_addr_changed` | `addr_id` | +| `subgraph_multicoin_addr_changed` | `coin_type`, `addr` | +| `subgraph_name_changed` | `name` | +| `subgraph_abi_changed` | `content_type` | +| `subgraph_pubkey_changed` | `x`, `y` | +| `subgraph_text_changed` | `key`, `value` | +| `subgraph_contenthash_changed` | `hash` | +| `subgraph_interface_changed` | `interface_id`, `implementer` | +| `subgraph_authorisation_changed` | `owner`, `target`, `is_authorized` | +| `subgraph_version_changed` | `version` | --- @@ -824,24 +829,24 @@ Tracks ENS-related NFT token ownership and secondary market sales via the Seapor #### `name_sales` -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | Unique and deterministic identifier of the on-chain event associated with the sale. Composite key format: `{chain_id}-{block_number}-{log_index}` (e.g. `1-1234567-5`). Primary key. | -| `chain_id` | `bigint` | no | The chain where the sale occurred. | -| `block_number` | `numeric(78)` | no | The block number on `chain_id` where the sale occurred. | -| `log_index` | `integer` | no | The log index position of the sale event within `block_number`. | -| `transaction_hash` | `text` | no | The EVM transaction hash on `chain_id` associated with the sale. | -| `order_hash` | `text` | no | The Seaport order hash. | -| `contract_address` | `text` | no | The address of the contract on `chain_id` that manages `token_id`. | -| `token_id` | `numeric(78)` | no | The tokenId managed by `contract_address` that was sold. | -| `asset_namespace` | `text` | no | The CAIP-19 Asset Namespace of the token that was sold. Either `erc721` or `erc1155`. | -| `asset_id` | `text` | no | The CAIP-19 Asset ID of the token that was sold. A globally unique reference to the specific asset. | -| `domain_id` | `text` | no | The namehash (Node) of the ENS domain that was sold. | -| `buyer` | `text` | no | The account that bought the token controlling ownership of `domain_id` from the seller, for the amount of currency associated with the sale. | -| `seller` | `text` | no | The account that sold the token controlling ownership of `domain_id` to the buyer, for the amount of currency associated with the sale. | -| `currency` | `text` | no | Currency of the payment. One of: ETH, USDC, or DAI. | -| `amount` | `numeric(78)` | no | The amount of currency paid, denominated in the smallest unit. ETH/WETH: wei (1 ETH = 10^18). USDC: micro-units (1 USDC = 10^6). DAI: wei-equivalent (1 DAI = 10^18). | -| `timestamp` | `numeric(78)` | no | Unix timestamp of the block when the sale occurred. | +| Column | Type | Nullable | Description | +| ------------------ | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `id` | `text` | no | Unique and deterministic identifier of the on-chain event associated with the sale. Composite key format: `{chain_id}-{block_number}-{log_index}` (e.g. `1-1234567-5`). Primary key. | +| `chain_id` | `bigint` | no | The chain where the sale occurred. | +| `block_number` | `numeric(78)` | no | The block number on `chain_id` where the sale occurred. | +| `log_index` | `integer` | no | The log index position of the sale event within `block_number`. | +| `transaction_hash` | `text` | no | The EVM transaction hash on `chain_id` associated with the sale. | +| `order_hash` | `text` | no | The Seaport order hash. | +| `contract_address` | `text` | no | The address of the contract on `chain_id` that manages `token_id`. | +| `token_id` | `numeric(78)` | no | The tokenId managed by `contract_address` that was sold. | +| `asset_namespace` | `text` | no | The CAIP-19 Asset Namespace of the token that was sold. Either `erc721` or `erc1155`. | +| `asset_id` | `text` | no | The CAIP-19 Asset ID of the token that was sold. A globally unique reference to the specific asset. | +| `domain_id` | `text` | no | The namehash (Node) of the ENS domain that was sold. | +| `buyer` | `text` | no | The account that bought the token controlling ownership of `domain_id` from the seller, for the amount of currency associated with the sale. | +| `seller` | `text` | no | The account that sold the token controlling ownership of `domain_id` to the buyer, for the amount of currency associated with the sale. | +| `currency` | `text` | no | Currency of the payment. One of: ETH, USDC, or DAI. | +| `amount` | `numeric(78)` | no | The amount of currency paid, denominated in the smallest unit. ETH/WETH: wei (1 ETH = 10^18). USDC: micro-units (1 USDC = 10^6). DAI: wei-equivalent (1 DAI = 10^18). | +| `timestamp` | `numeric(78)` | no | Unix timestamp of the block when the sale occurred. | **Indexes:** `domain_id`, `asset_id`, `buyer`, `seller`, `timestamp`. @@ -849,15 +854,15 @@ Tracks ENS-related NFT token ownership and secondary market sales via the Seapor After an NFT is indexed, it is never deleted from the index. When an indexed NFT is burned on-chain, its record is retained and its `mint_status` is updated to `burned`. If the NFT is minted again after being burned, `mint_status` is updated back to `minted`. -| Column | Type | Nullable | Description | -|--------|------|----------|-------------| -| `id` | `text` | no | The CAIP-19 Asset ID of the token. A globally unique reference to this token. Primary key. | -| `domain_id` | `text` | no | The namehash (Node) of the ENS name associated with the token. An ENS name may have more than one distinct token across time. It is also possible for multiple distinct tokens for an ENS name to have a `mint_status` of `minted` at the same time — for example, when a direct subname of `.eth` is wrapped by the NameWrapper (one `minted` token managed by the `BaseRegistrar`, owned by the NameWrapper; one `minted` token managed by the NameWrapper, owned by the effective owner). | -| `chain_id` | `bigint` | no | The chain that manages the token. | -| `contract_address` | `text` | no | The address of the contract on `chain_id` that manages the token. | -| `token_id` | `numeric(78)` | no | The tokenId of the token managed by `contract_address`. | -| `asset_namespace` | `text` | no | The CAIP-19 Asset Namespace of the token. Either `erc721` or `erc1155`. | -| `owner` | `text` | no | The account that owns the token. Value is the zero address if and only if `mint_status` is `burned`. Note: the owner of the token for a given `domain_id` may differ from the owner of the associated node in the registry. For example, if address X owns `foo.eth` in both the `BaseRegistrar` and the Registry, and X transfers registry ownership directly to Y, the `BaseRegistrar` token owner remains X. The `BaseRegistrar` implements a `reclaim` function allowing the token owner to reclaim registry ownership. | -| `mint_status` | `text` | no | Either `minted` or `burned`. | +| Column | Type | Nullable | Description | +| ------------------ | ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `text` | no | The CAIP-19 Asset ID of the token. A globally unique reference to this token. Primary key. | +| `domain_id` | `text` | no | The namehash (Node) of the ENS name associated with the token. An ENS name may have more than one distinct token across time. It is also possible for multiple distinct tokens for an ENS name to have a `mint_status` of `minted` at the same time — for example, when a direct subname of `.eth` is wrapped by the NameWrapper (one `minted` token managed by the `BaseRegistrar`, owned by the NameWrapper; one `minted` token managed by the NameWrapper, owned by the effective owner). | +| `chain_id` | `bigint` | no | The chain that manages the token. | +| `contract_address` | `text` | no | The address of the contract on `chain_id` that manages the token. | +| `token_id` | `numeric(78)` | no | The tokenId of the token managed by `contract_address`. | +| `asset_namespace` | `text` | no | The CAIP-19 Asset Namespace of the token. Either `erc721` or `erc1155`. | +| `owner` | `text` | no | The account that owns the token. Value is the zero address if and only if `mint_status` is `burned`. Note: the owner of the token for a given `domain_id` may differ from the owner of the associated node in the registry. For example, if address X owns `foo.eth` in both the `BaseRegistrar` and the Registry, and X transfers registry ownership directly to Y, the `BaseRegistrar` token owner remains X. The `BaseRegistrar` implements a `reclaim` function allowing the token owner to reclaim registry ownership. | +| `mint_status` | `text` | no | Either `minted` or `burned`. | **Indexes:** `domain_id`, `owner`. diff --git a/docs/ensnode.io/src/styles/starlight.css b/docs/ensnode.io/src/styles/starlight.css index cae3cc56aa..758520f7fe 100644 --- a/docs/ensnode.io/src/styles/starlight.css +++ b/docs/ensnode.io/src/styles/starlight.css @@ -154,30 +154,25 @@ a[rel="prev"]:hover { display: inline; } -.sl-markdown-content ul { - list-style: disc; +.sl-markdown-content :where(ul, ol) { color: var(--sl-color-text); - list-style-position: inside; + margin-block: 1em; + margin-inline: 0; + padding-inline-start: 40px; } -.sl-markdown-content ul ul { - margin-left: 1rem; +.sl-markdown-content ul { + list-style: disc; } .sl-markdown-content ol { list-style: decimal; - color: var(--sl-color-text); - list-style-position: inside; -} - -.sl-markdown-content ol li::marker, -.sl-markdown-content ul li::marker { - color: var(--sl-color-accent); } -/* Nested ul inside ol li should have small top margin */ -.sl-markdown-content ol li ul { - margin-top: 0.25rem; +/* Nested lists: tighten indent and vertical spacing */ +.sl-markdown-content :is(ul, ol) :where(ul, ol) { + padding-inline-start: 1.5em; + margin-block: 0.25rem; } /* Pagination styles */