diff --git a/.changeset/strong-baboons-help.md b/.changeset/strong-baboons-help.md new file mode 100644 index 0000000000..2c4c19280b --- /dev/null +++ b/.changeset/strong-baboons-help.md @@ -0,0 +1,5 @@ +--- +"ensapi": minor +--- + +ENSNode GraphQL API: `ENSv1Domain.rootRegistryOwner` is now available, indicating the owner of the Domain's node within the ENSv1 Registry contract. diff --git a/apps/ensapi/src/graphql-api/schema/domain.ts b/apps/ensapi/src/graphql-api/schema/domain.ts index f5bef77132..272a5b7a66 100644 --- a/apps/ensapi/src/graphql-api/schema/domain.ts +++ b/apps/ensapi/src/graphql-api/schema/domain.ts @@ -262,6 +262,17 @@ ENSv1DomainRef.implement({ nullable: true, resolve: (parent) => parent.parentId, }), + + ///////////////////////////////// + // ENSv1Domain.rootRegistryOwner + ///////////////////////////////// + rootRegistryOwner: t.field({ + description: + "The rootRegistryOwner of this Domain, i.e. the owner() of this Domain within the ENSv1 Registry.", + type: AccountRef, + nullable: true, + resolve: (parent) => parent.rootRegistryOwnerId, + }), }), }); diff --git a/apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts b/apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts index 65af6858cb..91183d21b3 100644 --- a/apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts +++ b/apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts @@ -76,13 +76,14 @@ export default function () { // upsert domain await context.db .insert(schema.v1Domain) - .values({ - id: domainId, - parentId, - labelHash, - }) + .values({ id: domainId, parentId, labelHash }) .onConflictDoNothing(); + // update rootRegistryOwner + await context.db + .update(schema.v1Domain, { id: domainId }) + .set({ rootRegistryOwnerId: interpretAddress(owner) }); + // materialize domain owner // NOTE: despite Domain.ownerId being materialized from other sources of truth (i.e. Registrars // like BaseRegistrars & NameWrapper) it's ok to always set it here because the Registrar-emitted @@ -99,25 +100,25 @@ export default function () { context: Context; event: EventWithArgs<{ node: Node; owner: Address }>; }) { - const { node, owner: _owner } = event.args; - const owner = interpretAddress(_owner); + const { node, owner } = event.args; // ENSv2 model does not include root node, no-op if (node === ROOT_NODE) return; const domainId = makeENSv1DomainId(node); - if (owner === null) { - await context.db.delete(schema.v1Domain, { id: domainId }); - } else { - // materialize domain owner - // NOTE: despite Domain.ownerId being materialized from other sources of truth (i.e. Registrars - // like BaseRegistrars & NameWrapper) it's ok to always set it here because the Registrar-emitted - // events occur _after_ the Registry events. So when a name is wrapped, for example, the Registry's - // owner changes to that of the NameWrapper but then the NameWrapper emits NameWrapped, and this - // indexing code re-materializes the Domain.ownerId to the NameWraper-emitted value. - await materializeENSv1DomainEffectiveOwner(context, domainId, owner); - } + // set the domain's rootRegistryOwner to `owner` + await context.db + .update(schema.v1Domain, { id: domainId }) + .set({ rootRegistryOwnerId: interpretAddress(owner) }); + + // materialize domain owner + // NOTE: despite Domain.ownerId being materialized from other sources of truth (i.e. Registrars + // like BaseRegistrars & NameWrapper) it's ok to always set it here because the Registrar-emitted + // events occur _after_ the Registry events. So when a name is wrapped, for example, the Registry's + // owner changes to that of the NameWrapper but then the NameWrapper emits NameWrapped, and this + // indexing code re-materializes the Domain.ownerId to the NameWraper-emitted value. + await materializeENSv1DomainEffectiveOwner(context, domainId, owner); } /** diff --git a/packages/ensnode-schema/src/schemas/ensv2.schema.ts b/packages/ensnode-schema/src/schemas/ensv2.schema.ts index 742d0820d9..028a8f4d1f 100644 --- a/packages/ensnode-schema/src/schemas/ensv2.schema.ts +++ b/packages/ensnode-schema/src/schemas/ensv2.schema.ts @@ -174,6 +174,9 @@ export const v1Domain = onchainTable( // represents a labelHash labelHash: t.hex().notNull().$type(), + // may have a `rootRegistryOwner` (ENSv1Registry's owner()), zeroAddress interpreted as null + rootRegistryOwnerId: t.hex().$type
(), + // NOTE: Domain-Resolver Relations tracked via Protocol Acceleration plugin }), (t) => ({ @@ -190,6 +193,11 @@ export const relations_v1Domain = relations(v1Domain, ({ one, many }) => ({ references: [v1Domain.id], }), children: many(v1Domain, { relationName: "parent" }), + rootRegistryOwner: one(account, { + relationName: "rootRegistryOwner", + fields: [v1Domain.rootRegistryOwnerId], + references: [account.id], + }), // shared owner: one(account, { diff --git a/packages/ensnode-sdk/src/graphql-api/example-queries.ts b/packages/ensnode-sdk/src/graphql-api/example-queries.ts index 5b346dd74b..7e3a5c3456 100644 --- a/packages/ensnode-sdk/src/graphql-api/example-queries.ts +++ b/packages/ensnode-sdk/src/graphql-api/example-queries.ts @@ -101,6 +101,10 @@ query DomainByName($name: Name!) { label { interpreted } name + ... on ENSv1Domain { + rootRegistryOwner { address } + } + ... on ENSv2Domain { subregistry { contract { chainId address }