From 226ca633be2f2a4570e27bf5bf0d3a28494341ae Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 14 Jan 2026 12:00:19 +0100 Subject: [PATCH 1/7] fix(ensindexer): extend managed names config Add `UniversalRegistrarRenewalWithReferrer` config --- apps/ensindexer/src/lib/managed-names.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/ensindexer/src/lib/managed-names.ts b/apps/ensindexer/src/lib/managed-names.ts index 79bf5f7f4..9470b7983 100644 --- a/apps/ensindexer/src/lib/managed-names.ts +++ b/apps/ensindexer/src/lib/managed-names.ts @@ -70,6 +70,11 @@ const CONTRACTS_BY_MANAGED_NAME: Record = { DatasourceNames.ENSRoot, "UnwrappedEthRegistrarController", ), + getDatasourceContract( + config.namespace, + DatasourceNames.ENSRoot, + "UniversalRegistrarRenewalWithReferrer", + ), ethnamesNameWrapper, ], "base.eth": [ From 1296392c908e486a75a56343058286da9dd67291 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 14 Jan 2026 12:03:20 +0100 Subject: [PATCH 2/7] refactor(ensnode-schema): update registrar actions metadata schema Constrain `_ensindexer_registrar_action_metadata` to at most 1 row. Related to #1287 --- .../src/schemas/registrars.schema.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/ensnode-schema/src/schemas/registrars.schema.ts b/packages/ensnode-schema/src/schemas/registrars.schema.ts index 001a537fa..3212da71b 100644 --- a/packages/ensnode-schema/src/schemas/registrars.schema.ts +++ b/packages/ensnode-schema/src/schemas/registrars.schema.ts @@ -400,6 +400,19 @@ export const registrarActions = onchainTable( }), ); +/** + * Logical Registrar Action Metadata Type Enum + * + * Types of internal registrar action metadata. + * + * NOTE: This enum is an internal implementation detail of ENSIndexer and + * should not be used outside of ENSIndexer. + */ +export const internal_registrarActionMetadataType = onchainEnum( + "_ensindexer_registrar_action_metadata_type", + ["CURRENT_LOGICAL_REGISTRAR_ACTION"], +); + /** * Logical Registrar Action Metadata * @@ -422,13 +435,20 @@ export const registrarActions = onchainTable( export const internal_registrarActionMetadata = onchainTable( "_ensindexer_registrar_action_metadata", (t) => ({ + /** + * Registrar Action Metadata Type + * + * The type of internal registrar action metadata being stored. + */ + metadataType: internal_registrarActionMetadataType().primaryKey(), + /** * Logical Event Key * * A fully lowercase string formatted as: - * `{chainId}:{subregistryAddress}:{node}:{transactionHash}` + * `{domainId}:{transactionHash}` */ - logicalEventKey: t.text().primaryKey(), + logicalEventKey: t.text().notNull(), /** * Logical Event ID From cc2f271896a063c245eff5fe378585efa5952ead Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 14 Jan 2026 12:07:20 +0100 Subject: [PATCH 3/7] feat(ensindexer.plugins.registrars): change ENSDb write method Use upsert instead of insert when initializing temp record for storing metadata about the current "logical registrar action". This guarantees at most 1 record being stored in ENSDb at any given time. --- .../registrars/shared/lib/registrar-action.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts b/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts index 5ae6fc4df..10aa6905e 100644 --- a/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts +++ b/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts @@ -24,15 +24,13 @@ export type LogicalEventKey = string; * Make a logical event key for a "logical registrar action". */ export function makeLogicalEventKey({ - subregistryId, node, transactionHash, }: { - subregistryId: AccountId; node: Node; transactionHash: Hash; }): LogicalEventKey { - return [formatAccountId(subregistryId), node, transactionHash].join(":").toLowerCase(); + return [node, transactionHash].join(":").toLowerCase(); } /** @@ -57,15 +55,25 @@ export async function insertRegistrarAction( // 1. Create logical event key const logicalEventKey = makeLogicalEventKey({ node, - subregistryId, transactionHash, }); // 2. Store mapping between logical event key and logical event id - await context.db.insert(schema.internal_registrarActionMetadata).values({ - logicalEventKey, - logicalEventId: id, - }); + // Note: If the metadata record already exists, + // we update it to point to the current logical event key and id. + // This ensures that there is always at most one record in ENSDb + // for the current "logical registrar action". + await context.db + .insert(schema.internal_registrarActionMetadata) + .values({ + metadataType: "CURRENT_LOGICAL_REGISTRAR_ACTION", + logicalEventKey, + logicalEventId: id, + }) + .onConflictDoUpdate(() => ({ + logicalEventKey, + logicalEventId: id, + })); // 3. Store initial record for the "logical registrar action" await context.db.insert(schema.registrarActions).values({ From e84cf0f9520d41a216268c793172d9b1dfdbec76 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 14 Jan 2026 12:09:52 +0100 Subject: [PATCH 4/7] feat(ensindexer.plugins.registrars): change ENSDb read method Use a constant metadata type value for fetching metadata for the current "logical registarar action" from ENSDb. Also, add new invaraint to enforce that only events related to the current "logical registrar action" can be indexed. --- .../handlers/Basenames_RegistrarController.ts | 5 ---- .../handlers/Ethnames_RegistrarController.ts | 6 ----- .../Lineanames_RegistrarController.ts | 4 --- .../shared/lib/registrar-controller-events.ts | 25 ++++++++++--------- ...-registrar-renewal-with-referrer-events.ts | 25 ++++++++++--------- 5 files changed, 26 insertions(+), 39 deletions(-) diff --git a/apps/ensindexer/src/plugins/registrars/basenames/handlers/Basenames_RegistrarController.ts b/apps/ensindexer/src/plugins/registrars/basenames/handlers/Basenames_RegistrarController.ts index ab54593d7..70141f260 100644 --- a/apps/ensindexer/src/plugins/registrars/basenames/handlers/Basenames_RegistrarController.ts +++ b/apps/ensindexer/src/plugins/registrars/basenames/handlers/Basenames_RegistrarController.ts @@ -63,7 +63,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -94,7 +93,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -121,7 +119,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -152,7 +149,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -179,7 +175,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, diff --git a/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_RegistrarController.ts b/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_RegistrarController.ts index 0af1e320f..4d1fbfdd1 100644 --- a/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_RegistrarController.ts +++ b/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_RegistrarController.ts @@ -67,7 +67,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -118,7 +117,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -170,7 +168,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -220,7 +217,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -275,7 +271,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -328,7 +323,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, diff --git a/apps/ensindexer/src/plugins/registrars/lineanames/handlers/Lineanames_RegistrarController.ts b/apps/ensindexer/src/plugins/registrars/lineanames/handlers/Lineanames_RegistrarController.ts index 19a181a1c..16b8c36c2 100644 --- a/apps/ensindexer/src/plugins/registrars/lineanames/handlers/Lineanames_RegistrarController.ts +++ b/apps/ensindexer/src/plugins/registrars/lineanames/handlers/Lineanames_RegistrarController.ts @@ -63,7 +63,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -101,7 +100,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -137,7 +135,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, @@ -173,7 +170,6 @@ export default function () { await handleRegistrarControllerEvent(context, { id, - subregistryId, node, pricing, referral, diff --git a/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-controller-events.ts b/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-controller-events.ts index 402c34b55..75f65e370 100644 --- a/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-controller-events.ts +++ b/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-controller-events.ts @@ -3,7 +3,6 @@ import schema from "ponder:schema"; import type { Address, Hash } from "viem"; import { - type AccountId, type EncodedReferrer, isRegistrarActionPricingAvailable, isRegistrarActionReferralAvailable, @@ -24,14 +23,12 @@ export async function handleRegistrarControllerEvent( context: Context, { id, - subregistryId, node, pricing, referral, transactionHash, }: { id: Event["id"]; - subregistryId: AccountId; node: Node; pricing: RegistrarActionPricing; referral: RegistrarActionReferral; @@ -40,7 +37,6 @@ export async function handleRegistrarControllerEvent( ): Promise { // 1. Make Logical Event Key const logicalEventKey = makeLogicalEventKey({ - subregistryId, node, transactionHash, }); @@ -48,29 +44,34 @@ export async function handleRegistrarControllerEvent( // 2. Use the Logical Event Key to get the "logical registrar action" record // which needs to be updated. - // 2. a) Find subregistryActionMetadata record by logical event key. - const subregistryActionMetadata = await context.db.find(schema.internal_registrarActionMetadata, { - logicalEventKey, + // 2. a) Find registrarActionMetadata record for the current "logical registrar action". + const registrarActionMetadata = await context.db.find(schema.internal_registrarActionMetadata, { + metadataType: "CURRENT_LOGICAL_REGISTRAR_ACTION", }); - // Invariant: the subregistryActionMetadata record must be available for `logicalEventKey` - if (!subregistryActionMetadata) { + // Invariant: the registrarActionMetadata record must exist + if (!registrarActionMetadata) { throw new Error( `The required "logical registrar action" ID could not be found for the following logical event key: '${logicalEventKey}'.`, ); } - const { logicalEventId } = subregistryActionMetadata; + // Invariant: the stored logical event key must match the current logical event key + if (registrarActionMetadata.logicalEventKey !== logicalEventKey) { + throw new Error( + `The logical event key ('${registrarActionMetadata.logicalEventKey}') for the "logical registrar action" record must be same as the current logical event key ('${logicalEventKey}').`, + ); + } // 2. b) Find "logical registrar action" record by `logicalEventId`. const logicalRegistrarAction = await context.db.find(schema.registrarActions, { - id: logicalEventId, + id: registrarActionMetadata.logicalEventId, }); // Invariant: the "logical registrar action" record must be available for `logicalEventId` if (!logicalRegistrarAction) { throw new Error( - `The "logical registrar action" record, which could not be found for the following logical event ID: '${logicalEventId}'.`, + `The "logical registrar action" record, which could not be found for the following logical event ID: '${registrarActionMetadata.logicalEventId}'.`, ); } diff --git a/apps/ensindexer/src/plugins/registrars/shared/lib/universal-registrar-renewal-with-referrer-events.ts b/apps/ensindexer/src/plugins/registrars/shared/lib/universal-registrar-renewal-with-referrer-events.ts index ebd646ef1..fe3e92b85 100644 --- a/apps/ensindexer/src/plugins/registrars/shared/lib/universal-registrar-renewal-with-referrer-events.ts +++ b/apps/ensindexer/src/plugins/registrars/shared/lib/universal-registrar-renewal-with-referrer-events.ts @@ -3,7 +3,6 @@ import schema from "ponder:schema"; import type { Address, Hash } from "viem"; import { - type AccountId, type EncodedReferrer, isRegistrarActionReferralAvailable, type Node, @@ -21,13 +20,11 @@ export async function handleUniversalRegistrarRenewalEvent( context: Context, { id, - subregistryId, node, referral, transactionHash, }: { id: Event["id"]; - subregistryId: AccountId; node: Node; referral: RegistrarActionReferral; transactionHash: Hash; @@ -35,7 +32,6 @@ export async function handleUniversalRegistrarRenewalEvent( ): Promise { // 1. Make Logical Event Key const logicalEventKey = makeLogicalEventKey({ - subregistryId, node, transactionHash, }); @@ -43,29 +39,34 @@ export async function handleUniversalRegistrarRenewalEvent( // 2. Use the Logical Event Key to get the "logical registrar action" record // which needs to be updated. - // 2. a) Find subregistryActionMetadata record by logical event key. - const subregistryActionMetadata = await context.db.find(schema.internal_registrarActionMetadata, { - logicalEventKey, + // 2. a) Find registrarActionMetadata record for the current "logical registrar action". + const registrarActionMetadata = await context.db.find(schema.internal_registrarActionMetadata, { + metadataType: "CURRENT_LOGICAL_REGISTRAR_ACTION", }); - // Invariant: the subregistryActionMetadata record must be available for `logicalEventKey` - if (!subregistryActionMetadata) { + // Invariant: the registrarActionMetadata record must exist + if (!registrarActionMetadata) { throw new Error( `The required "logical registrar action" ID could not be found for the following logical event key: '${logicalEventKey}'.`, ); } - const { logicalEventId } = subregistryActionMetadata; + // Invariant: the stored logical event key must match the current logical event key + if (registrarActionMetadata.logicalEventKey !== logicalEventKey) { + throw new Error( + `The logical event key ('${registrarActionMetadata.logicalEventKey}') for the "logical registrar action" record must be same as the current logical event key ('${logicalEventKey}').`, + ); + } // 2. b) Find "logical registrar action" record by `logicalEventId`. const logicalRegistrarAction = await context.db.find(schema.registrarActions, { - id: logicalEventId, + id: registrarActionMetadata.logicalEventId, }); // Invariant: the "logical registrar action" record must be available for `logicalEventId` if (!logicalRegistrarAction) { throw new Error( - `The "logical registrar action" record, which could not be found for the following logical event ID: '${logicalEventId}'.`, + `The "logical registrar action" record, which could not be found for the following logical event ID: '${registrarActionMetadata.logicalEventId}'.`, ); } From 88e58a715cb74d2e7e6e37a72e30adc8daf3277e Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 14 Jan 2026 14:25:58 +0100 Subject: [PATCH 5/7] fix type issues --- .../Ethnames_UniversalRegistrarRenewalWithReferrer.ts | 1 - .../src/plugins/registrars/shared/lib/registrar-action.ts | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_UniversalRegistrarRenewalWithReferrer.ts b/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_UniversalRegistrarRenewalWithReferrer.ts index 18c7345af..5b6273515 100644 --- a/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_UniversalRegistrarRenewalWithReferrer.ts +++ b/apps/ensindexer/src/plugins/registrars/ethnames/handlers/Ethnames_UniversalRegistrarRenewalWithReferrer.ts @@ -45,7 +45,6 @@ export default function () { await handleUniversalRegistrarRenewalEvent(context, { id, - subregistryId, node, referral, transactionHash, diff --git a/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts b/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts index 10aa6905e..c4d6f3fa7 100644 --- a/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts +++ b/apps/ensindexer/src/plugins/registrars/shared/lib/registrar-action.ts @@ -2,12 +2,7 @@ import type { Context } from "ponder:registry"; import schema from "ponder:schema"; import type { Hash } from "viem"; -import { - type AccountId, - formatAccountId, - type Node, - type RegistrarAction, -} from "@ensnode/ensnode-sdk"; +import { formatAccountId, type Node, type RegistrarAction } from "@ensnode/ensnode-sdk"; /** * Logical Event Key From 05a974c208e49d4259025604c4a86dd7d97a3173 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 14 Jan 2026 16:23:58 +0100 Subject: [PATCH 6/7] docs(changeset): Update `registrars` schema to enable storing at most one metadata record. --- .changeset/busy-poems-bow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/busy-poems-bow.md diff --git a/.changeset/busy-poems-bow.md b/.changeset/busy-poems-bow.md new file mode 100644 index 000000000..e45795a25 --- /dev/null +++ b/.changeset/busy-poems-bow.md @@ -0,0 +1,5 @@ +--- +"@ensnode/ensnode-schema": minor +--- + +Update `registrars` schema to enable storing at most one metadata record. From 12c95848a890721c4ef45e1873e023ed22915131 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 14 Jan 2026 16:25:26 +0100 Subject: [PATCH 7/7] docs(changeset): Update `registrars` plugin indexing logic to store at most one metadata record in ENSDb for current "logical registrar action". --- .changeset/social-colts-smell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/social-colts-smell.md diff --git a/.changeset/social-colts-smell.md b/.changeset/social-colts-smell.md new file mode 100644 index 000000000..97a2863a6 --- /dev/null +++ b/.changeset/social-colts-smell.md @@ -0,0 +1,5 @@ +--- +"ensindexer": patch +--- + +Update `registrars` plugin indexing logic to store at most one metadata record in ENSDb for current "logical registrar action".