diff --git a/README.md b/README.md index 1534c94..63f15d4 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ estimated mainnet backfill time @ <=500rps = ~12 hours on M1 Macbook (~14x-28x s ## next up +- [ ] implement events +- [ ] implement polymorphic events resolver on each relevant entity - [ ] `_nocase` case-insensitive where filters - not used interally but ensjs does technically expose this as an available filter to users - [ ] confirm all the schema relations are configured correctly diff --git a/ponder.schema.ts b/ponder.schema.ts index e38f2d0..6e6c38e 100644 --- a/ponder.schema.ts +++ b/ponder.schema.ts @@ -1,6 +1,10 @@ import { onchainTable, relations } from "ponder"; import type { Address } from "viem"; +/** + * Domain + */ + export const domain = onchainTable("domains", (t) => ({ // The namehash of the name id: t.hex().primaryKey(), @@ -39,62 +43,49 @@ export const domain = onchainTable("domains", (t) => ({ // The expiry date for the domain, from either the registration, or the wrapped domain if PCC is burned expiryDate: t.bigint("expiry_date"), - - // "The events associated with the domain" - // events: [DomainEvent!]! @derivedFrom(field: "domain") })); export const domainRelations = relations(domain, ({ one, many }) => ({ - resolvedAddress: one(account, { - fields: [domain.resolvedAddressId], - references: [account.id], - }), - owner: one(account, { - fields: [domain.ownerId], - references: [account.id], - }), - parent: one(domain, { - fields: [domain.parentId], - references: [domain.id], - }), - resolver: one(resolver, { - fields: [domain.resolverId], - references: [resolver.id], - }), + resolvedAddress: one(account, { fields: [domain.resolvedAddressId], references: [account.id] }), + owner: one(account, { fields: [domain.ownerId], references: [account.id] }), + parent: one(domain, { fields: [domain.parentId], references: [domain.id] }), + resolver: one(resolver, { fields: [domain.resolverId], references: [resolver.id] }), subdomains: many(domain, { relationName: "parent" }), - registrant: one(account, { - fields: [domain.registrantId], - references: [account.id], - }), - wrappedOwner: one(account, { - fields: [domain.wrappedOwnerId], - references: [account.id], - }), - - // The wrapped domain associated with the domain - wrappedDomain: one(wrappedDomain, { - fields: [domain.id], - references: [wrappedDomain.domainId], - }), + registrant: one(account, { fields: [domain.registrantId], references: [account.id] }), + wrappedOwner: one(account, { fields: [domain.wrappedOwnerId], references: [account.id] }), + wrappedDomain: one(wrappedDomain, { fields: [domain.id], references: [wrappedDomain.domainId] }), + registration: one(registration, { fields: [domain.id], references: [registration.domainId] }), - // The registration associated with the domain - registration: one(registration, { - fields: [domain.id], - references: [registration.domainId], - }), + // event relations + transfers: many(transfer), + newOwners: many(newOwner), + newResolvers: many(newResolver), + newTTLs: many(newTTL), + wrappedTransfers: many(wrappedTransfer), + nameWrappeds: many(nameWrapped), + nameUnwrappeds: many(nameUnwrapped), + fusesSets: many(fusesSet), + expiryExtendeds: many(expiryExtended), })); +/** + * Account + */ + export const account = onchainTable("accounts", (t) => ({ id: t.hex().primaryKey(), })); export const accountRelations = relations(account, ({ many }) => ({ - // account has many domains domains: many(domain), - // TODO: has many wrapped domains - // TODO: has many registrations + wrappedDomains: many(wrappedDomain), + registrations: many(registration), })); +/** + * Resolver + */ + export const resolver = onchainTable("resolvers", (t) => ({ // The unique identifier for this resolver, which is a concatenation of the domain namehash and the resolver address id: t.text().primaryKey(), @@ -113,21 +104,29 @@ export const resolver = onchainTable("resolvers", (t) => ({ // The set of observed SLIP-44 coin types for this resolver // NOTE: we avoid .notNull.default([]) to match subgraph behavior coinTypes: t.bigint("coin_types").array(), - - // TODO: has many events })); -export const resolverRelations = relations(resolver, ({ one }) => ({ - addr: one(account, { - fields: [resolver.addrId], - references: [account.id], - }), - domain: one(domain, { - fields: [resolver.domainId], - references: [domain.id], - }), +export const resolverRelations = relations(resolver, ({ one, many }) => ({ + addr: one(account, { fields: [resolver.addrId], references: [account.id] }), + domain: one(domain, { fields: [resolver.domainId], references: [domain.id] }), + + // event relations + addrChangeds: many(addrChanged), + multicoinAddrChangeds: many(multicoinAddrChanged), + nameChangeds: many(nameChanged), + abiChangeds: many(abiChanged), + pubkeyChangeds: many(pubkeyChanged), + textChangeds: many(textChanged), + contenthashChangeds: many(contenthashChanged), + interfaceChangeds: many(interfaceChanged), + authorisationChangeds: many(authorisationChanged), + versionChangeds: many(versionChanged), })); +/** + * Registration + */ + export const registration = onchainTable("registrations", (t) => ({ // The unique identifier of the registration id: t.hex().primaryKey(), @@ -143,22 +142,22 @@ export const registration = onchainTable("registrations", (t) => ({ registrantId: t.hex("registrant_id").notNull(), // The human-readable label name associated with the domain registration labelName: t.text(), - - // The events associated with the domain registration - // TODO: events })); -export const registrationRelations = relations(registration, ({ one }) => ({ - domain: one(domain, { - fields: [registration.domainId], - references: [domain.id], - }), - registrant: one(account, { - fields: [registration.registrantId], - references: [account.id], - }), +export const registrationRelations = relations(registration, ({ one, many }) => ({ + domain: one(domain, { fields: [registration.domainId], references: [domain.id] }), + registrant: one(account, { fields: [registration.registrantId], references: [account.id] }), + + // event relations + nameRegistereds: many(nameRegistered), + nameReneweds: many(nameRenewed), + nameTransferreds: many(nameTransferred), })); +/** + * Wrapped Domain + */ + export const wrappedDomain = onchainTable("wrapped_domains", (t) => ({ // The unique identifier for each instance of the WrappedDomain entity id: t.hex().primaryKey(), @@ -175,12 +174,274 @@ export const wrappedDomain = onchainTable("wrapped_domains", (t) => ({ })); export const wrappedDomainRelations = relations(wrappedDomain, ({ one }) => ({ - domain: one(domain, { - fields: [wrappedDomain.domainId], - references: [domain.id], + domain: one(domain, { fields: [wrappedDomain.domainId], references: [domain.id] }), + owner: one(account, { fields: [wrappedDomain.ownerId], references: [account.id] }), +})); + +/** + * Events + */ + +const domainEvent = (t: any) => ({ + id: t.text().primaryKey(), + domainId: t.hex("domain_id").notNull(), + blockNumber: t.integer("block_number").notNull(), + transactionID: t.hex("transaction_id").notNull(), +}); + +// Domain Event Entities + +export const transfer = onchainTable("transfers", (t) => ({ + ...domainEvent(t), + ownerId: t.hex("owner_id").notNull(), +})); + +export const newOwner = onchainTable("new_owners", (t) => ({ + ...domainEvent(t), + ownerId: t.hex("owner_id").notNull(), + parentDomainId: t.hex("parent_domain_id").notNull(), +})); + +export const newResolver = onchainTable("new_resolvers", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), +})); + +export const newTTL = onchainTable("new_ttls", (t) => ({ + ...domainEvent(t), + ttl: t.bigint().notNull(), +})); + +export const wrappedTransfer = onchainTable("wrapped_transfers", (t) => ({ + ...domainEvent(t), + ownerId: t.hex("owner_id").notNull(), +})); + +export const nameWrapped = onchainTable("name_wrapped", (t) => ({ + ...domainEvent(t), + name: t.text(), + fuses: t.integer().notNull(), + ownerId: t.hex("owner_id").notNull(), + expiryDate: t.bigint("expiry_date").notNull(), +})); + +export const nameUnwrapped = onchainTable("name_unwrapped", (t) => ({ + ...domainEvent(t), + ownerId: t.hex("owner_id").notNull(), +})); + +export const fusesSet = onchainTable("fuses_set", (t) => ({ + ...domainEvent(t), + fuses: t.integer().notNull(), +})); + +export const expiryExtended = onchainTable("expiry_extended", (t) => ({ + ...domainEvent(t), + expiryDate: t.bigint("expiry_date").notNull(), +})); + +// Registration Event Entities + +export const nameRegistered = onchainTable("name_registered", (t) => ({ + ...domainEvent(t), + registrationId: t.hex("registration_id").notNull(), + registrantId: t.hex("registrant_id").notNull(), + expiryDate: t.bigint("expiry_date").notNull(), +})); + +export const nameRenewed = onchainTable("name_renewed", (t) => ({ + ...domainEvent(t), + registrationId: t.hex("registration_id").notNull(), + expiryDate: t.bigint("expiry_date").notNull(), +})); + +export const nameTransferred = onchainTable("name_transferred", (t) => ({ + ...domainEvent(t), + registrationId: t.hex("registration_id").notNull(), + newOwnerId: t.hex("new_owner_id").notNull(), +})); + +// Resolver Event Entities + +export const addrChanged = onchainTable("addr_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + addrId: t.hex("addr_id").notNull(), +})); + +export const multicoinAddrChanged = onchainTable("multicoin_addr_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + coinType: t.bigint("coin_type").notNull(), + addr: t.hex().notNull(), +})); + +export const nameChanged = onchainTable("name_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + name: t.text().notNull(), +})); + +export const abiChanged = onchainTable("abi_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + contentType: t.bigint("content_type").notNull(), +})); + +export const pubkeyChanged = onchainTable("pubkey_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + x: t.hex().notNull(), + y: t.hex().notNull(), +})); + +export const textChanged = onchainTable("text_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + key: t.text().notNull(), + value: t.text(), +})); + +export const contenthashChanged = onchainTable("contenthash_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + hash: t.hex().notNull(), +})); + +export const interfaceChanged = onchainTable("interface_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + interfaceID: t.hex("interface_id").notNull(), + implementer: t.hex().notNull(), +})); + +export const authorisationChanged = onchainTable("authorisation_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + owner: t.hex().notNull(), + target: t.hex().notNull(), + isAuthorized: t.boolean("is_authorized").notNull(), +})); + +export const versionChanged = onchainTable("version_changed", (t) => ({ + ...domainEvent(t), + resolverId: t.text("resolver_id").notNull(), + version: t.bigint().notNull(), +})); + +/** + * Event Relations + */ + +// Domain Event Relations + +export const transferRelations = relations(transfer, ({ one }) => ({ + domain: one(domain, { fields: [transfer.domainId], references: [domain.id] }), + owner: one(account, { fields: [transfer.ownerId], references: [account.id] }), +})); + +export const newOwnerRelations = relations(newOwner, ({ one }) => ({ + domain: one(domain, { fields: [newOwner.domainId], references: [domain.id] }), + owner: one(account, { fields: [newOwner.ownerId], references: [account.id] }), + parentDomain: one(domain, { fields: [newOwner.parentDomainId], references: [domain.id] }), +})); + +export const newResolverRelations = relations(newResolver, ({ one }) => ({ + domain: one(domain, { fields: [newResolver.domainId], references: [domain.id] }), + resolver: one(resolver, { fields: [newResolver.resolverId], references: [resolver.id] }), +})); + +export const newTTLRelations = relations(newTTL, ({ one }) => ({ + domain: one(domain, { fields: [newTTL.domainId], references: [domain.id] }), +})); + +export const wrappedTransferRelations = relations(wrappedTransfer, ({ one }) => ({ + domain: one(domain, { fields: [wrappedTransfer.domainId], references: [domain.id] }), + owner: one(account, { fields: [wrappedTransfer.ownerId], references: [account.id] }), +})); + +export const nameWrappedRelations = relations(nameWrapped, ({ one }) => ({ + domain: one(domain, { fields: [nameWrapped.domainId], references: [domain.id] }), + owner: one(account, { fields: [nameWrapped.ownerId], references: [account.id] }), +})); + +export const nameUnwrappedRelations = relations(nameUnwrapped, ({ one }) => ({ + domain: one(domain, { fields: [nameUnwrapped.domainId], references: [domain.id] }), + owner: one(account, { fields: [nameUnwrapped.ownerId], references: [account.id] }), +})); + +export const fusesSetRelations = relations(fusesSet, ({ one }) => ({ + domain: one(domain, { fields: [fusesSet.domainId], references: [domain.id] }), +})); + +export const expiryExtendedRelations = relations(expiryExtended, ({ one }) => ({ + domain: one(domain, { fields: [expiryExtended.domainId], references: [domain.id] }), +})); + +// Registration Event Relations + +export const nameRegisteredRelations = relations(nameRegistered, ({ one }) => ({ + registration: one(registration, { + fields: [nameRegistered.registrationId], + references: [registration.id], + }), + registrant: one(account, { fields: [nameRegistered.registrantId], references: [account.id] }), +})); + +export const nameRenewedRelations = relations(nameRenewed, ({ one }) => ({ + registration: one(registration, { + fields: [nameRenewed.registrationId], + references: [registration.id], }), - owner: one(account, { - fields: [wrappedDomain.ownerId], - references: [account.id], +})); + +export const nameTransferredRelations = relations(nameTransferred, ({ one }) => ({ + registration: one(registration, { + fields: [nameTransferred.registrationId], + references: [registration.id], }), + newOwner: one(account, { fields: [nameTransferred.newOwnerId], references: [account.id] }), +})); + +// Resolver Event Relations + +export const addrChangedRelations = relations(addrChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [addrChanged.resolverId], references: [resolver.id] }), + addr: one(account, { fields: [addrChanged.addrId], references: [account.id] }), +})); + +export const multicoinAddrChangedRelations = relations(multicoinAddrChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [multicoinAddrChanged.resolverId], references: [resolver.id] }), +})); + +export const nameChangedRelations = relations(nameChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [nameChanged.resolverId], references: [resolver.id] }), +})); + +export const abiChangedRelations = relations(abiChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [abiChanged.resolverId], references: [resolver.id] }), +})); + +export const pubkeyChangedRelations = relations(pubkeyChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [pubkeyChanged.resolverId], references: [resolver.id] }), +})); + +export const textChangedRelations = relations(textChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [textChanged.resolverId], references: [resolver.id] }), +})); + +export const contenthashChangedRelations = relations(contenthashChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [contenthashChanged.resolverId], references: [resolver.id] }), +})); + +export const interfaceChangedRelations = relations(interfaceChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [interfaceChanged.resolverId], references: [resolver.id] }), +})); + +export const authorisationChangedRelations = relations(authorisationChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [authorisationChanged.resolverId], references: [resolver.id] }), +})); + +export const versionChangedRelations = relations(versionChanged, ({ one }) => ({ + resolver: one(resolver, { fields: [versionChanged.resolverId], references: [resolver.id] }), })); diff --git a/src/api/graphql.ts b/src/api/graphql.ts index 68c8f94..c43ed4f 100644 --- a/src/api/graphql.ts +++ b/src/api/graphql.ts @@ -6,6 +6,7 @@ * 2. removes ponder's encoded id params in favor of literal ids * 3. implement subgraph's simpler pagination style with first & skip w/out Page types * 4. PascalCase entity names + * 5. Hardcoded Polymorphic Event Types */ // here we inline the following types from this original import @@ -70,6 +71,8 @@ import { PgTableExtraConfig, TableConfig, isPgEnum, + union, + unionAll, } from "drizzle-orm/pg-core"; import { GraphQLBoolean, @@ -82,6 +85,7 @@ import { GraphQLInputObjectType, type GraphQLInputType, GraphQLInt, + GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, @@ -89,6 +93,7 @@ import { GraphQLScalarType, GraphQLSchema, GraphQLString, + GraphQLUnionType, } from "graphql"; import { GraphQLJSON } from "graphql-scalars"; @@ -118,6 +123,64 @@ const OrderDirectionEnum = new GraphQLEnumType({ }, }); +/** + * Polymorphic Event TsNames + */ + +const DomainEventTsNames = [ + "transfer", + "newOwner", + "newResolver", + "newTTL", + "wrappedTransfer", + "nameWrapped", + "nameUnwrapped", + "fusesSet", + "expiryExtended", +]; + +const RegistrationEventTsNames = ["nameRegistered", "nameRenewed", "nameTransferred"]; + +const ResolverEventTsNames = [ + "addrChanged", + "multicoinAddrChanged", + "nameChanged", + "abiChanged", + "pubkeyChanged", + "textChanged", + "contenthashChanged", + "interfaceChanged", + "authorisationChanged", + "versionChanged", +]; + +const DomainEvent = new GraphQLInterfaceType({ + name: "DomainEvent", + fields: { + id: { type: new GraphQLNonNull(GraphQLString) }, + blockNumber: { type: new GraphQLNonNull(GraphQLInt) }, + transactionID: { type: new GraphQLNonNull(GraphQLString) }, + }, +}); + +const RegistrationEvent = new GraphQLInterfaceType({ + name: "RegistrationEvent", + fields: { + id: { type: new GraphQLNonNull(GraphQLString) }, + blockNumber: { type: new GraphQLNonNull(GraphQLInt) }, + transactionID: { type: new GraphQLNonNull(GraphQLString) }, + }, +}); + +const ResolverEvent = new GraphQLInterfaceType({ + name: "ResolverEvent", + fields: { + id: { type: new GraphQLNonNull(GraphQLString) }, + blockNumber: { type: new GraphQLNonNull(GraphQLInt) }, + transactionID: { type: new GraphQLNonNull(GraphQLString) }, + }, +}); + export function buildGraphQLSchema(schema: Schema): GraphQLSchema { const tablesConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); @@ -154,7 +217,7 @@ export function buildGraphQLSchema(schema: Schema): GraphQLSchema { // TODO: relationships i.e. parent__labelName iff necessary entityOrderByEnums[table.tsName] = new GraphQLEnumType({ - name: `${pascalCase(table.tsName)}_orderBy`, + name: `${getSubgraphEntityName(table.tsName)}_orderBy`, values, }); } @@ -162,7 +225,7 @@ export function buildGraphQLSchema(schema: Schema): GraphQLSchema { const entityFilterTypes: Record = {}; for (const table of tables) { const filterType = new GraphQLInputObjectType({ - name: `${table.tsName}Filter`, + name: `${table.tsName}_filter`, fields: () => { const filterFields: GraphQLInputFieldConfigMap = { // Logical operators @@ -235,7 +298,13 @@ export function buildGraphQLSchema(schema: Schema): GraphQLSchema { for (const table of tables) { entityTypes[table.tsName] = new GraphQLObjectType({ - name: pascalCase(table.tsName), // NOTE: PascalCase to match subgraph + name: getSubgraphEntityName(table.tsName), + // polymorphic event interface logic + interfaces: [ + ...(DomainEventTsNames.includes(table.tsName) ? [DomainEvent] : []), + ...(RegistrationEventTsNames.includes(table.tsName) ? [RegistrationEvent] : []), + ...(ResolverEventTsNames.includes(table.tsName) ? [ResolverEvent] : []), + ], fields: () => { const fieldConfigMap: GraphQLFieldConfigMap = {}; @@ -418,6 +487,104 @@ export function buildGraphQLSchema(schema: Schema): GraphQLSchema { }; } + /** + * Polymorphic Event Logic + * + * Not super happy with how this is implemented but it gets the job done. + * + */ + + entityTypes["domain"]!.getFields().events = { + name: "events", + type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(DomainEvent))), + args: [], + description: "The events associated with the domain", + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + resolve: async (parent, _args, { drizzle }) => { + const eventTables = tables.filter((t) => DomainEventTsNames.includes(t.tsName)); + const results = await Promise.all( + eventTables.map((t) => + drizzle.query[t.tsName]!.findMany({ + where: eq(t.columns["domainId"]!, parent.id), + }), + ), + ); + + return results + .flatMap((events, i) => + events.map((event) => ({ + ...event, + __typename: getSubgraphEntityName(eventTables[i]!.tsName), + })), + ) + .sort(sortByBlockNumber); + }, + }; + + entityTypes["registration"]!.getFields().events = { + name: "events", + type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(RegistrationEvent))), + args: [], + description: "The events associated with the registration", + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + resolve: async (parent, _args, { drizzle }) => { + const eventTables = tables.filter((t) => RegistrationEventTsNames.includes(t.tsName)); + const results = await Promise.all( + eventTables.map((t) => + drizzle.query[t.tsName]!.findMany({ + where: eq(t.columns["registrationId"]!, parent.id), + }), + ), + ); + + return results + .flatMap((events, i) => + events.map((event) => ({ + ...event, + __typename: getSubgraphEntityName(eventTables[i]!.tsName), + })), + ) + .sort(sortByBlockNumber); + }, + }; + + entityTypes["resolver"]!.getFields().events = { + name: "events", + type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(ResolverEvent))), + args: [], + description: "The events associated with the resolver", + deprecationReason: undefined, + extensions: {}, + astNode: undefined, + resolve: async (parent, _args, { drizzle }) => { + const eventTables = tables.filter((t) => ResolverEventTsNames.includes(t.tsName)); + const results = await Promise.all( + eventTables.map((t) => + drizzle.query[t.tsName]!.findMany({ + where: eq(t.columns["resolverId"]!, parent.id), + }), + ), + ); + + return results + .flatMap((events, i) => + events.map((event) => ({ + ...event, + __typename: getSubgraphEntityName(eventTables[i]!.tsName), + })), + ) + .sort(sortByBlockNumber); + }, + }; + + /** + * ok back to ponder's regularly scheduled programming + */ + queryFields._meta = { type: GraphQLMeta, resolve: async (_source, _args, context) => { @@ -745,3 +912,13 @@ function getColumnTsName(column: Column) { const tableColumns = getTableColumns(column.table); return Object.entries(tableColumns).find(([_, c]) => c.name === column.name)![0]; } + +function getSubgraphEntityName(tsName: string) { + if (tsName === "newTTL") return "NewTTL"; + // if (tsName === "contentHashChanged") return "ContenthashChanged"; + return pascalCase(tsName); +} + +function sortByBlockNumber(a: { blockNumber: number }, b: { blockNumber: number }) { + return a.blockNumber - b.blockNumber; +} diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index c3d93fb..270f672 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -1,9 +1,9 @@ -import { Context } from "ponder:registry"; +import { Context, Event } from "ponder:registry"; import schema from "ponder:schema"; import { encodeLabelhash } from "@ensdomains/ensjs/utils"; import { Block } from "ponder"; import { type Hex, zeroAddress } from "viem"; -import { makeResolverId } from "../lib/ids"; +import { makeEventId, makeResolverId } from "../lib/ids"; import { ROOT_NODE, makeSubnodeNamehash } from "../lib/subname-helpers"; import { upsertAccount } from "../lib/upserts"; @@ -87,9 +87,8 @@ export const handleNewOwner = event, }: { context: Context; - event: { + event: Omit & { args: { node: Hex; label: Hex; owner: Hex }; - block: Block; }; }) => { const { label, node, owner } = event.args; @@ -137,6 +136,16 @@ export const handleNewOwner = if (owner === zeroAddress) { await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); } + + // DomainEvent + await context.db.insert(schema.newOwner).values({ + id: makeEventId(event), + blockNumber: event.block.number, + transactionID: event.transaction.hash, + parentDomainId: node, + domainId: subnode, + ownerId: owner, + }); }; export async function handleNewTTL({ diff --git a/src/lib/ids.ts b/src/lib/ids.ts index 9d9e763..d74678d 100644 --- a/src/lib/ids.ts +++ b/src/lib/ids.ts @@ -7,7 +7,7 @@ export const makeResolverId = (address: Address, node: Hex) => // https://github.com/ensdomains/ens-subgraph/blob/master/src/utils.ts#L5 // produces `blocknumber-logIndex` or `blocknumber-logindex-transferindex` -export const makeEventId = (event: Event, transferIndex?: number) => +export const makeEventId = (event: Pick, transferIndex?: number) => [event.block.number.toString(), event.log.logIndex.toString(), transferIndex?.toString()] .filter(Boolean) .join("-"); diff --git a/src/plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts index 4e5914f..f676796 100644 --- a/src/plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -21,7 +21,7 @@ export const pluginNamespace = createPluginNamespace(ownedName); // constrain the ponder indexing between the following start/end blocks // https://ponder.sh/0_6/docs/contracts-and-networks#block-range const START_BLOCK: ContractConfig["startBlock"] = undefined; -const END_BLOCK: ContractConfig["endBlock"] = 21_000_000; +const END_BLOCK: ContractConfig["endBlock"] = 4_000_000; const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";