From dcc37b33a18d6a291b133fe43a1420b703e17868 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 31 Dec 2024 06:56:09 +0100 Subject: [PATCH 01/30] feat(ponder-ens): introduce plugin architecture This change allows defining simple ponder plugins per supported ENS blockchain which can be loaded conditionally in the runtime. Requesting a chain to be indexed requires passing a chain ID in the `CHAINS_TO_INDEX` env var and comma-separated chain ID values. --- ponder.config.ts | 95 +-------- src/NameWrapper.ts | 0 src/ponder-ens-plugins/chain.ts | 5 + .../ethereum/abis}/BaseRegistrar.ts | 0 .../ethereum/abis}/EthRegistrarController.ts | 0 .../abis}/EthRegistrarControllerOld.ts | 0 .../ethereum/abis}/LegacyPublicResolver.ts | 0 .../ethereum/abis}/NameWrapper.ts | 0 .../ethereum/abis}/Registry.ts | 0 .../ethereum/abis}/Resolver.ts | 0 .../ethereum/handlers}/EthRegistrar.ts | 97 ++++++++-- .../ethereum/handlers/NameWrapper.ts | 5 + .../ethereum/handlers}/Registry.ts | 146 ++++++++------ .../ethereum/handlers}/Resolver.ts | 180 ++++++++++++------ .../ethereum/ponder.config.ts | 100 ++++++++++ .../ethereum/ponder.indexing.ts | 21 ++ src/ponder-ens-plugins/main.ts | 17 ++ src/ponder-ens-plugins/types.ts | 18 ++ 18 files changed, 452 insertions(+), 232 deletions(-) delete mode 100644 src/NameWrapper.ts create mode 100644 src/ponder-ens-plugins/chain.ts rename {abis => src/ponder-ens-plugins/ethereum/abis}/BaseRegistrar.ts (100%) rename {abis => src/ponder-ens-plugins/ethereum/abis}/EthRegistrarController.ts (100%) rename {abis => src/ponder-ens-plugins/ethereum/abis}/EthRegistrarControllerOld.ts (100%) rename {abis => src/ponder-ens-plugins/ethereum/abis}/LegacyPublicResolver.ts (100%) rename {abis => src/ponder-ens-plugins/ethereum/abis}/NameWrapper.ts (100%) rename {abis => src/ponder-ens-plugins/ethereum/abis}/Registry.ts (100%) rename {abis => src/ponder-ens-plugins/ethereum/abis}/Resolver.ts (100%) rename src/{ => ponder-ens-plugins/ethereum/handlers}/EthRegistrar.ts (56%) create mode 100644 src/ponder-ens-plugins/ethereum/handlers/NameWrapper.ts rename src/{ => ponder-ens-plugins/ethereum/handlers}/Registry.ts (63%) rename src/{ => ponder-ens-plugins/ethereum/handlers}/Resolver.ts (55%) create mode 100644 src/ponder-ens-plugins/ethereum/ponder.config.ts create mode 100644 src/ponder-ens-plugins/ethereum/ponder.indexing.ts create mode 100644 src/ponder-ens-plugins/main.ts create mode 100644 src/ponder-ens-plugins/types.ts diff --git a/ponder.config.ts b/ponder.config.ts index bca2b37c..cae10d01 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,98 +1,11 @@ -import { createConfig, factory, mergeAbis } from "ponder"; -import { http, getAbiItem } from "viem"; - -import { BaseRegistrar } from "./abis/BaseRegistrar"; -import { EthRegistrarController } from "./abis/EthRegistrarController"; -import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; -import { LegacyPublicResolver } from "./abis/LegacyPublicResolver"; -import { NameWrapper } from "./abis/NameWrapper"; -import { Registry } from "./abis/Registry"; -import { Resolver } from "./abis/Resolver"; - -// just for testing... -const END_BLOCK = 12_000_000; - -const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); - -const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; -const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; - -const BASE_REGISTRAR_ADDRESS = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85"; -const ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS = "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5"; -const ETH_REGISTRAR_CONTROLLER_ADDRESS = "0x253553366Da8546fC250F225fe3d25d0C782303b"; -const NAME_WRAPPER_ADDRESS = "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401"; +import { createConfig } from "ponder"; +import { config as ethereumConfig} from "./src/ponder-ens-plugins/ethereum/ponder.config"; export default createConfig({ networks: { - mainnet: { - chainId: 1, - transport: http(process.env.PONDER_RPC_URL_1), - }, + ...ethereumConfig.networks, }, contracts: { - RegistryOld: { - network: "mainnet", - abi: Registry, - address: REGISTRY_OLD_ADDRESS, - startBlock: 3327417, - endBlock: END_BLOCK, - }, - Registry: { - network: "mainnet", - abi: Registry, - address: REGISTRY_ADDRESS, - startBlock: 9380380, - endBlock: END_BLOCK, - }, - OldRegistryResolvers: { - network: "mainnet", - abi: RESOLVER_ABI, - address: factory({ - address: REGISTRY_OLD_ADDRESS, - event: getAbiItem({ abi: Registry, name: "NewResolver" }), - parameter: "resolver", - }), - startBlock: 9380380, - endBlock: END_BLOCK, - }, - Resolver: { - network: "mainnet", - abi: RESOLVER_ABI, - address: factory({ - address: REGISTRY_ADDRESS, - event: getAbiItem({ abi: Registry, name: "NewResolver" }), - parameter: "resolver", - }), - startBlock: 9380380, - endBlock: END_BLOCK, - }, - BaseRegistrar: { - network: "mainnet", - abi: BaseRegistrar, - address: BASE_REGISTRAR_ADDRESS, - startBlock: 9380410, - endBlock: END_BLOCK, - }, - EthRegistrarControllerOld: { - network: "mainnet", - abi: EthRegistrarControllerOld, - address: ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS, - startBlock: 9380471, - endBlock: END_BLOCK, - }, - EthRegistrarController: { - network: "mainnet", - abi: EthRegistrarController, - address: ETH_REGISTRAR_CONTROLLER_ADDRESS, - startBlock: Math.min(16925618, END_BLOCK), - endBlock: END_BLOCK, - }, - NameWrapper: { - network: "mainnet", - abi: NameWrapper, - address: NAME_WRAPPER_ADDRESS, - startBlock: Math.min(16925608, END_BLOCK), - endBlock: END_BLOCK, - }, + ...ethereumConfig.contracts, }, }); diff --git a/src/NameWrapper.ts b/src/NameWrapper.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/ponder-ens-plugins/chain.ts b/src/ponder-ens-plugins/chain.ts new file mode 100644 index 00000000..4327c356 --- /dev/null +++ b/src/ponder-ens-plugins/chain.ts @@ -0,0 +1,5 @@ +export function isChainIndexingActive(chainId: number) { + return process.env.CHAINS_TO_INDEX?.split(",") + .map((maybeChainId) => parseInt(maybeChainId, 10)) + .includes(chainId); +} diff --git a/abis/BaseRegistrar.ts b/src/ponder-ens-plugins/ethereum/abis/BaseRegistrar.ts similarity index 100% rename from abis/BaseRegistrar.ts rename to src/ponder-ens-plugins/ethereum/abis/BaseRegistrar.ts diff --git a/abis/EthRegistrarController.ts b/src/ponder-ens-plugins/ethereum/abis/EthRegistrarController.ts similarity index 100% rename from abis/EthRegistrarController.ts rename to src/ponder-ens-plugins/ethereum/abis/EthRegistrarController.ts diff --git a/abis/EthRegistrarControllerOld.ts b/src/ponder-ens-plugins/ethereum/abis/EthRegistrarControllerOld.ts similarity index 100% rename from abis/EthRegistrarControllerOld.ts rename to src/ponder-ens-plugins/ethereum/abis/EthRegistrarControllerOld.ts diff --git a/abis/LegacyPublicResolver.ts b/src/ponder-ens-plugins/ethereum/abis/LegacyPublicResolver.ts similarity index 100% rename from abis/LegacyPublicResolver.ts rename to src/ponder-ens-plugins/ethereum/abis/LegacyPublicResolver.ts diff --git a/abis/NameWrapper.ts b/src/ponder-ens-plugins/ethereum/abis/NameWrapper.ts similarity index 100% rename from abis/NameWrapper.ts rename to src/ponder-ens-plugins/ethereum/abis/NameWrapper.ts diff --git a/abis/Registry.ts b/src/ponder-ens-plugins/ethereum/abis/Registry.ts similarity index 100% rename from abis/Registry.ts rename to src/ponder-ens-plugins/ethereum/abis/Registry.ts diff --git a/abis/Resolver.ts b/src/ponder-ens-plugins/ethereum/abis/Resolver.ts similarity index 100% rename from abis/Resolver.ts rename to src/ponder-ens-plugins/ethereum/abis/Resolver.ts diff --git a/src/EthRegistrar.ts b/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts similarity index 56% rename from src/EthRegistrar.ts rename to src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts index 742a380f..b73b0fa8 100644 --- a/src/EthRegistrar.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts @@ -1,8 +1,14 @@ import { type Context, type Event, ponder } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; import type { Hex } from "viem"; -import { NAMEHASH_ETH, isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "./lib/ens-helpers"; -import { upsertAccount, upsertRegistration } from "./lib/upserts"; +import { + NAMEHASH_ETH, + isLabelValid, + makeSubnodeNamehash, + tokenIdToLabel, +} from "../../../lib/ens-helpers"; +import { upsertAccount, upsertRegistration } from "../../../lib/upserts"; +import { PonderEnsIndexingHandlerModule } from "../../types"; // all nodes referenced by EthRegistrar are parented to .eth const ROOT_NODE = NAMEHASH_ETH; @@ -11,7 +17,10 @@ const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds async function handleNameRegistered({ context, event, -}: { context: Context; event: Event<"BaseRegistrar:NameRegistered"> }) { +}: { + context: Context; + event: Event<"BaseRegistrar:NameRegistered">; +}) { const { id, owner, expires } = event.args; await upsertAccount(context, owner); @@ -47,7 +56,12 @@ async function handleNameRegisteredByControllerOld({ context: Context; event: Event<"EthRegistrarControllerOld:NameRegistered">; }) { - return await setNamePreimage(context, event.args.name, event.args.label, event.args.cost); + return await setNamePreimage( + context, + event.args.name, + event.args.label, + event.args.cost + ); } async function handleNameRegisteredByController({ @@ -61,7 +75,7 @@ async function handleNameRegisteredByController({ context, event.args.name, event.args.label, - event.args.baseCost + event.args.premium, + event.args.baseCost + event.args.premium ); } @@ -74,10 +88,20 @@ async function handleNameRenewedByController({ | Event<"EthRegistrarController:NameRenewed"> | Event<"EthRegistrarControllerOld:NameRenewed">; }) { - return await setNamePreimage(context, event.args.name, event.args.label, event.args.cost); + return await setNamePreimage( + context, + event.args.name, + event.args.label, + event.args.cost + ); } -async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { +async function setNamePreimage( + context: Context, + name: string, + label: Hex, + cost: bigint +) { if (!isLabelValid(name)) return; const node = makeSubnodeNamehash(ROOT_NODE, label); @@ -85,22 +109,31 @@ async function setNamePreimage(context: Context, name: string, label: Hex, cost: if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { - await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}.eth` }); + await context.db + .update(domains, { id: node }) + .set({ labelName: name, name: `${name}.eth` }); } - await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); + await context.db + .update(registrations, { id: label }) + .set({ labelName: name, cost }); } async function handleNameRenewed({ context, event, -}: { context: Context; event: Event<"BaseRegistrar:NameRenewed"> }) { +}: { + context: Context; + event: Event<"BaseRegistrar:NameRenewed">; +}) { const { id, expires } = event.args; const label = tokenIdToLabel(id); const node = makeSubnodeNamehash(ROOT_NODE, label); - await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); + await context.db + .update(registrations, { id: label }) + .set({ expiryDate: expires }); await context.db .update(domains, { id: node }) @@ -112,7 +145,10 @@ async function handleNameRenewed({ async function handleNameTransferred({ context, event, -}: { context: Context; event: Event<"BaseRegistrar:Transfer"> }) { +}: { + context: Context; + event: Event<"BaseRegistrar:Transfer">; +}) { const { tokenId, from, to } = event.args; await upsertAccount(context, to); @@ -123,19 +159,40 @@ async function handleNameTransferred({ const registration = await context.db.find(registrations, { id: label }); if (!registration) return; - await context.db.update(registrations, { id: label }).set({ registrantId: to }); + await context.db + .update(registrations, { id: label }) + .set({ registrantId: to }); await context.db.update(domains, { id: node }).set({ registrantId: to }); // TODO: log Event } -ponder.on("BaseRegistrar:NameRegistered", handleNameRegistered); -ponder.on("BaseRegistrar:NameRenewed", handleNameRenewed); -ponder.on("BaseRegistrar:Transfer", handleNameTransferred); +function initEthRegistrarHandlers() { + console.log("Indexing Ethereum ENS"); + ponder.on("BaseRegistrar:NameRegistered", handleNameRegistered); + ponder.on("BaseRegistrar:NameRenewed", handleNameRenewed); + ponder.on("BaseRegistrar:Transfer", handleNameTransferred); + + ponder.on( + "EthRegistrarControllerOld:NameRegistered", + handleNameRegisteredByControllerOld + ); + ponder.on( + "EthRegistrarControllerOld:NameRenewed", + handleNameRenewedByController + ); -ponder.on("EthRegistrarControllerOld:NameRegistered", handleNameRegisteredByControllerOld); -ponder.on("EthRegistrarControllerOld:NameRenewed", handleNameRenewedByController); + ponder.on( + "EthRegistrarController:NameRegistered", + handleNameRegisteredByController + ); + ponder.on( + "EthRegistrarController:NameRenewed", + handleNameRenewedByController + ); +} -ponder.on("EthRegistrarController:NameRegistered", handleNameRegisteredByController); -ponder.on("EthRegistrarController:NameRenewed", handleNameRenewedByController); +export const handlerModule: Readonly = { + attachHandlers: initEthRegistrarHandlers, +}; diff --git a/src/ponder-ens-plugins/ethereum/handlers/NameWrapper.ts b/src/ponder-ens-plugins/ethereum/handlers/NameWrapper.ts new file mode 100644 index 00000000..38905854 --- /dev/null +++ b/src/ponder-ens-plugins/ethereum/handlers/NameWrapper.ts @@ -0,0 +1,5 @@ +import { PonderEnsIndexingHandlerModule } from "../../types"; + +export const handlerModule: Readonly = { + attachHandlers: () => {}, +}; diff --git a/src/Registry.ts b/src/ponder-ens-plugins/ethereum/handlers/Registry.ts similarity index 63% rename from src/Registry.ts rename to src/ponder-ens-plugins/ethereum/handlers/Registry.ts index 9654e8f3..e606f52b 100644 --- a/src/Registry.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/Registry.ts @@ -2,9 +2,14 @@ import { type Context, type Event, ponder } from "ponder:registry"; import { resolvers } from "ponder:schema"; import { domains } from "ponder:schema"; import { type Hex, zeroAddress } from "viem"; -import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "./lib/ens-helpers"; -import { makeResolverId } from "./lib/ids"; -import { upsertAccount } from "./lib/upserts"; +import { + NAMEHASH_ZERO, + encodeLabelhash, + makeSubnodeNamehash, +} from "../../../lib/ens-helpers"; +import { makeResolverId } from "../../../lib/ids"; +import { upsertAccount } from "../../../lib/upserts"; +import { PonderEnsIndexingHandlerModule } from "../../types"; // a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not async function isDomainMigrated(context: Context, node: Hex) { @@ -14,13 +19,18 @@ async function isDomainMigrated(context: Context, node: Hex) { function isDomainEmpty(domain: typeof domains.$inferSelect) { return ( - domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 + domain.resolverId === null && + domain.ownerId === zeroAddress && + domain.subdomainCount === 0 ); } // a more accurate name for the following function // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context: Context, + node: Hex +) { const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error(`Domain not found: ${node}`); @@ -31,7 +41,10 @@ async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Con .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); + return recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.parentId + ); } } @@ -81,7 +94,9 @@ const _handleNewOwner = let domain = await context.db.find(domains, { id: subnode }); if (domain) { // if the domain already exists, this is just an update of the owner record. - await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); + await context.db + .update(domains, { id: domain.id }) + .set({ ownerId: owner, isMigrated }); } else { // otherwise create the domain domain = await context.db.insert(domains).values({ @@ -107,12 +122,17 @@ const _handleNewOwner = const labelName = encodeLabelhash(label); const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - await context.db.update(domains, { id: domain.id }).set({ name, labelName }); + await context.db + .update(domains, { id: domain.id }) + .set({ name, labelName }); } // garbage collect newly 'empty' domain iff necessary if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); + await recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.id + ); } }; @@ -171,55 +191,61 @@ async function _handleNewResolver({ // TODO: log DomainEvent } -// setup on old registry -ponder.on("RegistryOld:setup", async ({ context }) => { - // ensure we have an account for the zeroAddress - await upsertAccount(context, zeroAddress); - - // ensure we have a root Domain, owned by the zeroAddress - await context.db.insert(domains).values({ - id: NAMEHASH_ZERO, - ownerId: zeroAddress, - createdAt: 0n, - isMigrated: false, +function initRegistryHandlers() { + // setup on old registry + ponder.on("RegistryOld:setup", async ({ context }) => { + // ensure we have an account for the zeroAddress + await upsertAccount(context, zeroAddress); + + // ensure we have a root Domain, owned by the zeroAddress + await context.db.insert(domains).values({ + id: NAMEHASH_ZERO, + ownerId: zeroAddress, + createdAt: 0n, + isMigrated: false, + }); + }); + + // old registry functions are proxied to the current handlers + // iff the domain has not yet been migrated + ponder.on("RegistryOld:NewOwner", async ({ context, event }) => { + const node = makeSubnodeNamehash(event.args.node, event.args.label); + const isMigrated = await isDomainMigrated(context, node); + if (isMigrated) return; + return _handleNewOwner(false)({ context, event }); }); -}); - -// old registry functions are proxied to the current handlers -// iff the domain has not yet been migrated -ponder.on("RegistryOld:NewOwner", async ({ context, event }) => { - const node = makeSubnodeNamehash(event.args.node, event.args.label); - const isMigrated = await isDomainMigrated(context, node); - if (isMigrated) return; - return _handleNewOwner(false)({ context, event }); -}); - -ponder.on("RegistryOld:NewResolver", async ({ context, event }) => { - // NOTE: the subgraph makes an exception for the root node here - // but i don't know that that's necessary, as in ponder our root node starts out - // unmigrated and once the NewOwner event is emitted by the new registry, - // the root will be considered migrated - // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 - - // otherwise, only handle iff not migrated - const isMigrated = await isDomainMigrated(context, event.args.node); - if (isMigrated) return; - return _handleNewResolver({ context, event }); -}); - -ponder.on("RegistryOld:NewTTL", async ({ context, event }) => { - const isMigrated = await isDomainMigrated(context, event.args.node); - if (isMigrated) return; - return _handleNewTTL({ context, event }); -}); - -ponder.on("RegistryOld:Transfer", async ({ context, event }) => { - const isMigrated = await isDomainMigrated(context, event.args.node); - if (isMigrated) return; - return _handleTransfer({ context, event }); -}); - -ponder.on("Registry:NewOwner", _handleNewOwner(true)); -ponder.on("Registry:NewResolver", _handleNewResolver); -ponder.on("Registry:NewTTL", _handleNewTTL); -ponder.on("Registry:Transfer", _handleTransfer); + + ponder.on("RegistryOld:NewResolver", async ({ context, event }) => { + // NOTE: the subgraph makes an exception for the root node here + // but i don't know that that's necessary, as in ponder our root node starts out + // unmigrated and once the NewOwner event is emitted by the new registry, + // the root will be considered migrated + // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 + + // otherwise, only handle iff not migrated + const isMigrated = await isDomainMigrated(context, event.args.node); + if (isMigrated) return; + return _handleNewResolver({ context, event }); + }); + + ponder.on("RegistryOld:NewTTL", async ({ context, event }) => { + const isMigrated = await isDomainMigrated(context, event.args.node); + if (isMigrated) return; + return _handleNewTTL({ context, event }); + }); + + ponder.on("RegistryOld:Transfer", async ({ context, event }) => { + const isMigrated = await isDomainMigrated(context, event.args.node); + if (isMigrated) return; + return _handleTransfer({ context, event }); + }); + + ponder.on("Registry:NewOwner", _handleNewOwner(true)); + ponder.on("Registry:NewResolver", _handleNewResolver); + ponder.on("Registry:NewTTL", _handleNewTTL); + ponder.on("Registry:Transfer", _handleTransfer); +} + +export const handlerModule: Readonly = { + attachHandlers: initRegistryHandlers, +}; diff --git a/src/Resolver.ts b/src/ponder-ens-plugins/ethereum/handlers/Resolver.ts similarity index 55% rename from src/Resolver.ts rename to src/ponder-ens-plugins/ethereum/handlers/Resolver.ts index 912c998c..1462f2b2 100644 --- a/src/Resolver.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/Resolver.ts @@ -1,8 +1,9 @@ import { type Context, type Event, ponder } from "ponder:registry"; import { domains, resolvers } from "ponder:schema"; -import { hasNullByte, uniq } from "./lib/helpers"; -import { makeResolverId } from "./lib/ids"; -import { upsertAccount, upsertResolver } from "./lib/upserts"; +import { hasNullByte, uniq } from "../../../lib/helpers"; +import { makeResolverId } from "../../../lib/ids"; +import { upsertAccount, upsertResolver } from "../../../lib/upserts"; +import { PonderEnsIndexingHandlerModule } from "../../types"; // there is a legacy resolver abi with different TextChanged events. // luckily the subgraph doesn't care about the value parameter so we can use a union @@ -16,7 +17,10 @@ type AnyTextChangedEvent = async function _handleAddrChanged({ context, event, -}: { context: Context; event: Event<"Resolver:AddrChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:AddrChanged">; +}) { const { a: address, node } = event.args; await upsertAccount(context, address); @@ -31,7 +35,9 @@ async function _handleAddrChanged({ // materialize the resolved add to the domain iff this resolver is active const domain = await context.db.find(domains, { id: node }); if (domain?.resolverId === id) { - await context.db.update(domains, { id: node }).set({ resolvedAddress: address }); + await context.db + .update(domains, { id: node }) + .set({ resolvedAddress: address }); } // TODO: log ResolverEvent @@ -40,7 +46,10 @@ async function _handleAddrChanged({ async function _handleAddressChanged({ context, event, -}: { context: Context; event: Event<"Resolver:AddressChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:AddressChanged">; +}) { const { node, coinType, newAddress } = event.args; await upsertAccount(context, newAddress); @@ -62,7 +71,10 @@ async function _handleAddressChanged({ async function _handleNameChanged({ context, event, -}: { context: Context; event: Event<"Resolver:NameChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:NameChanged">; +}) { const { node, name } = event.args; if (hasNullByte(name)) return; @@ -79,7 +91,10 @@ async function _handleNameChanged({ async function _handleABIChanged({ context, event, -}: { context: Context; event: Event<"Resolver:ABIChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:ABIChanged">; +}) { const { node } = event.args; const id = makeResolverId(node, event.log.address); const resolver = await upsertResolver(context, { @@ -94,7 +109,10 @@ async function _handleABIChanged({ async function _handlePubkeyChanged({ context, event, -}: { context: Context; event: Event<"Resolver:PubkeyChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:PubkeyChanged">; +}) { const { node } = event.args; const id = makeResolverId(node, event.log.address); const resolver = await upsertResolver(context, { @@ -122,7 +140,9 @@ async function _handleTextChanged({ }); // upsert new key - await context.db.update(resolvers, { id }).set({ texts: uniq([...resolver.texts, key]) }); + await context.db + .update(resolvers, { id }) + .set({ texts: uniq([...resolver.texts, key]) }); // TODO: log ResolverEvent } @@ -130,7 +150,10 @@ async function _handleTextChanged({ async function _handleContenthashChanged({ context, event, -}: { context: Context; event: Event<"Resolver:ContenthashChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:ContenthashChanged">; +}) { const { node, hash } = event.args; const id = makeResolverId(node, event.log.address); await upsertResolver(context, { @@ -146,7 +169,10 @@ async function _handleContenthashChanged({ async function _handleInterfaceChanged({ context, event, -}: { context: Context; event: Event<"Resolver:InterfaceChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:InterfaceChanged">; +}) { const { node } = event.args; const id = makeResolverId(node, event.log.address); await upsertResolver(context, { @@ -161,7 +187,10 @@ async function _handleInterfaceChanged({ async function _handleAuthorisationChanged({ context, event, -}: { context: Context; event: Event<"Resolver:AuthorisationChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:AuthorisationChanged">; +}) { const { node } = event.args; const id = makeResolverId(node, event.log.address); await upsertResolver(context, { @@ -176,7 +205,10 @@ async function _handleAuthorisationChanged({ async function _handleVersionChanged({ context, event, -}: { context: Context; event: Event<"Resolver:VersionChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:VersionChanged">; +}) { // a version change nulls out the resolver const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -185,7 +217,9 @@ async function _handleVersionChanged({ // materialize the Domain's resolvedAddress field if (domain.resolverId === id) { - await context.db.update(domains, { id: node }).set({ resolvedAddress: null }); + await context.db + .update(domains, { id: node }) + .set({ resolvedAddress: null }); } // clear out the resolver's info @@ -202,64 +236,88 @@ async function _handleVersionChanged({ async function _handleDNSRecordChanged({ context, event, -}: { context: Context; event: Event<"Resolver:DNSRecordChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:DNSRecordChanged">; +}) { // subgraph ignores } async function _handleDNSRecordDeleted({ context, event, -}: { context: Context; event: Event<"Resolver:DNSRecordDeleted"> }) { +}: { + context: Context; + event: Event<"Resolver:DNSRecordDeleted">; +}) { // subgraph ignores } async function _handleDNSZonehashChanged({ context, event, -}: { context: Context; event: Event<"Resolver:DNSZonehashChanged"> }) { +}: { + context: Context; + event: Event<"Resolver:DNSZonehashChanged">; +}) { // subgraph ignores } -// Old registry handlers -ponder.on("OldRegistryResolvers:AddrChanged", _handleAddrChanged); -ponder.on("OldRegistryResolvers:AddressChanged", _handleAddressChanged); -ponder.on("OldRegistryResolvers:NameChanged", _handleNameChanged); -ponder.on("OldRegistryResolvers:ABIChanged", _handleABIChanged); -ponder.on("OldRegistryResolvers:PubkeyChanged", _handlePubkeyChanged); -ponder.on( - "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", - _handleTextChanged, -); -ponder.on( - "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", - _handleTextChanged, -); -ponder.on("OldRegistryResolvers:ContenthashChanged", _handleContenthashChanged); -ponder.on("OldRegistryResolvers:InterfaceChanged", _handleInterfaceChanged); -ponder.on("OldRegistryResolvers:AuthorisationChanged", _handleAuthorisationChanged); -ponder.on("OldRegistryResolvers:VersionChanged", _handleVersionChanged); -ponder.on("OldRegistryResolvers:DNSRecordChanged", _handleDNSRecordChanged); -ponder.on("OldRegistryResolvers:DNSRecordDeleted", _handleDNSRecordDeleted); -ponder.on("OldRegistryResolvers:DNSZonehashChanged", _handleDNSZonehashChanged); - -// New registry handlers -ponder.on("Resolver:AddrChanged", _handleAddrChanged); -ponder.on("Resolver:AddressChanged", _handleAddressChanged); -ponder.on("Resolver:NameChanged", _handleNameChanged); -ponder.on("Resolver:ABIChanged", _handleABIChanged); -ponder.on("Resolver:PubkeyChanged", _handlePubkeyChanged); -ponder.on( - "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", - _handleTextChanged, -); -ponder.on( - "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", - _handleTextChanged, -); -ponder.on("Resolver:ContenthashChanged", _handleContenthashChanged); -ponder.on("Resolver:InterfaceChanged", _handleInterfaceChanged); -ponder.on("Resolver:AuthorisationChanged", _handleAuthorisationChanged); -ponder.on("Resolver:VersionChanged", _handleVersionChanged); -ponder.on("Resolver:DNSRecordChanged", _handleDNSRecordChanged); -ponder.on("Resolver:DNSRecordDeleted", _handleDNSRecordDeleted); -ponder.on("Resolver:DNSZonehashChanged", _handleDNSZonehashChanged); +export function initResolverHandlers() { + // Old registry handlers + ponder.on("OldRegistryResolvers:AddrChanged", _handleAddrChanged); + ponder.on("OldRegistryResolvers:AddressChanged", _handleAddressChanged); + ponder.on("OldRegistryResolvers:NameChanged", _handleNameChanged); + ponder.on("OldRegistryResolvers:ABIChanged", _handleABIChanged); + ponder.on("OldRegistryResolvers:PubkeyChanged", _handlePubkeyChanged); + ponder.on( + "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", + _handleTextChanged + ); + ponder.on( + "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", + _handleTextChanged + ); + ponder.on( + "OldRegistryResolvers:ContenthashChanged", + _handleContenthashChanged + ); + ponder.on("OldRegistryResolvers:InterfaceChanged", _handleInterfaceChanged); + ponder.on( + "OldRegistryResolvers:AuthorisationChanged", + _handleAuthorisationChanged + ); + ponder.on("OldRegistryResolvers:VersionChanged", _handleVersionChanged); + ponder.on("OldRegistryResolvers:DNSRecordChanged", _handleDNSRecordChanged); + ponder.on("OldRegistryResolvers:DNSRecordDeleted", _handleDNSRecordDeleted); + ponder.on( + "OldRegistryResolvers:DNSZonehashChanged", + _handleDNSZonehashChanged + ); + + // New registry handlers + ponder.on("Resolver:AddrChanged", _handleAddrChanged); + ponder.on("Resolver:AddressChanged", _handleAddressChanged); + ponder.on("Resolver:NameChanged", _handleNameChanged); + ponder.on("Resolver:ABIChanged", _handleABIChanged); + ponder.on("Resolver:PubkeyChanged", _handlePubkeyChanged); + ponder.on( + "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", + _handleTextChanged + ); + ponder.on( + "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", + _handleTextChanged + ); + ponder.on("Resolver:ContenthashChanged", _handleContenthashChanged); + ponder.on("Resolver:InterfaceChanged", _handleInterfaceChanged); + ponder.on("Resolver:AuthorisationChanged", _handleAuthorisationChanged); + ponder.on("Resolver:VersionChanged", _handleVersionChanged); + ponder.on("Resolver:DNSRecordChanged", _handleDNSRecordChanged); + ponder.on("Resolver:DNSRecordDeleted", _handleDNSRecordDeleted); + ponder.on("Resolver:DNSZonehashChanged", _handleDNSZonehashChanged); +} + +export const handlerModule: Readonly = { + attachHandlers: initResolverHandlers, +}; diff --git a/src/ponder-ens-plugins/ethereum/ponder.config.ts b/src/ponder-ens-plugins/ethereum/ponder.config.ts new file mode 100644 index 00000000..b79293df --- /dev/null +++ b/src/ponder-ens-plugins/ethereum/ponder.config.ts @@ -0,0 +1,100 @@ +import { factory, mergeAbis } from "ponder"; +import { http, getAbiItem } from "viem"; + +import { BaseRegistrar } from "./abis/BaseRegistrar"; +import { EthRegistrarController } from "./abis/EthRegistrarController"; +import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; +import { LegacyPublicResolver } from "./abis/LegacyPublicResolver"; +import { NameWrapper } from "./abis/NameWrapper"; +import { Registry } from "./abis/Registry"; +import { Resolver } from "./abis/Resolver"; + +// just for testing... +const END_BLOCK = 12_000_000; + +const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); + +const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; +const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; + +const BASE_REGISTRAR_ADDRESS = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85"; +const ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS = + "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5"; +const ETH_REGISTRAR_CONTROLLER_ADDRESS = + "0x253553366Da8546fC250F225fe3d25d0C782303b"; +const NAME_WRAPPER_ADDRESS = "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401"; + +export const config = Object.freeze({ + networks: { + mainnet: { + chainId: 1, + transport: http(process.env.PONDER_RPC_URL_1), + }, + }, + contracts: { + RegistryOld: { + network: "mainnet", + abi: Registry, + address: REGISTRY_OLD_ADDRESS, + startBlock: 3327417, + endBlock: END_BLOCK, + }, + Registry: { + network: "mainnet", + abi: Registry, + address: REGISTRY_ADDRESS, + startBlock: 9380380, + endBlock: END_BLOCK, + }, + OldRegistryResolvers: { + network: "mainnet", + abi: RESOLVER_ABI, + address: factory({ + address: REGISTRY_OLD_ADDRESS, + event: getAbiItem({ abi: Registry, name: "NewResolver" }), + parameter: "resolver", + }), + startBlock: 9380380, + endBlock: END_BLOCK, + }, + Resolver: { + network: "mainnet", + abi: RESOLVER_ABI, + address: factory({ + address: REGISTRY_ADDRESS, + event: getAbiItem({ abi: Registry, name: "NewResolver" }), + parameter: "resolver", + }), + startBlock: 9380380, + endBlock: END_BLOCK, + }, + BaseRegistrar: { + network: "mainnet", + abi: BaseRegistrar, + address: BASE_REGISTRAR_ADDRESS, + startBlock: 9380410, + endBlock: END_BLOCK, + }, + EthRegistrarControllerOld: { + network: "mainnet", + abi: EthRegistrarControllerOld, + address: ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS, + startBlock: 9380471, + endBlock: END_BLOCK, + }, + EthRegistrarController: { + network: "mainnet", + abi: EthRegistrarController, + address: ETH_REGISTRAR_CONTROLLER_ADDRESS, + startBlock: Math.min(16925618, END_BLOCK), + endBlock: END_BLOCK, + }, + NameWrapper: { + network: "mainnet", + abi: NameWrapper, + address: NAME_WRAPPER_ADDRESS, + startBlock: Math.min(16925608, END_BLOCK), + endBlock: END_BLOCK, + }, + }, +} as const); diff --git a/src/ponder-ens-plugins/ethereum/ponder.indexing.ts b/src/ponder-ens-plugins/ethereum/ponder.indexing.ts new file mode 100644 index 00000000..bedd0227 --- /dev/null +++ b/src/ponder-ens-plugins/ethereum/ponder.indexing.ts @@ -0,0 +1,21 @@ +import { mainnet } from "viem/chains"; +import { isChainIndexingActive } from "../chain"; +import { PonderEnsModule } from "../types"; + +export default { + get canActivate() { + return isChainIndexingActive(mainnet.id); + }, + + async activate() { + const ponderIndexingModules = await Promise.all([ + import("./handlers/Registry"), + import("./handlers/EthRegistrar"), + import("./handlers/Resolver"), + ]); + + for (const { handlerModule } of ponderIndexingModules) { + handlerModule.attachHandlers(); + } + }, +} as PonderEnsModule; diff --git a/src/ponder-ens-plugins/main.ts b/src/ponder-ens-plugins/main.ts new file mode 100644 index 00000000..e0095d11 --- /dev/null +++ b/src/ponder-ens-plugins/main.ts @@ -0,0 +1,17 @@ +import ethereumPlugin from "./ethereum/ponder.indexing"; + +/** + * Main entry point for the Ponder ENS plugins. + * It tries to activate all the enlisted plugins. + */ +function main() { + const plugins = [ethereumPlugin]; + + for (const plugin of plugins) { + if (plugin.canActivate) { + plugin.activate(); + } + } +} + +main(); diff --git a/src/ponder-ens-plugins/types.ts b/src/ponder-ens-plugins/types.ts new file mode 100644 index 00000000..0d262a1a --- /dev/null +++ b/src/ponder-ens-plugins/types.ts @@ -0,0 +1,18 @@ +export interface PonderEnsModule { + /** + * Check if the module can be activated + */ + get canActivate(): boolean; + + /** + * Runs the module activation logic + */ + activate(): Promise; +} + +export interface PonderEnsIndexingHandlerModule { + /** + * Attaches indexing handlers for the module + */ + attachHandlers(): void; +} From a5bfb0059226b8d20feb063bee193b654ae91d37 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 3 Jan 2025 14:12:08 +0100 Subject: [PATCH 02/30] feat(ponder-ens-plugin): update abis and contract configs --- .env.local.example | 15 + ponder.config.ts | 16 +- src/ponder-ens-plugins/base/README.md | 9 + .../base/abis/BaseRegistrar.ts | 562 ++++++++++++++ .../base/abis/L1Resolver.ts | 467 ++++++++++++ .../base/abis/L2Resolver.ts | 699 ++++++++++++++++++ .../base/abis/RegistrarController.ts | 647 ++++++++++++++++ src/ponder-ens-plugins/base/abis/Registry.ts | 199 +++++ .../base/abis/ReverseRegistrar.ts | 251 +++++++ .../base/handlers/Registrar.ts | 172 +++++ .../base/handlers/Registry.ts | 233 ++++++ .../base/handlers/Resolver.ts | 258 +++++++ src/ponder-ens-plugins/base/ponder.config.ts | 91 +++ .../base/ponder.indexing.ts | 21 + src/ponder-ens-plugins/chain.ts | 22 +- .../ethereum/handlers/EthRegistrar.ts | 80 +- .../ethereum/handlers/Registry.ts | 146 ++-- .../ethereum/handlers/Resolver.ts | 132 ++-- .../ethereum/ponder.config.ts | 28 +- src/ponder-ens-plugins/main.ts | 3 +- 20 files changed, 3829 insertions(+), 222 deletions(-) create mode 100644 .env.local.example create mode 100644 src/ponder-ens-plugins/base/README.md create mode 100644 src/ponder-ens-plugins/base/abis/BaseRegistrar.ts create mode 100644 src/ponder-ens-plugins/base/abis/L1Resolver.ts create mode 100644 src/ponder-ens-plugins/base/abis/L2Resolver.ts create mode 100644 src/ponder-ens-plugins/base/abis/RegistrarController.ts create mode 100644 src/ponder-ens-plugins/base/abis/Registry.ts create mode 100644 src/ponder-ens-plugins/base/abis/ReverseRegistrar.ts create mode 100644 src/ponder-ens-plugins/base/handlers/Registrar.ts create mode 100644 src/ponder-ens-plugins/base/handlers/Registry.ts create mode 100644 src/ponder-ens-plugins/base/handlers/Resolver.ts create mode 100644 src/ponder-ens-plugins/base/ponder.config.ts create mode 100644 src/ponder-ens-plugins/base/ponder.indexing.ts diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 00000000..bb3f0599 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,15 @@ +# RPC urls, follow the format RPC_URL_{chainId}={rpcUrl} + +PONDER_RPC_URL_1=https://ethereum-rpc.publicnode.com +PONDER_RPC_URL_8453=https://base-rpc.publicnode.com +PONDER_RPC_URL_59144=https://linea-rpc.publicnode.com + +# ponder indexer ens deployment configuration + +ENS_DEPLOYMENT_CHAIN_ID=1 + +# ponder indexer database configuration + +## one schema name per chain ID, i.e. ponder_ens_${ENS_DEPLOYMENT_CHAIN_ID} +DATABASE_SCHEMA=ponder_ens_1 +DATABASE_URL=postgresql://dbuser:abcd1234@localhost:5432/my_database diff --git a/ponder.config.ts b/ponder.config.ts index cae10d01..88eea4eb 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,11 +1,25 @@ import { createConfig } from "ponder"; -import { config as ethereumConfig} from "./src/ponder-ens-plugins/ethereum/ponder.config"; +import { config as baseConfig } from "./src/ponder-ens-plugins/base/ponder.config"; +import { config as ethereumConfig } from "./src/ponder-ens-plugins/ethereum/ponder.config"; + +console.log({ + networks: { + ...baseConfig.networks, + ...ethereumConfig.networks, + }, + contracts: { + ...baseConfig.contracts, + ...ethereumConfig.contracts, + }, +}) export default createConfig({ networks: { + ...baseConfig.networks, ...ethereumConfig.networks, }, contracts: { + ...baseConfig.contracts, ...ethereumConfig.contracts, }, }); diff --git a/src/ponder-ens-plugins/base/README.md b/src/ponder-ens-plugins/base/README.md new file mode 100644 index 00000000..9af78654 --- /dev/null +++ b/src/ponder-ens-plugins/base/README.md @@ -0,0 +1,9 @@ +# Base ENS plugin for Ponder indexer + +This plugin contains configuration required to run blockchain indexing with [the Ponder app](https://ponder.sh/). It includes relevant ABI files, contract addresses and the block numbers those contracts were deployed at on a selected network. + +## Architecture + +The Base implementation for ENS protocol has custom naming convention, which was described here: +https://github.com/base-org/basenames?tab=readme-ov-file#architecture + diff --git a/src/ponder-ens-plugins/base/abis/BaseRegistrar.ts b/src/ponder-ens-plugins/base/abis/BaseRegistrar.ts new file mode 100644 index 00000000..ecfd073c --- /dev/null +++ b/src/ponder-ens-plugins/base/abis/BaseRegistrar.ts @@ -0,0 +1,562 @@ +export const BaseRegistrar = [ + { + inputs: [ + { internalType: "contract ENS", name: "registry_", type: "address" }, + { internalType: "address", name: "owner_", type: "address" }, + { internalType: "bytes32", name: "baseNode_", type: "bytes32" }, + { internalType: "string", name: "baseURI_", type: "string" }, + { internalType: "string", name: "collectionURI_", type: "string" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "AccountBalanceOverflow", type: "error" }, + { inputs: [], name: "AlreadyInitialized", type: "error" }, + { inputs: [], name: "BalanceQueryForZeroAddress", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "Expired", + type: "error", + }, + { inputs: [], name: "NewOwnerIsZeroAddress", type: "error" }, + { inputs: [], name: "NoHandoverRequest", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "NonexistentToken", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "tokenId", type: "uint256" }, + { internalType: "address", name: "sender", type: "address" }, + ], + name: "NotApprovedOwner", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "NotAvailable", + type: "error", + }, + { inputs: [], name: "NotOwnerNorApproved", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "NotRegisteredOrInGrace", + type: "error", + }, + { inputs: [], name: "OnlyController", type: "error" }, + { inputs: [], name: "RegistrarNotLive", type: "error" }, + { inputs: [], name: "TokenAlreadyExists", type: "error" }, + { inputs: [], name: "TokenDoesNotExist", type: "error" }, + { inputs: [], name: "TransferFromIncorrectOwner", type: "error" }, + { inputs: [], name: "TransferToNonERC721ReceiverImplementer", type: "error" }, + { inputs: [], name: "TransferToZeroAddress", type: "error" }, + { inputs: [], name: "Unauthorized", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { indexed: true, internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "isApproved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "_fromTokenId", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "_toTokenId", + type: "uint256", + }, + ], + name: "BatchMetadataUpdate", + type: "event", + }, + { anonymous: false, inputs: [], name: "ContractURIUpdated", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "controller", + type: "address", + }, + ], + name: "ControllerAdded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "controller", + type: "address", + }, + ], + name: "ControllerRemoved", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "uint256", name: "id", type: "uint256" }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameRegistered", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "uint256", name: "id", type: "uint256" }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + { + indexed: false, + internalType: "address", + name: "resolver", + type: "address", + }, + { indexed: false, internalType: "uint64", name: "ttl", type: "uint64" }, + ], + name: "NameRegisteredWithRecord", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "uint256", name: "id", type: "uint256" }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameRenewed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverCanceled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "from", type: "address" }, + { indexed: true, internalType: "address", name: "to", type: "address" }, + { indexed: true, internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [{ internalType: "address", name: "controller", type: "address" }], + name: "addController", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "approve", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "baseNode", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "cancelOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "completeOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "contractURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "controller", type: "address" }], + name: "controllers", + outputs: [{ internalType: "bool", name: "isApproved", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "getApproved", + outputs: [{ internalType: "address", name: "result", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "operator", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "result", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "isAvailable", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "id", type: "uint256" }], + name: "nameExpires", + outputs: [{ internalType: "uint256", name: "expiry", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "result", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "ownerOf", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "ownershipHandoverExpiresAt", + outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "owner", type: "address" }, + ], + name: "reclaim", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "register", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "registerOnly", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "uint64", name: "ttl", type: "uint64" }, + ], + name: "registerWithRecord", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "registry", + outputs: [{ internalType: "contract ENS", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "controller", type: "address" }], + name: "removeController", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "renew", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "requestOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "isApproved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "baseURI_", type: "string" }], + name: "setBaseTokenURI", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "collectionURI_", type: "string" }, + ], + name: "setContractURI", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "resolver", type: "address" }], + name: "setResolver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceID", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }], + name: "tokenURI", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "transferFrom", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/src/ponder-ens-plugins/base/abis/L1Resolver.ts b/src/ponder-ens-plugins/base/abis/L1Resolver.ts new file mode 100644 index 00000000..ba2c1b68 --- /dev/null +++ b/src/ponder-ens-plugins/base/abis/L1Resolver.ts @@ -0,0 +1,467 @@ +export const L1Resolver = [ + { + inputs: [ + { + internalType: "string", + name: "url_", + type: "string", + }, + { + internalType: "address[]", + name: "signers_", + type: "address[]", + }, + { + internalType: "address", + name: "owner_", + type: "address", + }, + { + internalType: "address", + name: "rootResolver_", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "AlreadyInitialized", + type: "error", + }, + { + inputs: [], + name: "InvalidSigner", + type: "error", + }, + { + inputs: [], + name: "NewOwnerIsZeroAddress", + type: "error", + }, + { + inputs: [], + name: "NoHandoverRequest", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "string[]", + name: "urls", + type: "string[]", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + { + internalType: "bytes4", + name: "callbackFunction", + type: "bytes4", + }, + { + internalType: "bytes", + name: "extraData", + type: "bytes", + }, + ], + name: "OffchainLookup", + type: "error", + }, + { + inputs: [], + name: "SignatureExpired", + type: "error", + }, + { + inputs: [], + name: "Unauthorized", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address[]", + name: "signers", + type: "address[]", + }, + ], + name: "AddedSigners", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverCanceled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "signer", + type: "address", + }, + ], + name: "RemovedSigner", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "resolver", + type: "address", + }, + ], + name: "RootResolverChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "string", + name: "newUrl", + type: "string", + }, + ], + name: "UrlChanged", + type: "event", + }, + { + stateMutability: "nonpayable", + type: "fallback", + }, + { + inputs: [ + { + internalType: "address[]", + name: "signers_", + type: "address[]", + }, + ], + name: "addSigners", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "cancelOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "completeOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "uint64", + name: "expires", + type: "uint64", + }, + { + internalType: "bytes", + name: "request", + type: "bytes", + }, + { + internalType: "bytes", + name: "result", + type: "bytes", + }, + ], + name: "makeSignatureHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "result", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "ownershipHandoverExpiresAt", + outputs: [ + { + internalType: "uint256", + name: "result", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signer", + type: "address", + }, + ], + name: "removeSigner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "requestOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "name", + type: "bytes", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "resolve", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes", + name: "response", + type: "bytes", + }, + { + internalType: "bytes", + name: "extraData", + type: "bytes", + }, + ], + name: "resolveWithProof", + outputs: [ + { + internalType: "bytes", + name: "", + type: "bytes", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rootResolver", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "rootResolver_", + type: "address", + }, + ], + name: "setRootResolver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "url_", + type: "string", + }, + ], + name: "setUrl", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "signer", + type: "address", + }, + ], + name: "signers", + outputs: [ + { + internalType: "bool", + name: "isApproved", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceID", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "url", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/src/ponder-ens-plugins/base/abis/L2Resolver.ts b/src/ponder-ens-plugins/base/abis/L2Resolver.ts new file mode 100644 index 00000000..1219260d --- /dev/null +++ b/src/ponder-ens-plugins/base/abis/L2Resolver.ts @@ -0,0 +1,699 @@ +export const L2Resolver = [ + { + inputs: [ + { internalType: "contract ENS", name: "ens_", type: "address" }, + { + internalType: "address", + name: "registrarController_", + type: "address", + }, + { internalType: "address", name: "reverseRegistrar_", type: "address" }, + { internalType: "address", name: "owner_", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "AlreadyInitialized", type: "error" }, + { inputs: [], name: "CantSetSelfAsDelegate", type: "error" }, + { inputs: [], name: "CantSetSelfAsOperator", type: "error" }, + { inputs: [], name: "NewOwnerIsZeroAddress", type: "error" }, + { inputs: [], name: "NoHandoverRequest", type: "error" }, + { inputs: [], name: "Unauthorized", type: "error" }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: true, + internalType: "uint256", + name: "contentType", + type: "uint256", + }, + ], + name: "ABIChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "address", name: "a", type: "address" }, + ], + name: "AddrChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: false, + internalType: "uint256", + name: "coinType", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "newAddress", + type: "bytes", + }, + ], + name: "AddressChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { indexed: false, internalType: "bool", name: "approved", type: "bool" }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "owner", + type: "address", + }, + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: true, + internalType: "address", + name: "delegate", + type: "address", + }, + { indexed: true, internalType: "bool", name: "approved", type: "bool" }, + ], + name: "Approved", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "bytes", name: "hash", type: "bytes" }, + ], + name: "ContenthashChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "bytes", name: "name", type: "bytes" }, + { + indexed: false, + internalType: "uint16", + name: "resource", + type: "uint16", + }, + { indexed: false, internalType: "bytes", name: "record", type: "bytes" }, + ], + name: "DNSRecordChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "bytes", name: "name", type: "bytes" }, + { + indexed: false, + internalType: "uint16", + name: "resource", + type: "uint16", + }, + ], + name: "DNSRecordDeleted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: false, + internalType: "bytes", + name: "lastzonehash", + type: "bytes", + }, + { + indexed: false, + internalType: "bytes", + name: "zonehash", + type: "bytes", + }, + ], + name: "DNSZonehashChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: true, + internalType: "bytes4", + name: "interfaceID", + type: "bytes4", + }, + { + indexed: false, + internalType: "address", + name: "implementer", + type: "address", + }, + ], + name: "InterfaceChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "string", name: "name", type: "string" }, + ], + name: "NameChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverCanceled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "bytes32", name: "x", type: "bytes32" }, + { indexed: false, internalType: "bytes32", name: "y", type: "bytes32" }, + ], + name: "PubkeyChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "newRegistrarController", + type: "address", + }, + ], + name: "RegistrarControllerUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "newReverseRegistrar", + type: "address", + }, + ], + name: "ReverseRegistrarUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: true, + internalType: "string", + name: "indexedKey", + type: "string", + }, + { indexed: false, internalType: "string", name: "key", type: "string" }, + { indexed: false, internalType: "string", name: "value", type: "string" }, + ], + name: "TextChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: false, + internalType: "uint64", + name: "newVersion", + type: "uint64", + }, + ], + name: "VersionChanged", + type: "event", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint256", name: "contentTypes", type: "uint256" }, + ], + name: "ABI", + outputs: [ + { internalType: "uint256", name: "", type: "uint256" }, + { internalType: "bytes", name: "", type: "bytes" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "addr", + outputs: [{ internalType: "address payable", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint256", name: "coinType", type: "uint256" }, + ], + name: "addr", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "delegate", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "approve", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "cancelOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "clearRecords", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "completeOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "contenthash", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes32", name: "name", type: "bytes32" }, + { internalType: "uint16", name: "resource", type: "uint16" }, + ], + name: "dnsRecord", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "ens", + outputs: [{ internalType: "contract ENS", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes32", name: "name", type: "bytes32" }, + ], + name: "hasDNSRecords", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes4", name: "interfaceID", type: "bytes4" }, + ], + name: "interfaceImplementer", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "delegate", type: "address" }, + ], + name: "isApprovedFor", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "address", name: "operator", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes[]", name: "data", type: "bytes[]" }], + name: "multicall", + outputs: [{ internalType: "bytes[]", name: "results", type: "bytes[]" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "nodehash", type: "bytes32" }, + { internalType: "bytes[]", name: "data", type: "bytes[]" }, + ], + name: "multicallWithNodeCheck", + outputs: [{ internalType: "bytes[]", name: "results", type: "bytes[]" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "result", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "ownershipHandoverExpiresAt", + outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "pubkey", + outputs: [ + { internalType: "bytes32", name: "x", type: "bytes32" }, + { internalType: "bytes32", name: "y", type: "bytes32" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + name: "recordVersions", + outputs: [{ internalType: "uint64", name: "", type: "uint64" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "registrarController", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "requestOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "", type: "bytes" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "resolve", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "reverseRegistrar", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint256", name: "contentType", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "setABI", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint256", name: "coinType", type: "uint256" }, + { internalType: "bytes", name: "a", type: "bytes" }, + ], + name: "setAddr", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "a", type: "address" }, + ], + name: "setAddr", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes", name: "hash", type: "bytes" }, + ], + name: "setContenthash", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "setDNSRecords", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes4", name: "interfaceID", type: "bytes4" }, + { internalType: "address", name: "implementer", type: "address" }, + ], + name: "setInterface", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "string", name: "newName", type: "string" }, + ], + name: "setName", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes32", name: "x", type: "bytes32" }, + { internalType: "bytes32", name: "y", type: "bytes32" }, + ], + name: "setPubkey", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "registrarController_", + type: "address", + }, + ], + name: "setRegistrarController", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "reverseRegistrar_", type: "address" }, + ], + name: "setReverseRegistrar", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "string", name: "key", type: "string" }, + { internalType: "string", name: "value", type: "string" }, + ], + name: "setText", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes", name: "hash", type: "bytes" }, + ], + name: "setZonehash", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceID", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "string", name: "key", type: "string" }, + ], + name: "text", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "zonehash", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/src/ponder-ens-plugins/base/abis/RegistrarController.ts b/src/ponder-ens-plugins/base/abis/RegistrarController.ts new file mode 100644 index 00000000..e301be80 --- /dev/null +++ b/src/ponder-ens-plugins/base/abis/RegistrarController.ts @@ -0,0 +1,647 @@ +export const RegistrarController = [ + { + inputs: [ + { + internalType: "contract BaseRegistrar", + name: "base_", + type: "address", + }, + { + internalType: "contract IPriceOracle", + name: "prices_", + type: "address", + }, + { + internalType: "contract IReverseRegistrar", + name: "reverseRegistrar_", + type: "address", + }, + { internalType: "address", name: "owner_", type: "address" }, + { internalType: "bytes32", name: "rootNode_", type: "bytes32" }, + { internalType: "string", name: "rootName_", type: "string" }, + { internalType: "address", name: "paymentReceiver_", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [{ internalType: "address", name: "target", type: "address" }], + name: "AddressEmptyCode", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "AddressInsufficientBalance", + type: "error", + }, + { inputs: [], name: "AlreadyInitialized", type: "error" }, + { + inputs: [{ internalType: "address", name: "sender", type: "address" }], + name: "AlreadyRegisteredWithDiscount", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "duration", type: "uint256" }], + name: "DurationTooShort", + type: "error", + }, + { inputs: [], name: "FailedInnerCall", type: "error" }, + { + inputs: [{ internalType: "bytes32", name: "key", type: "bytes32" }], + name: "InactiveDiscount", + type: "error", + }, + { inputs: [], name: "InsufficientValue", type: "error" }, + { + inputs: [ + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "InvalidDiscount", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "key", type: "bytes32" }], + name: "InvalidDiscountAmount", + type: "error", + }, + { inputs: [], name: "InvalidPaymentReceiver", type: "error" }, + { + inputs: [ + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "address", name: "validator", type: "address" }, + ], + name: "InvalidValidator", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "NameNotAvailable", + type: "error", + }, + { inputs: [], name: "NewOwnerIsZeroAddress", type: "error" }, + { inputs: [], name: "NoHandoverRequest", type: "error" }, + { inputs: [], name: "ResolverRequiredWhenDataSupplied", type: "error" }, + { + inputs: [{ internalType: "address", name: "token", type: "address" }], + name: "SafeERC20FailedOperation", + type: "error", + }, + { inputs: [], name: "TransferFailed", type: "error" }, + { inputs: [], name: "Unauthorized", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "registrant", + type: "address", + }, + { + indexed: true, + internalType: "bytes32", + name: "discountKey", + type: "bytes32", + }, + ], + name: "DiscountApplied", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "discountKey", + type: "bytes32", + }, + { + components: [ + { internalType: "bool", name: "active", type: "bool" }, + { + internalType: "address", + name: "discountValidator", + type: "address", + }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + indexed: false, + internalType: "struct RegistrarController.DiscountDetails", + name: "details", + type: "tuple", + }, + ], + name: "DiscountUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "payee", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "price", + type: "uint256", + }, + ], + name: "ETHPaymentProcessed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "string", name: "name", type: "string" }, + { + indexed: true, + internalType: "bytes32", + name: "label", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameRegistered", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "string", name: "name", type: "string" }, + { + indexed: true, + internalType: "bytes32", + name: "label", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameRenewed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverCanceled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newPaymentReceiver", + type: "address", + }, + ], + name: "PaymentReceiverUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newPrices", + type: "address", + }, + ], + name: "PriceOracleUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newReverseRegistrar", + type: "address", + }, + ], + name: "ReverseRegistrarUpdated", + type: "event", + }, + { + inputs: [], + name: "MIN_NAME_LENGTH", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MIN_REGISTRATION_DURATION", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "available", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "cancelOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "completeOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "bytes[]", name: "data", type: "bytes[]" }, + { internalType: "bool", name: "reverseRecord", type: "bool" }, + ], + internalType: "struct RegistrarController.RegisterRequest", + name: "request", + type: "tuple", + }, + { internalType: "bytes32", name: "discountKey", type: "bytes32" }, + { internalType: "bytes", name: "validationData", type: "bytes" }, + ], + name: "discountedRegister", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + { internalType: "bytes32", name: "discountKey", type: "bytes32" }, + ], + name: "discountedRegisterPrice", + outputs: [{ internalType: "uint256", name: "price", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "registrant", type: "address" }], + name: "discountedRegistrants", + outputs: [ + { internalType: "bool", name: "hasRegisteredWithDiscount", type: "bool" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "key", type: "bytes32" }], + name: "discounts", + outputs: [ + { internalType: "bool", name: "active", type: "bool" }, + { internalType: "address", name: "discountValidator", type: "address" }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getActiveDiscounts", + outputs: [ + { + components: [ + { internalType: "bool", name: "active", type: "bool" }, + { + internalType: "address", + name: "discountValidator", + type: "address", + }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + internalType: "struct RegistrarController.DiscountDetails[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address[]", name: "addresses", type: "address[]" }, + ], + name: "hasRegisteredWithDiscount", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "launchTime", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "result", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "ownershipHandoverExpiresAt", + outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "paymentReceiver", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "prices", + outputs: [ + { internalType: "contract IPriceOracle", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_token", type: "address" }, + { internalType: "address", name: "_to", type: "address" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "recoverFunds", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "bytes[]", name: "data", type: "bytes[]" }, + { internalType: "bool", name: "reverseRecord", type: "bool" }, + ], + internalType: "struct RegistrarController.RegisterRequest", + name: "request", + type: "tuple", + }, + ], + name: "register", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "registerPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "renew", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "rentPrice", + outputs: [ + { + components: [ + { internalType: "uint256", name: "base", type: "uint256" }, + { internalType: "uint256", name: "premium", type: "uint256" }, + ], + internalType: "struct IPriceOracle.Price", + name: "price", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "requestOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "reverseRegistrar", + outputs: [ + { internalType: "contract IReverseRegistrar", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rootName", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rootNode", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "bool", name: "active", type: "bool" }, + { + internalType: "address", + name: "discountValidator", + type: "address", + }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + internalType: "struct RegistrarController.DiscountDetails", + name: "details", + type: "tuple", + }, + ], + name: "setDiscountDetails", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "launchTime_", type: "uint256" }], + name: "setLaunchTime", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "paymentReceiver_", type: "address" }, + ], + name: "setPaymentReceiver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IPriceOracle", + name: "prices_", + type: "address", + }, + ], + name: "setPriceOracle", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IReverseRegistrar", + name: "reverse_", + type: "address", + }, + ], + name: "setReverseRegistrar", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "valid", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "withdrawETH", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/src/ponder-ens-plugins/base/abis/Registry.ts b/src/ponder-ens-plugins/base/abis/Registry.ts new file mode 100644 index 00000000..c0254788 --- /dev/null +++ b/src/ponder-ens-plugins/base/abis/Registry.ts @@ -0,0 +1,199 @@ +export const Registry = [ + { + inputs: [{ internalType: "address", name: "rootOwner", type: "address" }], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "Unauthorized", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { indexed: false, internalType: "bool", name: "approved", type: "bool" }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: true, + internalType: "bytes32", + name: "label", + type: "bytes32", + }, + { + indexed: false, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "NewOwner", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: false, + internalType: "address", + name: "resolver", + type: "address", + }, + ], + name: "NewResolver", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { indexed: false, internalType: "uint64", name: "ttl", type: "uint64" }, + ], + name: "NewTTL", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + { + indexed: false, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "Transfer", + type: "event", + }, + { + inputs: [ + { internalType: "address", name: "owner_", type: "address" }, + { internalType: "address", name: "operator", type: "address" }, + ], + name: "isApprovedForAll", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "recordExists", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "resolver", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "operator", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "owner_", type: "address" }, + ], + name: "setOwner", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "owner_", type: "address" }, + { internalType: "address", name: "resolver_", type: "address" }, + { internalType: "uint64", name: "ttl_", type: "uint64" }, + ], + name: "setRecord", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "address", name: "resolver_", type: "address" }, + ], + name: "setResolver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes32", name: "label", type: "bytes32" }, + { internalType: "address", name: "owner_", type: "address" }, + ], + name: "setSubnodeOwner", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "bytes32", name: "label", type: "bytes32" }, + { internalType: "address", name: "owner_", type: "address" }, + { internalType: "address", name: "resolver_", type: "address" }, + { internalType: "uint64", name: "ttl_", type: "uint64" }, + ], + name: "setSubnodeRecord", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "node", type: "bytes32" }, + { internalType: "uint64", name: "ttl_", type: "uint64" }, + ], + name: "setTTL", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "node", type: "bytes32" }], + name: "ttl", + outputs: [{ internalType: "uint64", name: "", type: "uint64" }], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/src/ponder-ens-plugins/base/abis/ReverseRegistrar.ts b/src/ponder-ens-plugins/base/abis/ReverseRegistrar.ts new file mode 100644 index 00000000..b1354f1e --- /dev/null +++ b/src/ponder-ens-plugins/base/abis/ReverseRegistrar.ts @@ -0,0 +1,251 @@ +export const ReverseRegistrar = [ + { + inputs: [ + { internalType: "contract ENS", name: "registry_", type: "address" }, + { internalType: "address", name: "owner_", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "AlreadyInitialized", type: "error" }, + { inputs: [], name: "NewOwnerIsZeroAddress", type: "error" }, + { inputs: [], name: "NoHandoverRequest", type: "error" }, + { inputs: [], name: "NoZeroAddress", type: "error" }, + { + inputs: [ + { internalType: "address", name: "addr", type: "address" }, + { internalType: "address", name: "sender", type: "address" }, + ], + name: "NotAuthorized", + type: "error", + }, + { inputs: [], name: "Unauthorized", type: "error" }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "addr", type: "address" }, + { indexed: true, internalType: "bytes32", name: "node", type: "bytes32" }, + ], + name: "BaseReverseClaimed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "controller", + type: "address", + }, + { indexed: false, internalType: "bool", name: "approved", type: "bool" }, + ], + name: "ControllerApprovalChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "contract NameResolver", + name: "resolver", + type: "address", + }, + ], + name: "DefaultResolverChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverCanceled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + inputs: [], + name: "cancelOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "claim", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "addr", type: "address" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "resolver", type: "address" }, + ], + name: "claimForBaseAddr", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "resolver", type: "address" }, + ], + name: "claimWithResolver", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "completeOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "controller", type: "address" }], + name: "controllers", + outputs: [{ internalType: "bool", name: "approved", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "defaultResolver", + outputs: [ + { internalType: "contract NameResolver", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "addr", type: "address" }], + name: "node", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "result", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "ownershipHandoverExpiresAt", + outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "registry", + outputs: [{ internalType: "contract ENS", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "requestOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "controller", type: "address" }, + { internalType: "bool", name: "approved", type: "bool" }, + ], + name: "setControllerApproval", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "resolver", type: "address" }], + name: "setDefaultResolver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "setName", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "addr", type: "address" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "string", name: "name", type: "string" }, + ], + name: "setNameForAddr", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/src/ponder-ens-plugins/base/handlers/Registrar.ts b/src/ponder-ens-plugins/base/handlers/Registrar.ts new file mode 100644 index 00000000..c2b0ad11 --- /dev/null +++ b/src/ponder-ens-plugins/base/handlers/Registrar.ts @@ -0,0 +1,172 @@ +import { type Context, type Event, EventNames, ponder } from "ponder:registry"; +import { domains, registrations } from "ponder:schema"; +import type { Hex } from "viem"; +import { + NAMEHASH_ETH, + isLabelValid, + makeSubnodeNamehash, + tokenIdToLabel, +} from "../../../lib/ens-helpers"; +import { upsertAccount, upsertRegistration } from "../../../lib/upserts"; +import { PonderEnsIndexingHandlerModule } from "../../types"; +import { type NsType, ns } from "../ponder.config"; + +// all nodes referenced by EthRegistrar are parented to .eth +const ROOT_NODE = NAMEHASH_ETH; +const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds + +async function handleNameRegistered({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { id, owner, expires } = event.args; + + await upsertAccount(context, owner); + + const label = tokenIdToLabel(id); + const node = makeSubnodeNamehash(ROOT_NODE, label); + + // TODO: materialze labelName via rainbow tables ala Registry.ts + const labelName = undefined; + + await upsertRegistration(context, { + id: label, + domainId: node, + registrationDate: event.block.timestamp, + expiryDate: expires, + registrantId: owner, + labelName, + }); + + await context.db.update(domains, { id: node }).set({ + registrantId: owner, + expiryDate: expires + GRACE_PERIOD_SECONDS, + labelName, + }); + + // TODO: log Event +} + +async function handleNameRegisteredByController({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + return await setNamePreimage( + context, + event.args.name, + event.args.label, + null + ); +} + +async function handleNameRenewedByController({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + return await setNamePreimage( + context, + event.args.name, + event.args.label, + null + ); +} + +async function setNamePreimage( + context: Context, + name: string, + label: Hex, + cost: bigint | null +) { + if (!isLabelValid(name)) return; + + const node = makeSubnodeNamehash(ROOT_NODE, label); + const domain = await context.db.find(domains, { id: node }); + if (!domain) throw new Error("domain expected"); + + if (domain.labelName !== name) { + await context.db + .update(domains, { id: node }) + .set({ labelName: name, name: `${name}.eth` }); + } + + await context.db + .update(registrations, { id: label }) + .set({ labelName: name, cost }); +} + +async function handleNameRenewed({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { id, expires } = event.args; + + const label = tokenIdToLabel(id); + const node = makeSubnodeNamehash(ROOT_NODE, label); + + await context.db + .update(registrations, { id: label }) + .set({ expiryDate: expires }); + + await context.db + .update(domains, { id: node }) + .set({ expiryDate: expires + GRACE_PERIOD_SECONDS }); + + // TODO: log Event +} + +async function handleNameTransferred({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { id: tokenId, from, to } = event.args; + + await upsertAccount(context, to); + + const label = tokenIdToLabel(tokenId); + const node = makeSubnodeNamehash(ROOT_NODE, label); + + const registration = await context.db.find(registrations, { id: label }); + if (!registration) return; + + await context.db + .update(registrations, { id: label }) + .set({ registrantId: to }); + + await context.db.update(domains, { id: node }).set({ registrantId: to }); + + // TODO: log Event +} + +function initEthRegistrarHandlers() { + ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); + ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); + ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); + + ponder.on( + ns("RegistrarController:NameRegistered"), + handleNameRegisteredByController + ); + ponder.on( + ns("RegistrarController:NameRenewed"), + handleNameRenewedByController + ); +} + +export const handlerModule: Readonly = { + attachHandlers: initEthRegistrarHandlers, +}; diff --git a/src/ponder-ens-plugins/base/handlers/Registry.ts b/src/ponder-ens-plugins/base/handlers/Registry.ts new file mode 100644 index 00000000..5b10271f --- /dev/null +++ b/src/ponder-ens-plugins/base/handlers/Registry.ts @@ -0,0 +1,233 @@ +import { type Context, type Event, ponder } from "ponder:registry"; +import { resolvers } from "ponder:schema"; +import { domains } from "ponder:schema"; +import { type Hex, zeroAddress } from "viem"; +import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../../../lib/ens-helpers"; +import { makeResolverId } from "../../../lib/ids"; +import { upsertAccount } from "../../../lib/upserts"; +import { PonderEnsIndexingHandlerModule } from "../../types"; +import { type NsType, ns } from "../ponder.config"; + +// a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not +async function isDomainMigrated(context: Context, node: Hex) { + const domain = await context.db.find(domains, { id: node }); + return domain?.isMigrated ?? false; +} + +function isDomainEmpty(domain: typeof domains.$inferSelect) { + return ( + domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 + ); +} + +// a more accurate name for the following function +// https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { + const domain = await context.db.find(domains, { id: node }); + if (!domain) throw new Error(`Domain not found: ${node}`); + + if (isDomainEmpty(domain) && domain.parentId !== null) { + // decrement parent's subdomain count + await context.db + .update(domains, { id: domain.parentId }) + .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); + + // recurse to parent + return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); + } +} + +async function _handleTransfer({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, owner } = event.args; + + // ensure owner account + await upsertAccount(context, owner); + + // ensure domain & update owner + await context.db + .insert(domains) + .values([{ id: node, ownerId: owner, createdAt: event.block.timestamp }]) + .onConflictDoUpdate({ ownerId: owner }); + + // garbage collect newly 'empty' domain iff necessary + if (owner === zeroAddress) { + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); + } + + // TODO: log DomainEvent +} + +const _handleNewOwner = + (isMigrated: boolean) => + async ({ + context, + event, + }: { + context: Context; + event: Event>; + }) => { + const { label, node, owner } = event.args; + + const subnode = makeSubnodeNamehash(node, label); + + // ensure owner + await upsertAccount(context, owner); + + // note that we set isMigrated so that if this domain is being interacted with on the new registry, its migration status is set here + let domain = await context.db.find(domains, { id: subnode }); + if (domain) { + // if the domain already exists, this is just an update of the owner record. + await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); + } else { + // otherwise create the domain + domain = await context.db.insert(domains).values({ + id: subnode, + ownerId: owner, + parentId: node, + createdAt: event.block.timestamp, + isMigrated, + }); + + // and increment parent subdomainCount + await context.db + .update(domains, { id: node }) + .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); + } + + // if the domain doesn't yet have a name, construct it here + if (!domain.name) { + const parent = await context.db.find(domains, { id: node }); + + // TODO: implement sync rainbow table lookups + // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L111 + const labelName = encodeLabelhash(label); + const name = parent?.name ? `${labelName}.${parent.name}` : labelName; + + await context.db.update(domains, { id: domain.id }).set({ name, labelName }); + } + + // garbage collect newly 'empty' domain iff necessary + if (owner === zeroAddress) { + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); + } + }; + +async function _handleNewTTL({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, ttl } = event.args; + + // TODO: handle the edge case in which the domain no longer exists? + // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L215 + // NOTE: i'm not sure this needs to be here, as domains are never deleted (??) + await context.db.update(domains, { id: node }).set({ ttl }); + + // TODO: log DomainEvent +} + +async function _handleNewResolver({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, resolver: resolverAddress } = event.args; + + // if zeroing out a domain's resolver, remove the reference instead of tracking a zeroAddress Resolver + // NOTE: old resolver resources are kept for event logs + if (event.args.resolver === zeroAddress) { + await context.db.update(domains, { id: node }).set({ resolverId: null }); + + // garbage collect newly 'empty' domain iff necessary + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); + } else { + // otherwise upsert the resolver + const resolverId = makeResolverId(node, resolverAddress); + + const resolver = await context.db + .insert(resolvers) + .values({ + id: resolverId, + domainId: event.args.node, + address: event.args.resolver, + }) + .onConflictDoNothing(); + + // update the domain to point to it, and denormalize the eth addr + await context.db + .update(domains, { id: node }) + .set({ resolverId, resolvedAddress: resolver?.addrId }); + } + + // TODO: log DomainEvent +} + +function initRegistryHandlers() { + // // setup on old registry + // ponder.on("RegistryOld:setup", async ({ context }) => { + // // ensure we have an account for the zeroAddress + // await upsertAccount(context, zeroAddress); + + // // ensure we have a root Domain, owned by the zeroAddress + // await context.db.insert(domains).values({ + // id: NAMEHASH_ZERO, + // ownerId: zeroAddress, + // createdAt: 0n, + // isMigrated: false, + // }); + // }); + + // // old registry functions are proxied to the current handlers + // // iff the domain has not yet been migrated + // ponder.on("RegistryOld:NewOwner", async ({ context, event }) => { + // const node = makeSubnodeNamehash(event.args.node, event.args.label); + // const isMigrated = await isDomainMigrated(context, node); + // if (isMigrated) return; + // return _handleNewOwner(false)({ context, event }); + // }); + + // ponder.on("RegistryOld:NewResolver", async ({ context, event }) => { + // // NOTE: the subgraph makes an exception for the root node here + // // but i don't know that that's necessary, as in ponder our root node starts out + // // unmigrated and once the NewOwner event is emitted by the new registry, + // // the root will be considered migrated + // // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 + + // // otherwise, only handle iff not migrated + // const isMigrated = await isDomainMigrated(context, event.args.node); + // if (isMigrated) return; + // return _handleNewResolver({ context, event }); + // }); + + // ponder.on("RegistryOld:NewTTL", async ({ context, event }) => { + // const isMigrated = await isDomainMigrated(context, event.args.node); + // if (isMigrated) return; + // return _handleNewTTL({ context, event }); + // }); + + // ponder.on("RegistryOld:Transfer", async ({ context, event }) => { + // const isMigrated = await isDomainMigrated(context, event.args.node); + // if (isMigrated) return; + // return _handleTransfer({ context, event }); + // }); + + ponder.on(ns("Registry:NewOwner"), _handleNewOwner(true)); + ponder.on(ns("Registry:NewResolver"), _handleNewResolver); + ponder.on(ns("Registry:NewTTL"), _handleNewTTL); + ponder.on(ns("Registry:Transfer"), _handleTransfer); +} + +export const handlerModule: Readonly = { + attachHandlers: initRegistryHandlers, +}; diff --git a/src/ponder-ens-plugins/base/handlers/Resolver.ts b/src/ponder-ens-plugins/base/handlers/Resolver.ts new file mode 100644 index 00000000..2382fd5b --- /dev/null +++ b/src/ponder-ens-plugins/base/handlers/Resolver.ts @@ -0,0 +1,258 @@ +import { type Context, type Event, ponder } from "ponder:registry"; +import { domains, resolvers } from "ponder:schema"; +import { hasNullByte, uniq } from "../../../lib/helpers"; +import { makeResolverId } from "../../../lib/ids"; +import { upsertAccount, upsertResolver } from "../../../lib/upserts"; +import { PonderEnsIndexingHandlerModule } from "../../types"; +import { type NsType, ns } from "../ponder.config"; + +async function _handleAddrChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { a: address, node } = event.args; + await upsertAccount(context, address); + + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + addrId: address, + }); + + // materialize the resolved add to the domain iff this resolver is active + const domain = await context.db.find(domains, { id: node }); + if (domain?.resolverId === id) { + await context.db + .update(domains, { id: node }) + .set({ resolvedAddress: address }); + } + + // TODO: log ResolverEvent +} + +async function _handleAddressChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, coinType, newAddress } = event.args; + await upsertAccount(context, newAddress); + + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // upsert the new coinType + await context.db + .update(resolvers, { id }) + .set({ coinTypes: uniq([...resolver.coinTypes, coinType]) }); + + // TODO: log ResolverEvent +} + +async function _handleNameChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, name } = event.args; + if (hasNullByte(name)) return; + + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +async function _handleABIChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +async function _handlePubkeyChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +async function _handleTextChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, key } = event.args; + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // upsert new key + await context.db + .update(resolvers, { id }) + .set({ texts: uniq([...resolver.texts, key]) }); + + // TODO: log ResolverEvent +} + +async function _handleContenthashChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, hash } = event.args; + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + contentHash: hash, + }); + + // TODO: log ResolverEvent +} + +async function _handleInterfaceChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +async function _handleVersionChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // a version change nulls out the resolver + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + const domain = await context.db.find(domains, { id: node }); + if (!domain) throw new Error("domain expected"); + + // materialize the Domain's resolvedAddress field + if (domain.resolverId === id) { + await context.db + .update(domains, { id: node }) + .set({ resolvedAddress: null }); + } + + // clear out the resolver's info + await context.db.update(resolvers, { id }).set({ + addrId: null, + contentHash: null, + coinTypes: [], + texts: [], + }); + + // TODO: log ResolverEvent +} + +async function _handleDNSRecordChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // subgraph ignores +} + +async function _handleDNSRecordDeleted({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // subgraph ignores +} + +async function _handleDNSZonehashChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // subgraph ignores +} + +export function initResolverHandlers() { + // New registry handlers + ponder.on(ns("Resolver:AddrChanged"), _handleAddrChanged); + ponder.on(ns("Resolver:AddressChanged"), _handleAddressChanged); + ponder.on(ns("Resolver:NameChanged"), _handleNameChanged); + ponder.on(ns("Resolver:ABIChanged"), _handleABIChanged); + ponder.on(ns("Resolver:PubkeyChanged"), _handlePubkeyChanged); + ponder.on(ns("Resolver:TextChanged"), _handleTextChanged); + ponder.on(ns("Resolver:ContenthashChanged"), _handleContenthashChanged); + ponder.on(ns("Resolver:InterfaceChanged"), _handleInterfaceChanged); + ponder.on(ns("Resolver:VersionChanged"), _handleVersionChanged); + ponder.on(ns("Resolver:DNSRecordChanged"), _handleDNSRecordChanged); + ponder.on(ns("Resolver:DNSRecordDeleted"), _handleDNSRecordDeleted); + ponder.on(ns("Resolver:DNSZonehashChanged"), _handleDNSZonehashChanged); +} + +export const handlerModule: Readonly = { + attachHandlers: initResolverHandlers, +}; diff --git a/src/ponder-ens-plugins/base/ponder.config.ts b/src/ponder-ens-plugins/base/ponder.config.ts new file mode 100644 index 00000000..7fa35982 --- /dev/null +++ b/src/ponder-ens-plugins/base/ponder.config.ts @@ -0,0 +1,91 @@ +import { factory } from "ponder"; +import { http, getAbiItem } from "viem"; +import { base, mainnet } from "viem/chains"; + +import { type NsReturnType, createNs } from "../chain"; + +// Replacing ABIs with the Base-specific ones, as per https://github.com/base-org/basenames?tab=readme-ov-file#architecture +// import { BaseRegistrar } from "./abis/BaseRegistrar"; +// import { EthRegistrarController } from "./abis/EthRegistrarController"; +// import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; +// import { LegacyPublicResolver } from "./abis/LegacyPublicResolver"; +// import { NameWrapper } from "./abis/NameWrapper"; +// import { Registry } from "./abis/Registry"; +// import { Resolver } from "./abis/Resolver"; + +import { BaseRegistrar } from "./abis/BaseRegistrar"; +import { L2Resolver } from "./abis/L2Resolver"; +import { RegistrarController } from "./abis/RegistrarController"; +import { Registry } from "./abis/Registry"; + +// just for testing... +const END_BLOCK = 20_200_200; + +export const ns = createNs(base.id); + +export type NsType = NsReturnType; + +const REGISTRY_ADDRESS = '0xb94704422c2a1e396835a571837aa5ae53285a95'; +const REGISTRY_START_BLOCK = 17571480; + +const BASE_REGISTRAR_ADDRESS = '0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a'; +const BASE_REGISTRAR_START_BLOCK = 17571486; + +const REGISTRAR_CONTROLLER_ADDRESS = '0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5'; +const REGISTRAR_CONTROLLER_START_BLOCK = 18619035; + +const REVERSE_REGISTRAR_ADDRESS = '0x79ea96012eea67a83431f1701b3dff7e37f9e282'; +const REVERSE_REGISTRAR_START_BLOCK = 17571485; + +const L1_RESOLVER_ADDRESS = '0xde9049636F4a1dfE0a64d1bFe3155C0A14C54F31'; +const L1_RESOLVER_START_BLOCK = 20420641; + +const L2_RESOLVER_ADDRESS = '0xC6d566A56A1aFf6508b41f6c90ff131615583BCD'; +const L2_RESOLVER_START_BLOCK = 17575714; + +export const config = Object.freeze({ + networks: { + base: { + chainId: base.id, + transport: http(process.env[`PONDER_RPC_URL_${base.id}`]), + }, + mainnet: { + chainId: mainnet.id, + transport: http(process.env[`PONDER_RPC_URL_${mainnet.id}`]), + }, + }, + contracts: { + [ns("Registry")]: { + network: "base", + abi: Registry, + address: REGISTRY_ADDRESS, + startBlock: REGISTRY_START_BLOCK, + endBlock: END_BLOCK, + }, + [ns("Resolver")]: { + network: "base", + abi: L2Resolver, + address: factory({ + address: L2_RESOLVER_ADDRESS, + event: getAbiItem({ abi: Registry, name: "NewResolver" }), + parameter: "resolver", + }), + startBlock: L2_RESOLVER_START_BLOCK, + endBlock: END_BLOCK, + }, + [ns("BaseRegistrar")]: { + network: "base", + abi: BaseRegistrar, + address: BASE_REGISTRAR_ADDRESS, + startBlock: 9380410, + endBlock: END_BLOCK, + }, + [ns("RegistrarController")]: { + network: "base", + abi: RegistrarController, + address: REGISTRAR_CONTROLLER_ADDRESS, + startBlock: Math.min(REGISTRAR_CONTROLLER_START_BLOCK, END_BLOCK), + endBlock: END_BLOCK, + }, + }, +} as const); diff --git a/src/ponder-ens-plugins/base/ponder.indexing.ts b/src/ponder-ens-plugins/base/ponder.indexing.ts new file mode 100644 index 00000000..99058c9c --- /dev/null +++ b/src/ponder-ens-plugins/base/ponder.indexing.ts @@ -0,0 +1,21 @@ +import { base } from "viem/chains"; +import { isChainIndexingActive } from "../chain"; +import { PonderEnsModule } from "../types"; + +export default { + get canActivate() { + return isChainIndexingActive(base.id); + }, + + async activate() { + const ponderIndexingModules = await Promise.all([ + import("./handlers/Registry"), + import("./handlers/Registrar"), + import("./handlers/Resolver"), + ]); + + for (const { handlerModule } of ponderIndexingModules) { + handlerModule.attachHandlers(); + } + }, +} as PonderEnsModule; diff --git a/src/ponder-ens-plugins/chain.ts b/src/ponder-ens-plugins/chain.ts index 4327c356..1c2428c3 100644 --- a/src/ponder-ens-plugins/chain.ts +++ b/src/ponder-ens-plugins/chain.ts @@ -1,5 +1,21 @@ export function isChainIndexingActive(chainId: number) { - return process.env.CHAINS_TO_INDEX?.split(",") - .map((maybeChainId) => parseInt(maybeChainId, 10)) - .includes(chainId); + if (!process.env.ENS_DEPLOYMENT_CHAIN_ID) { + console.warn("ENS_DEPLOYMENT_CHAIN_ID is not set"); + return false; + } + + return parseInt(process.env.ENS_DEPLOYMENT_CHAIN_ID, 10) === chainId; } + +export function createNs(chainId: ChainId) { + return function ns( + contractName: ContractName, + ): NsReturnType { + return `Chain${chainId}_${contractName}` as NsReturnType; + }; +} + +export type NsReturnType< + ContractName extends string, + ChainId extends number, +> = `Chain${ChainId}_${ContractName}`; diff --git a/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts index b73b0fa8..45237dc9 100644 --- a/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts @@ -9,6 +9,7 @@ import { } from "../../../lib/ens-helpers"; import { upsertAccount, upsertRegistration } from "../../../lib/upserts"; import { PonderEnsIndexingHandlerModule } from "../../types"; +import { type NsType, ns } from "../ponder.config"; // all nodes referenced by EthRegistrar are parented to .eth const ROOT_NODE = NAMEHASH_ETH; @@ -19,7 +20,7 @@ async function handleNameRegistered({ event, }: { context: Context; - event: Event<"BaseRegistrar:NameRegistered">; + event: Event>; }) { const { id, owner, expires } = event.args; @@ -54,14 +55,9 @@ async function handleNameRegisteredByControllerOld({ event, }: { context: Context; - event: Event<"EthRegistrarControllerOld:NameRegistered">; + event: Event>; }) { - return await setNamePreimage( - context, - event.args.name, - event.args.label, - event.args.cost - ); + return await setNamePreimage(context, event.args.name, event.args.label, event.args.cost); } async function handleNameRegisteredByController({ @@ -69,13 +65,13 @@ async function handleNameRegisteredByController({ event, }: { context: Context; - event: Event<"EthRegistrarController:NameRegistered">; + event: Event>; }) { return await setNamePreimage( context, event.args.name, event.args.label, - event.args.baseCost + event.args.premium + event.args.baseCost + event.args.premium, ); } @@ -85,23 +81,13 @@ async function handleNameRenewedByController({ }: { context: Context; event: - | Event<"EthRegistrarController:NameRenewed"> - | Event<"EthRegistrarControllerOld:NameRenewed">; + | Event> + | Event>; }) { - return await setNamePreimage( - context, - event.args.name, - event.args.label, - event.args.cost - ); + return await setNamePreimage(context, event.args.name, event.args.label, event.args.cost); } -async function setNamePreimage( - context: Context, - name: string, - label: Hex, - cost: bigint -) { +async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; const node = makeSubnodeNamehash(ROOT_NODE, label); @@ -109,14 +95,10 @@ async function setNamePreimage( if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { - await context.db - .update(domains, { id: node }) - .set({ labelName: name, name: `${name}.eth` }); + await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}.eth` }); } - await context.db - .update(registrations, { id: label }) - .set({ labelName: name, cost }); + await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); } async function handleNameRenewed({ @@ -124,16 +106,14 @@ async function handleNameRenewed({ event, }: { context: Context; - event: Event<"BaseRegistrar:NameRenewed">; + event: Event>; }) { const { id, expires } = event.args; const label = tokenIdToLabel(id); const node = makeSubnodeNamehash(ROOT_NODE, label); - await context.db - .update(registrations, { id: label }) - .set({ expiryDate: expires }); + await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); await context.db .update(domains, { id: node }) @@ -147,7 +127,7 @@ async function handleNameTransferred({ event, }: { context: Context; - event: Event<"BaseRegistrar:Transfer">; + event: Event>; }) { const { tokenId, from, to } = event.args; @@ -159,9 +139,7 @@ async function handleNameTransferred({ const registration = await context.db.find(registrations, { id: label }); if (!registration) return; - await context.db - .update(registrations, { id: label }) - .set({ registrantId: to }); + await context.db.update(registrations, { id: label }).set({ registrantId: to }); await context.db.update(domains, { id: node }).set({ registrantId: to }); @@ -170,27 +148,15 @@ async function handleNameTransferred({ function initEthRegistrarHandlers() { console.log("Indexing Ethereum ENS"); - ponder.on("BaseRegistrar:NameRegistered", handleNameRegistered); - ponder.on("BaseRegistrar:NameRenewed", handleNameRenewed); - ponder.on("BaseRegistrar:Transfer", handleNameTransferred); + ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); + ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); + ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); - ponder.on( - "EthRegistrarControllerOld:NameRegistered", - handleNameRegisteredByControllerOld - ); - ponder.on( - "EthRegistrarControllerOld:NameRenewed", - handleNameRenewedByController - ); + ponder.on(ns("EthRegistrarControllerOld:NameRegistered"), handleNameRegisteredByControllerOld); + ponder.on(ns("EthRegistrarControllerOld:NameRenewed"), handleNameRenewedByController); - ponder.on( - "EthRegistrarController:NameRegistered", - handleNameRegisteredByController - ); - ponder.on( - "EthRegistrarController:NameRenewed", - handleNameRenewedByController - ); + ponder.on(ns("EthRegistrarController:NameRegistered"), handleNameRegisteredByController); + ponder.on(ns("EthRegistrarController:NameRenewed"), handleNameRenewedByController); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/ethereum/handlers/Registry.ts b/src/ponder-ens-plugins/ethereum/handlers/Registry.ts index e606f52b..59caa901 100644 --- a/src/ponder-ens-plugins/ethereum/handlers/Registry.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/Registry.ts @@ -2,14 +2,11 @@ import { type Context, type Event, ponder } from "ponder:registry"; import { resolvers } from "ponder:schema"; import { domains } from "ponder:schema"; import { type Hex, zeroAddress } from "viem"; -import { - NAMEHASH_ZERO, - encodeLabelhash, - makeSubnodeNamehash, -} from "../../../lib/ens-helpers"; +import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../../../lib/ens-helpers"; import { makeResolverId } from "../../../lib/ids"; import { upsertAccount } from "../../../lib/upserts"; import { PonderEnsIndexingHandlerModule } from "../../types"; +import { NsType, ns } from "../ponder.config"; // a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not async function isDomainMigrated(context: Context, node: Hex) { @@ -19,18 +16,13 @@ async function isDomainMigrated(context: Context, node: Hex) { function isDomainEmpty(domain: typeof domains.$inferSelect) { return ( - domain.resolverId === null && - domain.ownerId === zeroAddress && - domain.subdomainCount === 0 + domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 ); } // a more accurate name for the following function // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context: Context, - node: Hex -) { +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error(`Domain not found: ${node}`); @@ -41,10 +33,7 @@ async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.parentId - ); + return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); } } @@ -53,7 +42,7 @@ async function _handleTransfer({ event, }: { context: Context; - event: Event<"Registry:Transfer">; + event: Event>; }) { const { node, owner } = event.args; @@ -81,7 +70,7 @@ const _handleNewOwner = event, }: { context: Context; - event: Event<"Registry:NewOwner">; + event: Event>; }) => { const { label, node, owner } = event.args; @@ -94,9 +83,7 @@ const _handleNewOwner = let domain = await context.db.find(domains, { id: subnode }); if (domain) { // if the domain already exists, this is just an update of the owner record. - await context.db - .update(domains, { id: domain.id }) - .set({ ownerId: owner, isMigrated }); + await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); } else { // otherwise create the domain domain = await context.db.insert(domains).values({ @@ -122,17 +109,12 @@ const _handleNewOwner = const labelName = encodeLabelhash(label); const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - await context.db - .update(domains, { id: domain.id }) - .set({ name, labelName }); + await context.db.update(domains, { id: domain.id }).set({ name, labelName }); } // garbage collect newly 'empty' domain iff necessary if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.id - ); + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); } }; @@ -141,7 +123,7 @@ async function _handleNewTTL({ event, }: { context: Context; - event: Event<"Registry:NewTTL">; + event: Event>; }) { const { node, ttl } = event.args; @@ -158,7 +140,7 @@ async function _handleNewResolver({ event, }: { context: Context; - event: Event<"Registry:NewResolver">; + event: Event>; }) { const { node, resolver: resolverAddress } = event.args; @@ -192,58 +174,58 @@ async function _handleNewResolver({ } function initRegistryHandlers() { - // setup on old registry - ponder.on("RegistryOld:setup", async ({ context }) => { - // ensure we have an account for the zeroAddress - await upsertAccount(context, zeroAddress); - - // ensure we have a root Domain, owned by the zeroAddress - await context.db.insert(domains).values({ - id: NAMEHASH_ZERO, - ownerId: zeroAddress, - createdAt: 0n, - isMigrated: false, - }); - }); - - // old registry functions are proxied to the current handlers - // iff the domain has not yet been migrated - ponder.on("RegistryOld:NewOwner", async ({ context, event }) => { - const node = makeSubnodeNamehash(event.args.node, event.args.label); - const isMigrated = await isDomainMigrated(context, node); - if (isMigrated) return; - return _handleNewOwner(false)({ context, event }); - }); - - ponder.on("RegistryOld:NewResolver", async ({ context, event }) => { - // NOTE: the subgraph makes an exception for the root node here - // but i don't know that that's necessary, as in ponder our root node starts out - // unmigrated and once the NewOwner event is emitted by the new registry, - // the root will be considered migrated - // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 - - // otherwise, only handle iff not migrated - const isMigrated = await isDomainMigrated(context, event.args.node); - if (isMigrated) return; - return _handleNewResolver({ context, event }); - }); - - ponder.on("RegistryOld:NewTTL", async ({ context, event }) => { - const isMigrated = await isDomainMigrated(context, event.args.node); - if (isMigrated) return; - return _handleNewTTL({ context, event }); - }); - - ponder.on("RegistryOld:Transfer", async ({ context, event }) => { - const isMigrated = await isDomainMigrated(context, event.args.node); - if (isMigrated) return; - return _handleTransfer({ context, event }); - }); - - ponder.on("Registry:NewOwner", _handleNewOwner(true)); - ponder.on("Registry:NewResolver", _handleNewResolver); - ponder.on("Registry:NewTTL", _handleNewTTL); - ponder.on("Registry:Transfer", _handleTransfer); + // // setup on old registry + // ponder.on("RegistryOld:setup", async ({ context }) => { + // // ensure we have an account for the zeroAddress + // await upsertAccount(context, zeroAddress); + + // // ensure we have a root Domain, owned by the zeroAddress + // await context.db.insert(domains).values({ + // id: NAMEHASH_ZERO, + // ownerId: zeroAddress, + // createdAt: 0n, + // isMigrated: false, + // }); + // }); + + // // old registry functions are proxied to the current handlers + // // iff the domain has not yet been migrated + // ponder.on("RegistryOld:NewOwner", async ({ context, event }) => { + // const node = makeSubnodeNamehash(event.args.node, event.args.label); + // const isMigrated = await isDomainMigrated(context, node); + // if (isMigrated) return; + // return _handleNewOwner(false)({ context, event }); + // }); + + // ponder.on("RegistryOld:NewResolver", async ({ context, event }) => { + // // NOTE: the subgraph makes an exception for the root node here + // // but i don't know that that's necessary, as in ponder our root node starts out + // // unmigrated and once the NewOwner event is emitted by the new registry, + // // the root will be considered migrated + // // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 + + // // otherwise, only handle iff not migrated + // const isMigrated = await isDomainMigrated(context, event.args.node); + // if (isMigrated) return; + // return _handleNewResolver({ context, event }); + // }); + + // ponder.on("RegistryOld:NewTTL", async ({ context, event }) => { + // const isMigrated = await isDomainMigrated(context, event.args.node); + // if (isMigrated) return; + // return _handleNewTTL({ context, event }); + // }); + + // ponder.on("RegistryOld:Transfer", async ({ context, event }) => { + // const isMigrated = await isDomainMigrated(context, event.args.node); + // if (isMigrated) return; + // return _handleTransfer({ context, event }); + // }); + + ponder.on(ns("Registry:NewOwner"), _handleNewOwner(true)); + ponder.on(ns("Registry:NewResolver"), _handleNewResolver); + ponder.on(ns("Registry:NewTTL"), _handleNewTTL); + ponder.on(ns("Registry:Transfer"), _handleTransfer); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/ethereum/handlers/Resolver.ts b/src/ponder-ens-plugins/ethereum/handlers/Resolver.ts index 1462f2b2..3f76c2cb 100644 --- a/src/ponder-ens-plugins/ethereum/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/Resolver.ts @@ -4,22 +4,31 @@ import { hasNullByte, uniq } from "../../../lib/helpers"; import { makeResolverId } from "../../../lib/ids"; import { upsertAccount, upsertResolver } from "../../../lib/upserts"; import { PonderEnsIndexingHandlerModule } from "../../types"; +import { type NsType, ns } from "../ponder.config"; // there is a legacy resolver abi with different TextChanged events. // luckily the subgraph doesn't care about the value parameter so we can use a union // to unify the codepath type AnyTextChangedEvent = - | Event<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> - | Event<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> - | Event<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> - | Event<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)">; + | Event< + NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> + > + | Event< + NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> + > + | Event< + NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> + > + | Event< + NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> + >; async function _handleAddrChanged({ context, event, }: { context: Context; - event: Event<"Resolver:AddrChanged">; + event: Event>; }) { const { a: address, node } = event.args; await upsertAccount(context, address); @@ -35,9 +44,7 @@ async function _handleAddrChanged({ // materialize the resolved add to the domain iff this resolver is active const domain = await context.db.find(domains, { id: node }); if (domain?.resolverId === id) { - await context.db - .update(domains, { id: node }) - .set({ resolvedAddress: address }); + await context.db.update(domains, { id: node }).set({ resolvedAddress: address }); } // TODO: log ResolverEvent @@ -48,7 +55,7 @@ async function _handleAddressChanged({ event, }: { context: Context; - event: Event<"Resolver:AddressChanged">; + event: Event>; }) { const { node, coinType, newAddress } = event.args; await upsertAccount(context, newAddress); @@ -73,7 +80,7 @@ async function _handleNameChanged({ event, }: { context: Context; - event: Event<"Resolver:NameChanged">; + event: Event>; }) { const { node, name } = event.args; if (hasNullByte(name)) return; @@ -93,7 +100,7 @@ async function _handleABIChanged({ event, }: { context: Context; - event: Event<"Resolver:ABIChanged">; + event: Event>; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -111,7 +118,7 @@ async function _handlePubkeyChanged({ event, }: { context: Context; - event: Event<"Resolver:PubkeyChanged">; + event: Event>; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -140,9 +147,7 @@ async function _handleTextChanged({ }); // upsert new key - await context.db - .update(resolvers, { id }) - .set({ texts: uniq([...resolver.texts, key]) }); + await context.db.update(resolvers, { id }).set({ texts: uniq([...resolver.texts, key]) }); // TODO: log ResolverEvent } @@ -152,7 +157,7 @@ async function _handleContenthashChanged({ event, }: { context: Context; - event: Event<"Resolver:ContenthashChanged">; + event: Event>; }) { const { node, hash } = event.args; const id = makeResolverId(node, event.log.address); @@ -171,7 +176,7 @@ async function _handleInterfaceChanged({ event, }: { context: Context; - event: Event<"Resolver:InterfaceChanged">; + event: Event>; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -189,7 +194,7 @@ async function _handleAuthorisationChanged({ event, }: { context: Context; - event: Event<"Resolver:AuthorisationChanged">; + event: Event>; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -207,7 +212,7 @@ async function _handleVersionChanged({ event, }: { context: Context; - event: Event<"Resolver:VersionChanged">; + event: Event>; }) { // a version change nulls out the resolver const { node } = event.args; @@ -217,9 +222,7 @@ async function _handleVersionChanged({ // materialize the Domain's resolvedAddress field if (domain.resolverId === id) { - await context.db - .update(domains, { id: node }) - .set({ resolvedAddress: null }); + await context.db.update(domains, { id: node }).set({ resolvedAddress: null }); } // clear out the resolver's info @@ -238,7 +241,7 @@ async function _handleDNSRecordChanged({ event, }: { context: Context; - event: Event<"Resolver:DNSRecordChanged">; + event: Event>; }) { // subgraph ignores } @@ -248,7 +251,7 @@ async function _handleDNSRecordDeleted({ event, }: { context: Context; - event: Event<"Resolver:DNSRecordDeleted">; + event: Event>; }) { // subgraph ignores } @@ -258,64 +261,61 @@ async function _handleDNSZonehashChanged({ event, }: { context: Context; - event: Event<"Resolver:DNSZonehashChanged">; + event: Event>; }) { // subgraph ignores } export function initResolverHandlers() { // Old registry handlers - ponder.on("OldRegistryResolvers:AddrChanged", _handleAddrChanged); - ponder.on("OldRegistryResolvers:AddressChanged", _handleAddressChanged); - ponder.on("OldRegistryResolvers:NameChanged", _handleNameChanged); - ponder.on("OldRegistryResolvers:ABIChanged", _handleABIChanged); - ponder.on("OldRegistryResolvers:PubkeyChanged", _handlePubkeyChanged); - ponder.on( - "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", - _handleTextChanged - ); - ponder.on( - "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", - _handleTextChanged - ); - ponder.on( - "OldRegistryResolvers:ContenthashChanged", - _handleContenthashChanged - ); - ponder.on("OldRegistryResolvers:InterfaceChanged", _handleInterfaceChanged); + ponder.on(ns("OldRegistryResolvers:AddrChanged"), _handleAddrChanged); + ponder.on(ns("OldRegistryResolvers:AddressChanged"), _handleAddressChanged); + ponder.on(ns("OldRegistryResolvers:NameChanged"), _handleNameChanged); + ponder.on(ns("OldRegistryResolvers:ABIChanged"), _handleABIChanged); + ponder.on(ns("OldRegistryResolvers:PubkeyChanged"), _handlePubkeyChanged); ponder.on( - "OldRegistryResolvers:AuthorisationChanged", - _handleAuthorisationChanged + ns( + "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", + ), + _handleTextChanged, ); - ponder.on("OldRegistryResolvers:VersionChanged", _handleVersionChanged); - ponder.on("OldRegistryResolvers:DNSRecordChanged", _handleDNSRecordChanged); - ponder.on("OldRegistryResolvers:DNSRecordDeleted", _handleDNSRecordDeleted); ponder.on( - "OldRegistryResolvers:DNSZonehashChanged", - _handleDNSZonehashChanged + ns( + "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", + ), + _handleTextChanged, ); + ponder.on(ns("OldRegistryResolvers:ContenthashChanged"), _handleContenthashChanged); + ponder.on(ns("OldRegistryResolvers:InterfaceChanged"), _handleInterfaceChanged); + ponder.on(ns("OldRegistryResolvers:AuthorisationChanged"), _handleAuthorisationChanged); + ponder.on(ns("OldRegistryResolvers:VersionChanged"), _handleVersionChanged); + ponder.on(ns("OldRegistryResolvers:DNSRecordChanged"), _handleDNSRecordChanged); + ponder.on(ns("OldRegistryResolvers:DNSRecordDeleted"), _handleDNSRecordDeleted); + ponder.on(ns("OldRegistryResolvers:DNSZonehashChanged"), _handleDNSZonehashChanged); // New registry handlers - ponder.on("Resolver:AddrChanged", _handleAddrChanged); - ponder.on("Resolver:AddressChanged", _handleAddressChanged); - ponder.on("Resolver:NameChanged", _handleNameChanged); - ponder.on("Resolver:ABIChanged", _handleABIChanged); - ponder.on("Resolver:PubkeyChanged", _handlePubkeyChanged); + ponder.on(ns("Resolver:AddrChanged"), _handleAddrChanged); + ponder.on(ns("Resolver:AddressChanged"), _handleAddressChanged); + ponder.on(ns("Resolver:NameChanged"), _handleNameChanged); + ponder.on(ns("Resolver:ABIChanged"), _handleABIChanged); + ponder.on(ns("Resolver:PubkeyChanged"), _handlePubkeyChanged); ponder.on( - "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", - _handleTextChanged + ns("Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"), + _handleTextChanged, ); ponder.on( - "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", - _handleTextChanged + ns( + "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", + ), + _handleTextChanged, ); - ponder.on("Resolver:ContenthashChanged", _handleContenthashChanged); - ponder.on("Resolver:InterfaceChanged", _handleInterfaceChanged); - ponder.on("Resolver:AuthorisationChanged", _handleAuthorisationChanged); - ponder.on("Resolver:VersionChanged", _handleVersionChanged); - ponder.on("Resolver:DNSRecordChanged", _handleDNSRecordChanged); - ponder.on("Resolver:DNSRecordDeleted", _handleDNSRecordDeleted); - ponder.on("Resolver:DNSZonehashChanged", _handleDNSZonehashChanged); + ponder.on(ns("Resolver:ContenthashChanged"), _handleContenthashChanged); + ponder.on(ns("Resolver:InterfaceChanged"), _handleInterfaceChanged); + ponder.on(ns("Resolver:AuthorisationChanged"), _handleAuthorisationChanged); + ponder.on(ns("Resolver:VersionChanged"), _handleVersionChanged); + ponder.on(ns("Resolver:DNSRecordChanged"), _handleDNSRecordChanged); + ponder.on(ns("Resolver:DNSRecordDeleted"), _handleDNSRecordDeleted); + ponder.on(ns("Resolver:DNSZonehashChanged"), _handleDNSZonehashChanged); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/ethereum/ponder.config.ts b/src/ponder-ens-plugins/ethereum/ponder.config.ts index b79293df..cfeb1132 100644 --- a/src/ponder-ens-plugins/ethereum/ponder.config.ts +++ b/src/ponder-ens-plugins/ethereum/ponder.config.ts @@ -1,6 +1,8 @@ import { factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; +import { mainnet } from "viem/chains"; +import { NsReturnType, createNs } from "../chain"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; @@ -18,12 +20,14 @@ const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; const BASE_REGISTRAR_ADDRESS = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85"; -const ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS = - "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5"; -const ETH_REGISTRAR_CONTROLLER_ADDRESS = - "0x253553366Da8546fC250F225fe3d25d0C782303b"; +const ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS = "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5"; +const ETH_REGISTRAR_CONTROLLER_ADDRESS = "0x253553366Da8546fC250F225fe3d25d0C782303b"; const NAME_WRAPPER_ADDRESS = "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401"; +export const ns = createNs(mainnet.id); + +export type NsType = NsReturnType; + export const config = Object.freeze({ networks: { mainnet: { @@ -32,21 +36,21 @@ export const config = Object.freeze({ }, }, contracts: { - RegistryOld: { + [ns("RegistryOld")]: { network: "mainnet", abi: Registry, address: REGISTRY_OLD_ADDRESS, startBlock: 3327417, endBlock: END_BLOCK, }, - Registry: { + [ns("Registry")]: { network: "mainnet", abi: Registry, address: REGISTRY_ADDRESS, startBlock: 9380380, endBlock: END_BLOCK, }, - OldRegistryResolvers: { + [ns("OldRegistryResolvers")]: { network: "mainnet", abi: RESOLVER_ABI, address: factory({ @@ -57,7 +61,7 @@ export const config = Object.freeze({ startBlock: 9380380, endBlock: END_BLOCK, }, - Resolver: { + [ns("Resolver")]: { network: "mainnet", abi: RESOLVER_ABI, address: factory({ @@ -68,28 +72,28 @@ export const config = Object.freeze({ startBlock: 9380380, endBlock: END_BLOCK, }, - BaseRegistrar: { + [ns("BaseRegistrar")]: { network: "mainnet", abi: BaseRegistrar, address: BASE_REGISTRAR_ADDRESS, startBlock: 9380410, endBlock: END_BLOCK, }, - EthRegistrarControllerOld: { + [ns("EthRegistrarControllerOld")]: { network: "mainnet", abi: EthRegistrarControllerOld, address: ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS, startBlock: 9380471, endBlock: END_BLOCK, }, - EthRegistrarController: { + [ns("EthRegistrarController")]: { network: "mainnet", abi: EthRegistrarController, address: ETH_REGISTRAR_CONTROLLER_ADDRESS, startBlock: Math.min(16925618, END_BLOCK), endBlock: END_BLOCK, }, - NameWrapper: { + [ns("NameWrapper")]: { network: "mainnet", abi: NameWrapper, address: NAME_WRAPPER_ADDRESS, diff --git a/src/ponder-ens-plugins/main.ts b/src/ponder-ens-plugins/main.ts index e0095d11..69600def 100644 --- a/src/ponder-ens-plugins/main.ts +++ b/src/ponder-ens-plugins/main.ts @@ -1,3 +1,4 @@ +import basePlugin from "./base/ponder.indexing"; import ethereumPlugin from "./ethereum/ponder.indexing"; /** @@ -5,7 +6,7 @@ import ethereumPlugin from "./ethereum/ponder.indexing"; * It tries to activate all the enlisted plugins. */ function main() { - const plugins = [ethereumPlugin]; + const plugins = [basePlugin, ethereumPlugin]; for (const plugin of plugins) { if (plugin.canActivate) { From 2578b0fc6815641bb32d6cd3ee3769ad1c85116c Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 3 Jan 2025 14:28:57 +0100 Subject: [PATCH 03/30] fix(ens-base-plugin): enalbe domain insert setup event --- src/lib/ens-helpers.ts | 1 + .../base/handlers/Registrar.ts | 6 +- .../base/handlers/Registry.ts | 96 ++++++------- src/ponder-ens-plugins/base/ponder.config.ts | 2 +- .../ethereum/handlers/EthRegistrar.ts | 1 - .../ethereum/handlers/Registry.ts | 127 ++++++++++-------- 6 files changed, 121 insertions(+), 112 deletions(-) diff --git a/src/lib/ens-helpers.ts b/src/lib/ens-helpers.ts index 4e256a7b..a0605bfd 100644 --- a/src/lib/ens-helpers.ts +++ b/src/lib/ens-helpers.ts @@ -3,6 +3,7 @@ import { type Hex, concat, keccak256, namehash, toHex } from "viem"; // TODO: pull from ens utils lib or something export const NAMEHASH_ZERO = namehash(""); export const NAMEHASH_ETH = namehash("eth"); +export const NAMEHASH_BASE_ETH = namehash("base.eth"); // TODO: this should probably be a part of some ens util lib export const makeSubnodeNamehash = (node: Hex, label: Hex) => keccak256(concat([node, label])); diff --git a/src/ponder-ens-plugins/base/handlers/Registrar.ts b/src/ponder-ens-plugins/base/handlers/Registrar.ts index c2b0ad11..3d1b1ce6 100644 --- a/src/ponder-ens-plugins/base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/base/handlers/Registrar.ts @@ -2,7 +2,7 @@ import { type Context, type Event, EventNames, ponder } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; import type { Hex } from "viem"; import { - NAMEHASH_ETH, + NAMEHASH_BASE_ETH, isLabelValid, makeSubnodeNamehash, tokenIdToLabel, @@ -12,7 +12,7 @@ import { PonderEnsIndexingHandlerModule } from "../../types"; import { type NsType, ns } from "../ponder.config"; // all nodes referenced by EthRegistrar are parented to .eth -const ROOT_NODE = NAMEHASH_ETH; +const ROOT_NODE = NAMEHASH_BASE_ETH; const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds async function handleNameRegistered({ @@ -41,6 +41,8 @@ async function handleNameRegistered({ labelName, }); + console.log('handleNameRegistered', { id, owner, expires, label, node, labelName }); + await context.db.update(domains, { id: node }).set({ registrantId: owner, expiryDate: expires + GRACE_PERIOD_SECONDS, diff --git a/src/ponder-ens-plugins/base/handlers/Registry.ts b/src/ponder-ens-plugins/base/handlers/Registry.ts index 5b10271f..222fe618 100644 --- a/src/ponder-ens-plugins/base/handlers/Registry.ts +++ b/src/ponder-ens-plugins/base/handlers/Registry.ts @@ -2,7 +2,12 @@ import { type Context, type Event, ponder } from "ponder:registry"; import { resolvers } from "ponder:schema"; import { domains } from "ponder:schema"; import { type Hex, zeroAddress } from "viem"; -import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../../../lib/ens-helpers"; +import { + NAMEHASH_BASE_ETH, + NAMEHASH_ZERO, + encodeLabelhash, + makeSubnodeNamehash, +} from "../../../lib/ens-helpers"; import { makeResolverId } from "../../../lib/ids"; import { upsertAccount } from "../../../lib/upserts"; import { PonderEnsIndexingHandlerModule } from "../../types"; @@ -16,13 +21,18 @@ async function isDomainMigrated(context: Context, node: Hex) { function isDomainEmpty(domain: typeof domains.$inferSelect) { return ( - domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 + domain.resolverId === null && + domain.ownerId === zeroAddress && + domain.subdomainCount === 0 ); } // a more accurate name for the following function // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context: Context, + node: Hex +) { const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error(`Domain not found: ${node}`); @@ -33,7 +43,10 @@ async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Con .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); + return recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.parentId + ); } } @@ -83,7 +96,9 @@ const _handleNewOwner = let domain = await context.db.find(domains, { id: subnode }); if (domain) { // if the domain already exists, this is just an update of the owner record. - await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); + await context.db + .update(domains, { id: domain.id }) + .set({ ownerId: owner, isMigrated }); } else { // otherwise create the domain domain = await context.db.insert(domains).values({ @@ -94,6 +109,8 @@ const _handleNewOwner = isMigrated, }); + console.log("New domain created", domain); + // and increment parent subdomainCount await context.db .update(domains, { id: node }) @@ -109,12 +126,17 @@ const _handleNewOwner = const labelName = encodeLabelhash(label); const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - await context.db.update(domains, { id: domain.id }).set({ name, labelName }); + await context.db + .update(domains, { id: domain.id }) + .set({ name, labelName }); } // garbage collect newly 'empty' domain iff necessary if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); + await recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.id + ); } }; @@ -174,53 +196,19 @@ async function _handleNewResolver({ } function initRegistryHandlers() { - // // setup on old registry - // ponder.on("RegistryOld:setup", async ({ context }) => { - // // ensure we have an account for the zeroAddress - // await upsertAccount(context, zeroAddress); - - // // ensure we have a root Domain, owned by the zeroAddress - // await context.db.insert(domains).values({ - // id: NAMEHASH_ZERO, - // ownerId: zeroAddress, - // createdAt: 0n, - // isMigrated: false, - // }); - // }); - - // // old registry functions are proxied to the current handlers - // // iff the domain has not yet been migrated - // ponder.on("RegistryOld:NewOwner", async ({ context, event }) => { - // const node = makeSubnodeNamehash(event.args.node, event.args.label); - // const isMigrated = await isDomainMigrated(context, node); - // if (isMigrated) return; - // return _handleNewOwner(false)({ context, event }); - // }); - - // ponder.on("RegistryOld:NewResolver", async ({ context, event }) => { - // // NOTE: the subgraph makes an exception for the root node here - // // but i don't know that that's necessary, as in ponder our root node starts out - // // unmigrated and once the NewOwner event is emitted by the new registry, - // // the root will be considered migrated - // // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 - - // // otherwise, only handle iff not migrated - // const isMigrated = await isDomainMigrated(context, event.args.node); - // if (isMigrated) return; - // return _handleNewResolver({ context, event }); - // }); - - // ponder.on("RegistryOld:NewTTL", async ({ context, event }) => { - // const isMigrated = await isDomainMigrated(context, event.args.node); - // if (isMigrated) return; - // return _handleNewTTL({ context, event }); - // }); - - // ponder.on("RegistryOld:Transfer", async ({ context, event }) => { - // const isMigrated = await isDomainMigrated(context, event.args.node); - // if (isMigrated) return; - // return _handleTransfer({ context, event }); - // }); + // setup on registry + ponder.on(ns("Registry:setup"), async ({ context }) => { + // ensure we have an account for the zeroAddress + await upsertAccount(context, zeroAddress); + + // ensure we have a root Domain, owned by the zeroAddress + await context.db.insert(domains).values({ + id: NAMEHASH_ZERO, + ownerId: zeroAddress, + createdAt: 0n, + isMigrated: false, + }); + }); ponder.on(ns("Registry:NewOwner"), _handleNewOwner(true)); ponder.on(ns("Registry:NewResolver"), _handleNewResolver); diff --git a/src/ponder-ens-plugins/base/ponder.config.ts b/src/ponder-ens-plugins/base/ponder.config.ts index 7fa35982..9ccc14d5 100644 --- a/src/ponder-ens-plugins/base/ponder.config.ts +++ b/src/ponder-ens-plugins/base/ponder.config.ts @@ -19,7 +19,7 @@ import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; // just for testing... -const END_BLOCK = 20_200_200; +const END_BLOCK = 24_559_294; export const ns = createNs(base.id); diff --git a/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts index 45237dc9..0b94c18a 100644 --- a/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts @@ -147,7 +147,6 @@ async function handleNameTransferred({ } function initEthRegistrarHandlers() { - console.log("Indexing Ethereum ENS"); ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); diff --git a/src/ponder-ens-plugins/ethereum/handlers/Registry.ts b/src/ponder-ens-plugins/ethereum/handlers/Registry.ts index 59caa901..b7cc08cb 100644 --- a/src/ponder-ens-plugins/ethereum/handlers/Registry.ts +++ b/src/ponder-ens-plugins/ethereum/handlers/Registry.ts @@ -2,7 +2,11 @@ import { type Context, type Event, ponder } from "ponder:registry"; import { resolvers } from "ponder:schema"; import { domains } from "ponder:schema"; import { type Hex, zeroAddress } from "viem"; -import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../../../lib/ens-helpers"; +import { + NAMEHASH_ZERO, + encodeLabelhash, + makeSubnodeNamehash, +} from "../../../lib/ens-helpers"; import { makeResolverId } from "../../../lib/ids"; import { upsertAccount } from "../../../lib/upserts"; import { PonderEnsIndexingHandlerModule } from "../../types"; @@ -16,13 +20,18 @@ async function isDomainMigrated(context: Context, node: Hex) { function isDomainEmpty(domain: typeof domains.$inferSelect) { return ( - domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 + domain.resolverId === null && + domain.ownerId === zeroAddress && + domain.subdomainCount === 0 ); } // a more accurate name for the following function // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context: Context, + node: Hex +) { const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error(`Domain not found: ${node}`); @@ -33,7 +42,10 @@ async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Con .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); + return recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.parentId + ); } } @@ -83,7 +95,9 @@ const _handleNewOwner = let domain = await context.db.find(domains, { id: subnode }); if (domain) { // if the domain already exists, this is just an update of the owner record. - await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); + await context.db + .update(domains, { id: domain.id }) + .set({ ownerId: owner, isMigrated }); } else { // otherwise create the domain domain = await context.db.insert(domains).values({ @@ -109,12 +123,17 @@ const _handleNewOwner = const labelName = encodeLabelhash(label); const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - await context.db.update(domains, { id: domain.id }).set({ name, labelName }); + await context.db + .update(domains, { id: domain.id }) + .set({ name, labelName }); } // garbage collect newly 'empty' domain iff necessary if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); + await recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.id + ); } }; @@ -174,53 +193,53 @@ async function _handleNewResolver({ } function initRegistryHandlers() { - // // setup on old registry - // ponder.on("RegistryOld:setup", async ({ context }) => { - // // ensure we have an account for the zeroAddress - // await upsertAccount(context, zeroAddress); - - // // ensure we have a root Domain, owned by the zeroAddress - // await context.db.insert(domains).values({ - // id: NAMEHASH_ZERO, - // ownerId: zeroAddress, - // createdAt: 0n, - // isMigrated: false, - // }); - // }); - - // // old registry functions are proxied to the current handlers - // // iff the domain has not yet been migrated - // ponder.on("RegistryOld:NewOwner", async ({ context, event }) => { - // const node = makeSubnodeNamehash(event.args.node, event.args.label); - // const isMigrated = await isDomainMigrated(context, node); - // if (isMigrated) return; - // return _handleNewOwner(false)({ context, event }); - // }); - - // ponder.on("RegistryOld:NewResolver", async ({ context, event }) => { - // // NOTE: the subgraph makes an exception for the root node here - // // but i don't know that that's necessary, as in ponder our root node starts out - // // unmigrated and once the NewOwner event is emitted by the new registry, - // // the root will be considered migrated - // // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 - - // // otherwise, only handle iff not migrated - // const isMigrated = await isDomainMigrated(context, event.args.node); - // if (isMigrated) return; - // return _handleNewResolver({ context, event }); - // }); - - // ponder.on("RegistryOld:NewTTL", async ({ context, event }) => { - // const isMigrated = await isDomainMigrated(context, event.args.node); - // if (isMigrated) return; - // return _handleNewTTL({ context, event }); - // }); - - // ponder.on("RegistryOld:Transfer", async ({ context, event }) => { - // const isMigrated = await isDomainMigrated(context, event.args.node); - // if (isMigrated) return; - // return _handleTransfer({ context, event }); - // }); + // setup on old registry + ponder.on(ns("RegistryOld:setup"), async ({ context }) => { + // ensure we have an account for the zeroAddress + await upsertAccount(context, zeroAddress); + + // ensure we have a root Domain, owned by the zeroAddress + await context.db.insert(domains).values({ + id: NAMEHASH_ZERO, + ownerId: zeroAddress, + createdAt: 0n, + isMigrated: false, + }); + }); + + // old registry functions are proxied to the current handlers + // iff the domain has not yet been migrated + ponder.on(ns("RegistryOld:NewOwner"), async ({ context, event }) => { + const node = makeSubnodeNamehash(event.args.node, event.args.label); + const isMigrated = await isDomainMigrated(context, node); + if (isMigrated) return; + return _handleNewOwner(false)({ context, event }); + }); + + ponder.on(ns("RegistryOld:NewResolver"), async ({ context, event }) => { + // NOTE: the subgraph makes an exception for the root node here + // but i don't know that that's necessary, as in ponder our root node starts out + // unmigrated and once the NewOwner event is emitted by the new registry, + // the root will be considered migrated + // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L246 + + // otherwise, only handle iff not migrated + const isMigrated = await isDomainMigrated(context, event.args.node); + if (isMigrated) return; + return _handleNewResolver({ context, event }); + }); + + ponder.on(ns("RegistryOld:NewTTL"), async ({ context, event }) => { + const isMigrated = await isDomainMigrated(context, event.args.node); + if (isMigrated) return; + return _handleNewTTL({ context, event }); + }); + + ponder.on(ns("RegistryOld:Transfer"), async ({ context, event }) => { + const isMigrated = await isDomainMigrated(context, event.args.node); + if (isMigrated) return; + return _handleTransfer({ context, event }); + }); ponder.on(ns("Registry:NewOwner"), _handleNewOwner(true)); ponder.on(ns("Registry:NewResolver"), _handleNewResolver); From fae95081dc46c908c8ae4d06e2b5d770fecc82f6 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 3 Jan 2025 16:22:31 +0100 Subject: [PATCH 04/30] feat(ponder-ens-plugin): include early adopter registrar contract --- .../base/abis/EARegistrarController.ts | 582 ++++++++++++++++++ .../base/handlers/Registrar.ts | 5 + src/ponder-ens-plugins/base/ponder.config.ts | 17 +- 3 files changed, 601 insertions(+), 3 deletions(-) create mode 100644 src/ponder-ens-plugins/base/abis/EARegistrarController.ts diff --git a/src/ponder-ens-plugins/base/abis/EARegistrarController.ts b/src/ponder-ens-plugins/base/abis/EARegistrarController.ts new file mode 100644 index 00000000..f485bc16 --- /dev/null +++ b/src/ponder-ens-plugins/base/abis/EARegistrarController.ts @@ -0,0 +1,582 @@ +export const EarlyAccessRegistrarController = [ + { + inputs: [ + { + internalType: "contract BaseRegistrar", + name: "base_", + type: "address", + }, + { + internalType: "contract IPriceOracle", + name: "prices_", + type: "address", + }, + { + internalType: "contract IReverseRegistrar", + name: "reverseRegistrar_", + type: "address", + }, + { internalType: "address", name: "owner_", type: "address" }, + { internalType: "bytes32", name: "rootNode_", type: "bytes32" }, + { internalType: "string", name: "rootName_", type: "string" }, + { internalType: "address", name: "paymentReceiver_", type: "address" }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [{ internalType: "address", name: "target", type: "address" }], + name: "AddressEmptyCode", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "AddressInsufficientBalance", + type: "error", + }, + { inputs: [], name: "AlreadyInitialized", type: "error" }, + { + inputs: [{ internalType: "address", name: "sender", type: "address" }], + name: "AlreadyRegisteredWithDiscount", + type: "error", + }, + { + inputs: [{ internalType: "uint256", name: "duration", type: "uint256" }], + name: "DurationTooShort", + type: "error", + }, + { inputs: [], name: "FailedInnerCall", type: "error" }, + { + inputs: [{ internalType: "bytes32", name: "key", type: "bytes32" }], + name: "InactiveDiscount", + type: "error", + }, + { inputs: [], name: "InsufficientValue", type: "error" }, + { + inputs: [ + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "InvalidDiscount", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "key", type: "bytes32" }], + name: "InvalidDiscountAmount", + type: "error", + }, + { inputs: [], name: "InvalidPaymentReceiver", type: "error" }, + { + inputs: [ + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "address", name: "validator", type: "address" }, + ], + name: "InvalidValidator", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "NameNotAvailable", + type: "error", + }, + { inputs: [], name: "NewOwnerIsZeroAddress", type: "error" }, + { inputs: [], name: "NoHandoverRequest", type: "error" }, + { inputs: [], name: "ResolverRequiredWhenDataSupplied", type: "error" }, + { + inputs: [{ internalType: "address", name: "token", type: "address" }], + name: "SafeERC20FailedOperation", + type: "error", + }, + { inputs: [], name: "TransferFailed", type: "error" }, + { inputs: [], name: "Unauthorized", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "registrant", + type: "address", + }, + { + indexed: true, + internalType: "bytes32", + name: "discountKey", + type: "bytes32", + }, + ], + name: "DiscountApplied", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "discountKey", + type: "bytes32", + }, + { + components: [ + { internalType: "bool", name: "active", type: "bool" }, + { + internalType: "address", + name: "discountValidator", + type: "address", + }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + indexed: false, + internalType: "struct EARegistrarController.DiscountDetails", + name: "details", + type: "tuple", + }, + ], + name: "DiscountUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "payee", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "price", + type: "uint256", + }, + ], + name: "ETHPaymentProcessed", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "string", name: "name", type: "string" }, + { + indexed: true, + internalType: "bytes32", + name: "label", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "expires", + type: "uint256", + }, + ], + name: "NameRegistered", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverCanceled", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "pendingOwner", + type: "address", + }, + ], + name: "OwnershipHandoverRequested", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newPaymentReceiver", + type: "address", + }, + ], + name: "PaymentReceiverUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newPrices", + type: "address", + }, + ], + name: "PriceOracleUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "newReverseRegistrar", + type: "address", + }, + ], + name: "ReverseRegistrarUpdated", + type: "event", + }, + { + inputs: [], + name: "MIN_NAME_LENGTH", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MIN_REGISTRATION_DURATION", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "available", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "cancelOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "completeOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "address", name: "owner", type: "address" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + { internalType: "address", name: "resolver", type: "address" }, + { internalType: "bytes[]", name: "data", type: "bytes[]" }, + { internalType: "bool", name: "reverseRecord", type: "bool" }, + ], + internalType: "struct EARegistrarController.RegisterRequest", + name: "request", + type: "tuple", + }, + { internalType: "bytes32", name: "discountKey", type: "bytes32" }, + { internalType: "bytes", name: "validationData", type: "bytes" }, + ], + name: "discountedRegister", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + { internalType: "bytes32", name: "discountKey", type: "bytes32" }, + ], + name: "discountedRegisterPrice", + outputs: [{ internalType: "uint256", name: "price", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "registrant", type: "address" }], + name: "discountedRegistrants", + outputs: [ + { internalType: "bool", name: "hasRegisteredWithDiscount", type: "bool" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "key", type: "bytes32" }], + name: "discounts", + outputs: [ + { internalType: "bool", name: "active", type: "bool" }, + { internalType: "address", name: "discountValidator", type: "address" }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getActiveDiscounts", + outputs: [ + { + components: [ + { internalType: "bool", name: "active", type: "bool" }, + { + internalType: "address", + name: "discountValidator", + type: "address", + }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + internalType: "struct EARegistrarController.DiscountDetails[]", + name: "", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address[]", name: "addresses", type: "address[]" }, + ], + name: "hasRegisteredWithDiscount", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "result", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "pendingOwner", type: "address" }, + ], + name: "ownershipHandoverExpiresAt", + outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "paymentReceiver", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "prices", + outputs: [ + { internalType: "contract IPriceOracle", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_token", type: "address" }, + { internalType: "address", name: "_to", type: "address" }, + { internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "recoverFunds", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "registerPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { internalType: "string", name: "name", type: "string" }, + { internalType: "uint256", name: "duration", type: "uint256" }, + ], + name: "rentPrice", + outputs: [ + { + components: [ + { internalType: "uint256", name: "base", type: "uint256" }, + { internalType: "uint256", name: "premium", type: "uint256" }, + ], + internalType: "struct IPriceOracle.Price", + name: "price", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "requestOwnershipHandover", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "reverseRegistrar", + outputs: [ + { internalType: "contract IReverseRegistrar", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rootName", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "rootNode", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "bool", name: "active", type: "bool" }, + { + internalType: "address", + name: "discountValidator", + type: "address", + }, + { internalType: "bytes32", name: "key", type: "bytes32" }, + { internalType: "uint256", name: "discount", type: "uint256" }, + ], + internalType: "struct EARegistrarController.DiscountDetails", + name: "details", + type: "tuple", + }, + ], + name: "setDiscountDetails", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "paymentReceiver_", type: "address" }, + ], + name: "setPaymentReceiver", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IPriceOracle", + name: "prices_", + type: "address", + }, + ], + name: "setPriceOracle", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IReverseRegistrar", + name: "reverse_", + type: "address", + }, + ], + name: "setReverseRegistrar", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "valid", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "withdrawETH", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/src/ponder-ens-plugins/base/handlers/Registrar.ts b/src/ponder-ens-plugins/base/handlers/Registrar.ts index 3d1b1ce6..7332c95b 100644 --- a/src/ponder-ens-plugins/base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/base/handlers/Registrar.ts @@ -167,6 +167,11 @@ function initEthRegistrarHandlers() { ns("RegistrarController:NameRenewed"), handleNameRenewedByController ); + + ponder.on( + ns("EARegistrarController:NameRegistered"), + handleNameRegisteredByController + ); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/base/ponder.config.ts b/src/ponder-ens-plugins/base/ponder.config.ts index 9ccc14d5..0a4401c0 100644 --- a/src/ponder-ens-plugins/base/ponder.config.ts +++ b/src/ponder-ens-plugins/base/ponder.config.ts @@ -17,9 +17,10 @@ import { BaseRegistrar } from "./abis/BaseRegistrar"; import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; +import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; // just for testing... -const END_BLOCK = 24_559_294; +const END_BLOCK = undefined; export const ns = createNs(base.id); @@ -34,6 +35,9 @@ const BASE_REGISTRAR_START_BLOCK = 17571486; const REGISTRAR_CONTROLLER_ADDRESS = '0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5'; const REGISTRAR_CONTROLLER_START_BLOCK = 18619035; +const EA_REGISTRAR_CONTROLLER_ADDRESS = '0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda'; +const EA_REGISTRAR_CONTROLLER_START_BLOCK = 17575699; + const REVERSE_REGISTRAR_ADDRESS = '0x79ea96012eea67a83431f1701b3dff7e37f9e282'; const REVERSE_REGISTRAR_START_BLOCK = 17571485; @@ -77,14 +81,21 @@ export const config = Object.freeze({ network: "base", abi: BaseRegistrar, address: BASE_REGISTRAR_ADDRESS, - startBlock: 9380410, + startBlock: BASE_REGISTRAR_START_BLOCK, + endBlock: END_BLOCK, + }, + [ns("EARegistrarController")]: { + network: "base", + abi: EarlyAccessRegistrarController, + address: EA_REGISTRAR_CONTROLLER_ADDRESS, + startBlock: EA_REGISTRAR_CONTROLLER_START_BLOCK, endBlock: END_BLOCK, }, [ns("RegistrarController")]: { network: "base", abi: RegistrarController, address: REGISTRAR_CONTROLLER_ADDRESS, - startBlock: Math.min(REGISTRAR_CONTROLLER_START_BLOCK, END_BLOCK), + startBlock: REGISTRAR_CONTROLLER_START_BLOCK, endBlock: END_BLOCK, }, }, From e1dad39ec0bff74f2300affee53809ce0d6121d8 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 3 Jan 2025 18:18:10 +0100 Subject: [PATCH 05/30] feat(ponder-ens-plugin): focus plugins around domain names --- .env.local.example | 6 +++--- ponder.config.ts | 4 ++-- src/ponder-ens-plugins/chain.ts | 12 ++++++++---- src/ponder-ens-plugins/{base => eth.base}/README.md | 0 .../{base => eth.base}/abis/BaseRegistrar.ts | 0 .../{base => eth.base}/abis/EARegistrarController.ts | 0 .../{base => eth.base}/abis/L1Resolver.ts | 0 .../{base => eth.base}/abis/L2Resolver.ts | 0 .../{base => eth.base}/abis/RegistrarController.ts | 0 .../{base => eth.base}/abis/Registry.ts | 0 .../{base => eth.base}/abis/ReverseRegistrar.ts | 0 .../{base => eth.base}/handlers/Registrar.ts | 10 +++++++--- .../{base => eth.base}/handlers/Registry.ts | 0 .../{base => eth.base}/handlers/Resolver.ts | 3 +++ .../{base => eth.base}/ponder.config.ts | 11 +++++++++++ .../{base => eth.base}/ponder.indexing.ts | 3 +-- .../{ethereum => eth}/abis/BaseRegistrar.ts | 0 .../{ethereum => eth}/abis/EthRegistrarController.ts | 0 .../abis/EthRegistrarControllerOld.ts | 0 .../{ethereum => eth}/abis/LegacyPublicResolver.ts | 0 .../{ethereum => eth}/abis/NameWrapper.ts | 0 .../{ethereum => eth}/abis/Registry.ts | 0 .../{ethereum => eth}/abis/Resolver.ts | 0 .../{ethereum => eth}/handlers/EthRegistrar.ts | 0 .../{ethereum => eth}/handlers/NameWrapper.ts | 0 .../{ethereum => eth}/handlers/Registry.ts | 0 .../{ethereum => eth}/handlers/Resolver.ts | 0 .../{ethereum => eth}/ponder.config.ts | 0 .../{ethereum => eth}/ponder.indexing.ts | 3 +-- src/ponder-ens-plugins/main.ts | 4 ++-- 30 files changed, 38 insertions(+), 18 deletions(-) rename src/ponder-ens-plugins/{base => eth.base}/README.md (100%) rename src/ponder-ens-plugins/{base => eth.base}/abis/BaseRegistrar.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/abis/EARegistrarController.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/abis/L1Resolver.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/abis/L2Resolver.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/abis/RegistrarController.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/abis/Registry.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/abis/ReverseRegistrar.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/handlers/Registrar.ts (93%) rename src/ponder-ens-plugins/{base => eth.base}/handlers/Registry.ts (100%) rename src/ponder-ens-plugins/{base => eth.base}/handlers/Resolver.ts (97%) rename src/ponder-ens-plugins/{base => eth.base}/ponder.config.ts (91%) rename src/ponder-ens-plugins/{base => eth.base}/ponder.indexing.ts (85%) rename src/ponder-ens-plugins/{ethereum => eth}/abis/BaseRegistrar.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/abis/EthRegistrarController.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/abis/EthRegistrarControllerOld.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/abis/LegacyPublicResolver.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/abis/NameWrapper.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/abis/Registry.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/abis/Resolver.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/handlers/EthRegistrar.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/handlers/NameWrapper.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/handlers/Registry.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/handlers/Resolver.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/ponder.config.ts (100%) rename src/ponder-ens-plugins/{ethereum => eth}/ponder.indexing.ts (84%) diff --git a/.env.local.example b/.env.local.example index bb3f0599..cc3c2294 100644 --- a/.env.local.example +++ b/.env.local.example @@ -6,10 +6,10 @@ PONDER_RPC_URL_59144=https://linea-rpc.publicnode.com # ponder indexer ens deployment configuration -ENS_DEPLOYMENT_CHAIN_ID=1 +INDEX_ENS_ROOT_NODE=base.eth # ponder indexer database configuration -## one schema name per chain ID, i.e. ponder_ens_${ENS_DEPLOYMENT_CHAIN_ID} -DATABASE_SCHEMA=ponder_ens_1 +## one schema name per chain ID, i.e. ponder_ens_${INDEX_ENS_ROOT_NODE} +DATABASE_SCHEMA=ponder_ens_base.eth DATABASE_URL=postgresql://dbuser:abcd1234@localhost:5432/my_database diff --git a/ponder.config.ts b/ponder.config.ts index 88eea4eb..60f6f85a 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,6 +1,6 @@ import { createConfig } from "ponder"; -import { config as baseConfig } from "./src/ponder-ens-plugins/base/ponder.config"; -import { config as ethereumConfig } from "./src/ponder-ens-plugins/ethereum/ponder.config"; +import { config as baseConfig } from "./src/ponder-ens-plugins/eth.base/ponder.config"; +import { config as ethereumConfig } from "./src/ponder-ens-plugins/eth/ponder.config"; console.log({ networks: { diff --git a/src/ponder-ens-plugins/chain.ts b/src/ponder-ens-plugins/chain.ts index 1c2428c3..eb16db3a 100644 --- a/src/ponder-ens-plugins/chain.ts +++ b/src/ponder-ens-plugins/chain.ts @@ -1,13 +1,17 @@ -export function isChainIndexingActive(chainId: number) { - if (!process.env.ENS_DEPLOYMENT_CHAIN_ID) { - console.warn("ENS_DEPLOYMENT_CHAIN_ID is not set"); + +// TODO: change the chainId to the root path (i.e. ("base.eth")) +export function isChainIndexingActive(rootPath: `${string}.eth`) { + if (!process.env.INDEX_ENS_ROOT_NODE) { + console.warn("INDEX_ENS_ROOT_NODE is not set"); return false; } - return parseInt(process.env.ENS_DEPLOYMENT_CHAIN_ID, 10) === chainId; + return process.env.INDEX_ENS_ROOT_NODE === rootPath; } +// TODO: change the chainId to the root node value (i.e. namehash("base.eth")) export function createNs(chainId: ChainId) { + /** Creates a name-spaced contract name */ return function ns( contractName: ContractName, ): NsReturnType { diff --git a/src/ponder-ens-plugins/base/README.md b/src/ponder-ens-plugins/eth.base/README.md similarity index 100% rename from src/ponder-ens-plugins/base/README.md rename to src/ponder-ens-plugins/eth.base/README.md diff --git a/src/ponder-ens-plugins/base/abis/BaseRegistrar.ts b/src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/base/abis/BaseRegistrar.ts rename to src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts diff --git a/src/ponder-ens-plugins/base/abis/EARegistrarController.ts b/src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts similarity index 100% rename from src/ponder-ens-plugins/base/abis/EARegistrarController.ts rename to src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts diff --git a/src/ponder-ens-plugins/base/abis/L1Resolver.ts b/src/ponder-ens-plugins/eth.base/abis/L1Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/base/abis/L1Resolver.ts rename to src/ponder-ens-plugins/eth.base/abis/L1Resolver.ts diff --git a/src/ponder-ens-plugins/base/abis/L2Resolver.ts b/src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/base/abis/L2Resolver.ts rename to src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts diff --git a/src/ponder-ens-plugins/base/abis/RegistrarController.ts b/src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts similarity index 100% rename from src/ponder-ens-plugins/base/abis/RegistrarController.ts rename to src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts diff --git a/src/ponder-ens-plugins/base/abis/Registry.ts b/src/ponder-ens-plugins/eth.base/abis/Registry.ts similarity index 100% rename from src/ponder-ens-plugins/base/abis/Registry.ts rename to src/ponder-ens-plugins/eth.base/abis/Registry.ts diff --git a/src/ponder-ens-plugins/base/abis/ReverseRegistrar.ts b/src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/base/abis/ReverseRegistrar.ts rename to src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts diff --git a/src/ponder-ens-plugins/base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts similarity index 93% rename from src/ponder-ens-plugins/base/handlers/Registrar.ts rename to src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index 7332c95b..6e866d1e 100644 --- a/src/ponder-ens-plugins/base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -41,7 +41,11 @@ async function handleNameRegistered({ labelName, }); - console.log('handleNameRegistered', { id, owner, expires, label, node, labelName }); + console.log('handleNameRegistered', { event: { + block: event.block.number, + tx: event.transaction.hash, + logIndex: event.log.logIndex, + }, id, owner, expires, label, node, labelName }); await context.db.update(domains, { id: node }).set({ registrantId: owner, @@ -154,7 +158,7 @@ async function handleNameTransferred({ // TODO: log Event } -function initEthRegistrarHandlers() { +function initBaseRegistrarHandlers() { ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); @@ -175,5 +179,5 @@ function initEthRegistrarHandlers() { } export const handlerModule: Readonly = { - attachHandlers: initEthRegistrarHandlers, + attachHandlers: initBaseRegistrarHandlers, }; diff --git a/src/ponder-ens-plugins/base/handlers/Registry.ts b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts similarity index 100% rename from src/ponder-ens-plugins/base/handlers/Registry.ts rename to src/ponder-ens-plugins/eth.base/handlers/Registry.ts diff --git a/src/ponder-ens-plugins/base/handlers/Resolver.ts b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts similarity index 97% rename from src/ponder-ens-plugins/base/handlers/Resolver.ts rename to src/ponder-ens-plugins/eth.base/handlers/Resolver.ts index 2382fd5b..4a692b5e 100644 --- a/src/ponder-ens-plugins/base/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts @@ -251,6 +251,9 @@ export function initResolverHandlers() { ponder.on(ns("Resolver:DNSRecordChanged"), _handleDNSRecordChanged); ponder.on(ns("Resolver:DNSRecordDeleted"), _handleDNSRecordDeleted); ponder.on(ns("Resolver:DNSZonehashChanged"), _handleDNSZonehashChanged); + + // FIXME: make sure to use domain name in the indexing handler name + // ponder.on("eth.base.Resolver:AddrChanged", _handleAddrChanged); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts similarity index 91% rename from src/ponder-ens-plugins/base/ponder.config.ts rename to src/ponder-ens-plugins/eth.base/ponder.config.ts index 0a4401c0..1261cff5 100644 --- a/src/ponder-ens-plugins/base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -77,6 +77,17 @@ export const config = Object.freeze({ startBlock: L2_RESOLVER_START_BLOCK, endBlock: END_BLOCK, }, + ["eth.base.Resolver"]: { + network: "base", + abi: L2Resolver, + address: factory({ + address: L2_RESOLVER_ADDRESS, + event: getAbiItem({ abi: Registry, name: "NewResolver" }), + parameter: "resolver", + }), + startBlock: L2_RESOLVER_START_BLOCK, + endBlock: END_BLOCK, + }, [ns("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, diff --git a/src/ponder-ens-plugins/base/ponder.indexing.ts b/src/ponder-ens-plugins/eth.base/ponder.indexing.ts similarity index 85% rename from src/ponder-ens-plugins/base/ponder.indexing.ts rename to src/ponder-ens-plugins/eth.base/ponder.indexing.ts index 99058c9c..cda0f6e9 100644 --- a/src/ponder-ens-plugins/base/ponder.indexing.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.indexing.ts @@ -1,10 +1,9 @@ -import { base } from "viem/chains"; import { isChainIndexingActive } from "../chain"; import { PonderEnsModule } from "../types"; export default { get canActivate() { - return isChainIndexingActive(base.id); + return isChainIndexingActive('base.eth'); }, async activate() { diff --git a/src/ponder-ens-plugins/ethereum/abis/BaseRegistrar.ts b/src/ponder-ens-plugins/eth/abis/BaseRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/abis/BaseRegistrar.ts rename to src/ponder-ens-plugins/eth/abis/BaseRegistrar.ts diff --git a/src/ponder-ens-plugins/ethereum/abis/EthRegistrarController.ts b/src/ponder-ens-plugins/eth/abis/EthRegistrarController.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/abis/EthRegistrarController.ts rename to src/ponder-ens-plugins/eth/abis/EthRegistrarController.ts diff --git a/src/ponder-ens-plugins/ethereum/abis/EthRegistrarControllerOld.ts b/src/ponder-ens-plugins/eth/abis/EthRegistrarControllerOld.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/abis/EthRegistrarControllerOld.ts rename to src/ponder-ens-plugins/eth/abis/EthRegistrarControllerOld.ts diff --git a/src/ponder-ens-plugins/ethereum/abis/LegacyPublicResolver.ts b/src/ponder-ens-plugins/eth/abis/LegacyPublicResolver.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/abis/LegacyPublicResolver.ts rename to src/ponder-ens-plugins/eth/abis/LegacyPublicResolver.ts diff --git a/src/ponder-ens-plugins/ethereum/abis/NameWrapper.ts b/src/ponder-ens-plugins/eth/abis/NameWrapper.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/abis/NameWrapper.ts rename to src/ponder-ens-plugins/eth/abis/NameWrapper.ts diff --git a/src/ponder-ens-plugins/ethereum/abis/Registry.ts b/src/ponder-ens-plugins/eth/abis/Registry.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/abis/Registry.ts rename to src/ponder-ens-plugins/eth/abis/Registry.ts diff --git a/src/ponder-ens-plugins/ethereum/abis/Resolver.ts b/src/ponder-ens-plugins/eth/abis/Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/abis/Resolver.ts rename to src/ponder-ens-plugins/eth/abis/Resolver.ts diff --git a/src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/handlers/EthRegistrar.ts rename to src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts diff --git a/src/ponder-ens-plugins/ethereum/handlers/NameWrapper.ts b/src/ponder-ens-plugins/eth/handlers/NameWrapper.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/handlers/NameWrapper.ts rename to src/ponder-ens-plugins/eth/handlers/NameWrapper.ts diff --git a/src/ponder-ens-plugins/ethereum/handlers/Registry.ts b/src/ponder-ens-plugins/eth/handlers/Registry.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/handlers/Registry.ts rename to src/ponder-ens-plugins/eth/handlers/Registry.ts diff --git a/src/ponder-ens-plugins/ethereum/handlers/Resolver.ts b/src/ponder-ens-plugins/eth/handlers/Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/handlers/Resolver.ts rename to src/ponder-ens-plugins/eth/handlers/Resolver.ts diff --git a/src/ponder-ens-plugins/ethereum/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts similarity index 100% rename from src/ponder-ens-plugins/ethereum/ponder.config.ts rename to src/ponder-ens-plugins/eth/ponder.config.ts diff --git a/src/ponder-ens-plugins/ethereum/ponder.indexing.ts b/src/ponder-ens-plugins/eth/ponder.indexing.ts similarity index 84% rename from src/ponder-ens-plugins/ethereum/ponder.indexing.ts rename to src/ponder-ens-plugins/eth/ponder.indexing.ts index bedd0227..b6091ccb 100644 --- a/src/ponder-ens-plugins/ethereum/ponder.indexing.ts +++ b/src/ponder-ens-plugins/eth/ponder.indexing.ts @@ -1,10 +1,9 @@ -import { mainnet } from "viem/chains"; import { isChainIndexingActive } from "../chain"; import { PonderEnsModule } from "../types"; export default { get canActivate() { - return isChainIndexingActive(mainnet.id); + return isChainIndexingActive('.eth'); }, async activate() { diff --git a/src/ponder-ens-plugins/main.ts b/src/ponder-ens-plugins/main.ts index 69600def..ad9bb3bf 100644 --- a/src/ponder-ens-plugins/main.ts +++ b/src/ponder-ens-plugins/main.ts @@ -1,5 +1,5 @@ -import basePlugin from "./base/ponder.indexing"; -import ethereumPlugin from "./ethereum/ponder.indexing"; +import basePlugin from "./eth.base/ponder.indexing"; +import ethereumPlugin from "./eth/ponder.indexing"; /** * Main entry point for the Ponder ENS plugins. From 1931ec7178b6e93fbf4d33ab806ac5c20d92d2c7 Mon Sep 17 00:00:00 2001 From: shrugs Date: Fri, 3 Jan 2025 12:51:00 -0600 Subject: [PATCH 06/30] checkpoint: pull shared logic for registry and resolvers into shared handler lib --- ponder.config.ts | 2 +- src/handlers/Registry.ts | 183 ++++++++++ src/handlers/Resolver.ts | 270 ++++++++++++++ .../eth.base/handlers/Registry.ts | 225 +----------- .../eth.base/handlers/Resolver.ts | 280 ++------------- .../eth/handlers/Registry.ts | 224 +----------- .../eth/handlers/Resolver.ts | 339 +++--------------- 7 files changed, 559 insertions(+), 964 deletions(-) create mode 100644 src/handlers/Registry.ts create mode 100644 src/handlers/Resolver.ts diff --git a/ponder.config.ts b/ponder.config.ts index 60f6f85a..817673ff 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -11,7 +11,7 @@ console.log({ ...baseConfig.contracts, ...ethereumConfig.contracts, }, -}) +}); export default createConfig({ networks: { diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts new file mode 100644 index 00000000..fe5471ef --- /dev/null +++ b/src/handlers/Registry.ts @@ -0,0 +1,183 @@ +import { type Context, type Event } from "ponder:registry"; +import { resolvers } from "ponder:schema"; +import { domains } from "ponder:schema"; +import { type Hex, zeroAddress } from "viem"; +import { mainnet } from "viem/chains"; +import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; +import { makeResolverId } from "../lib/ids"; +import { upsertAccount } from "../lib/upserts"; +import { NsReturnType } from "../ponder-ens-plugins/chain"; + +type NsType = NsReturnType; + +export async function setup({ context }: { context: Context }) { + // ensure we have an account for the zeroAddress + await upsertAccount(context, zeroAddress); + + // ensure we have a root Domain, owned by the zeroAddress + await context.db.insert(domains).values({ + id: NAMEHASH_ZERO, + ownerId: zeroAddress, + createdAt: 0n, + isMigrated: false, + }); +} + +function isDomainEmpty(domain: typeof domains.$inferSelect) { + return ( + domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 + ); +} + +// a more accurate name for the following function +// https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { + const domain = await context.db.find(domains, { id: node }); + if (!domain) throw new Error(`Domain not found: ${node}`); + + if (isDomainEmpty(domain) && domain.parentId !== null) { + // decrement parent's subdomain count + await context.db + .update(domains, { id: domain.parentId }) + .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); + + // recurse to parent + return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); + } +} + +export async function handleTransfer({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, owner } = event.args; + + // ensure owner account + await upsertAccount(context, owner); + + // ensure domain & update owner + await context.db + .insert(domains) + .values([{ id: node, ownerId: owner, createdAt: event.block.timestamp }]) + .onConflictDoUpdate({ ownerId: owner }); + + // garbage collect newly 'empty' domain iff necessary + if (owner === zeroAddress) { + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); + } + + // TODO: log DomainEvent +} + +export const handleNewOwner = + (isMigrated: boolean) => + async ({ + context, + event, + }: { + context: Context; + event: Event>; + }) => { + const { label, node, owner } = event.args; + + const subnode = makeSubnodeNamehash(node, label); + + // ensure owner + await upsertAccount(context, owner); + + // note that we set isMigrated so that if this domain is being interacted with on the new registry, its migration status is set here + let domain = await context.db.find(domains, { id: subnode }); + if (domain) { + // if the domain already exists, this is just an update of the owner record. + await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); + } else { + // otherwise create the domain + domain = await context.db.insert(domains).values({ + id: subnode, + ownerId: owner, + parentId: node, + createdAt: event.block.timestamp, + isMigrated, + }); + + // and increment parent subdomainCount + await context.db + .update(domains, { id: node }) + .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); + } + + // if the domain doesn't yet have a name, construct it here + if (!domain.name) { + const parent = await context.db.find(domains, { id: node }); + + // TODO: implement sync rainbow table lookups + // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L111 + const labelName = encodeLabelhash(label); + const name = parent?.name ? `${labelName}.${parent.name}` : labelName; + + await context.db.update(domains, { id: domain.id }).set({ name, labelName }); + } + + // garbage collect newly 'empty' domain iff necessary + if (owner === zeroAddress) { + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); + } + }; + +export async function handleNewTTL({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, ttl } = event.args; + + // TODO: handle the edge case in which the domain no longer exists? + // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L215 + // NOTE: i'm not sure this needs to be here, as domains are never deleted (??) + await context.db.update(domains, { id: node }).set({ ttl }); + + // TODO: log DomainEvent +} + +export async function handleNewResolver({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, resolver: resolverAddress } = event.args; + + // if zeroing out a domain's resolver, remove the reference instead of tracking a zeroAddress Resolver + // NOTE: old resolver resources are kept for event logs + if (event.args.resolver === zeroAddress) { + await context.db.update(domains, { id: node }).set({ resolverId: null }); + + // garbage collect newly 'empty' domain iff necessary + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); + } else { + // otherwise upsert the resolver + const resolverId = makeResolverId(node, resolverAddress); + + const resolver = await context.db + .insert(resolvers) + .values({ + id: resolverId, + domainId: event.args.node, + address: event.args.resolver, + }) + .onConflictDoNothing(); + + // update the domain to point to it, and denormalize the eth addr + await context.db + .update(domains, { id: node }) + .set({ resolverId, resolvedAddress: resolver?.addrId }); + } + + // TODO: log DomainEvent +} diff --git a/src/handlers/Resolver.ts b/src/handlers/Resolver.ts new file mode 100644 index 00000000..b8355f8c --- /dev/null +++ b/src/handlers/Resolver.ts @@ -0,0 +1,270 @@ +import { type Context, type Event } from "ponder:registry"; +import { domains, resolvers } from "ponder:schema"; +import { base, mainnet } from "viem/chains"; +import { hasNullByte, uniq } from "../lib/helpers"; +import { makeResolverId } from "../lib/ids"; +import { upsertAccount, upsertResolver } from "../lib/upserts"; +import { NsReturnType } from "../ponder-ens-plugins/chain"; + +type NsType = NsReturnType; + +// there is a legacy resolver abi with different TextChanged events. +// luckily the subgraph doesn't care about the value parameter so we can use a union +// to unify the codepath +type AnyTextChangedEvent = + | Event< + NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> + > + | Event< + NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> + > + | Event> + | Event< + NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> + > + | Event< + NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> + >; + +export async function handleAddrChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { a: address, node } = event.args; + await upsertAccount(context, address); + + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + addrId: address, + }); + + // materialize the resolved add to the domain iff this resolver is active + const domain = await context.db.find(domains, { id: node }); + if (domain?.resolverId === id) { + await context.db.update(domains, { id: node }).set({ resolvedAddress: address }); + } + + // TODO: log ResolverEvent +} + +export async function handleAddressChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, coinType, newAddress } = event.args; + await upsertAccount(context, newAddress); + + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // upsert the new coinType + await context.db + .update(resolvers, { id }) + .set({ coinTypes: uniq([...resolver.coinTypes, coinType]) }); + + // TODO: log ResolverEvent +} + +export async function handleNameChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, name } = event.args; + if (hasNullByte(name)) return; + + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +export async function handleABIChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +export async function handlePubkeyChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +export async function handleTextChanged({ + context, + event, +}: { + context: Context; + event: AnyTextChangedEvent; +}) { + const { node, key } = event.args; + const id = makeResolverId(node, event.log.address); + const resolver = await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // upsert new key + await context.db.update(resolvers, { id }).set({ texts: uniq([...resolver.texts, key]) }); + + // TODO: log ResolverEvent +} + +export async function handleContenthashChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node, hash } = event.args; + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + contentHash: hash, + }); + + // TODO: log ResolverEvent +} + +export async function handleInterfaceChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +export async function handleAuthorisationChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + await upsertResolver(context, { + id, + domainId: node, + address: event.log.address, + }); + + // TODO: log ResolverEvent +} + +export async function handleVersionChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // a version change nulls out the resolver + const { node } = event.args; + const id = makeResolverId(node, event.log.address); + const domain = await context.db.find(domains, { id: node }); + if (!domain) throw new Error("domain expected"); + + // materialize the Domain's resolvedAddress field + if (domain.resolverId === id) { + await context.db.update(domains, { id: node }).set({ resolvedAddress: null }); + } + + // clear out the resolver's info + await context.db.update(resolvers, { id }).set({ + addrId: null, + contentHash: null, + coinTypes: [], + texts: [], + }); + + // TODO: log ResolverEvent +} + +export async function handleDNSRecordChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // subgraph ignores +} + +export async function handleDNSRecordDeleted({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // subgraph ignores +} + +export async function handleDNSZonehashChanged({ + context, + event, +}: { + context: Context; + event: Event>; +}) { + // subgraph ignores +} diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts index 222fe618..36ad32fe 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts @@ -1,219 +1,20 @@ -import { type Context, type Event, ponder } from "ponder:registry"; -import { resolvers } from "ponder:schema"; -import { domains } from "ponder:schema"; -import { type Hex, zeroAddress } from "viem"; +import { ponder } from "ponder:registry"; import { - NAMEHASH_BASE_ETH, - NAMEHASH_ZERO, - encodeLabelhash, - makeSubnodeNamehash, -} from "../../../lib/ens-helpers"; -import { makeResolverId } from "../../../lib/ids"; -import { upsertAccount } from "../../../lib/upserts"; + handleNewOwner, + handleNewResolver, + handleNewTTL, + handleTransfer, + setup, +} from "../../../handlers/Registry"; import { PonderEnsIndexingHandlerModule } from "../../types"; -import { type NsType, ns } from "../ponder.config"; - -// a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not -async function isDomainMigrated(context: Context, node: Hex) { - const domain = await context.db.find(domains, { id: node }); - return domain?.isMigrated ?? false; -} - -function isDomainEmpty(domain: typeof domains.$inferSelect) { - return ( - domain.resolverId === null && - domain.ownerId === zeroAddress && - domain.subdomainCount === 0 - ); -} - -// a more accurate name for the following function -// https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context: Context, - node: Hex -) { - const domain = await context.db.find(domains, { id: node }); - if (!domain) throw new Error(`Domain not found: ${node}`); - - if (isDomainEmpty(domain) && domain.parentId !== null) { - // decrement parent's subdomain count - await context.db - .update(domains, { id: domain.parentId }) - .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); - - // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.parentId - ); - } -} - -async function _handleTransfer({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, owner } = event.args; - - // ensure owner account - await upsertAccount(context, owner); - - // ensure domain & update owner - await context.db - .insert(domains) - .values([{ id: node, ownerId: owner, createdAt: event.block.timestamp }]) - .onConflictDoUpdate({ ownerId: owner }); - - // garbage collect newly 'empty' domain iff necessary - if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); - } - - // TODO: log DomainEvent -} - -const _handleNewOwner = - (isMigrated: boolean) => - async ({ - context, - event, - }: { - context: Context; - event: Event>; - }) => { - const { label, node, owner } = event.args; - - const subnode = makeSubnodeNamehash(node, label); - - // ensure owner - await upsertAccount(context, owner); - - // note that we set isMigrated so that if this domain is being interacted with on the new registry, its migration status is set here - let domain = await context.db.find(domains, { id: subnode }); - if (domain) { - // if the domain already exists, this is just an update of the owner record. - await context.db - .update(domains, { id: domain.id }) - .set({ ownerId: owner, isMigrated }); - } else { - // otherwise create the domain - domain = await context.db.insert(domains).values({ - id: subnode, - ownerId: owner, - parentId: node, - createdAt: event.block.timestamp, - isMigrated, - }); - - console.log("New domain created", domain); - - // and increment parent subdomainCount - await context.db - .update(domains, { id: node }) - .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); - } - - // if the domain doesn't yet have a name, construct it here - if (!domain.name) { - const parent = await context.db.find(domains, { id: node }); - - // TODO: implement sync rainbow table lookups - // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L111 - const labelName = encodeLabelhash(label); - const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - - await context.db - .update(domains, { id: domain.id }) - .set({ name, labelName }); - } - - // garbage collect newly 'empty' domain iff necessary - if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.id - ); - } - }; - -async function _handleNewTTL({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, ttl } = event.args; - - // TODO: handle the edge case in which the domain no longer exists? - // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L215 - // NOTE: i'm not sure this needs to be here, as domains are never deleted (??) - await context.db.update(domains, { id: node }).set({ ttl }); - - // TODO: log DomainEvent -} - -async function _handleNewResolver({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, resolver: resolverAddress } = event.args; - - // if zeroing out a domain's resolver, remove the reference instead of tracking a zeroAddress Resolver - // NOTE: old resolver resources are kept for event logs - if (event.args.resolver === zeroAddress) { - await context.db.update(domains, { id: node }).set({ resolverId: null }); - - // garbage collect newly 'empty' domain iff necessary - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); - } else { - // otherwise upsert the resolver - const resolverId = makeResolverId(node, resolverAddress); - - const resolver = await context.db - .insert(resolvers) - .values({ - id: resolverId, - domainId: event.args.node, - address: event.args.resolver, - }) - .onConflictDoNothing(); - - // update the domain to point to it, and denormalize the eth addr - await context.db - .update(domains, { id: node }) - .set({ resolverId, resolvedAddress: resolver?.addrId }); - } - - // TODO: log DomainEvent -} +import { ns } from "../ponder.config"; function initRegistryHandlers() { - // setup on registry - ponder.on(ns("Registry:setup"), async ({ context }) => { - // ensure we have an account for the zeroAddress - await upsertAccount(context, zeroAddress); - - // ensure we have a root Domain, owned by the zeroAddress - await context.db.insert(domains).values({ - id: NAMEHASH_ZERO, - ownerId: zeroAddress, - createdAt: 0n, - isMigrated: false, - }); - }); - - ponder.on(ns("Registry:NewOwner"), _handleNewOwner(true)); - ponder.on(ns("Registry:NewResolver"), _handleNewResolver); - ponder.on(ns("Registry:NewTTL"), _handleNewTTL); - ponder.on(ns("Registry:Transfer"), _handleTransfer); + ponder.on(ns("Registry:setup"), setup); + ponder.on(ns("Registry:NewOwner"), handleNewOwner(true)); + ponder.on(ns("Registry:NewResolver"), handleNewResolver); + ponder.on(ns("Registry:NewTTL"), handleNewTTL); + ponder.on(ns("Registry:Transfer"), handleTransfer); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts index 4a692b5e..c8748bf6 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts @@ -1,259 +1,35 @@ -import { type Context, type Event, ponder } from "ponder:registry"; -import { domains, resolvers } from "ponder:schema"; -import { hasNullByte, uniq } from "../../../lib/helpers"; -import { makeResolverId } from "../../../lib/ids"; -import { upsertAccount, upsertResolver } from "../../../lib/upserts"; +import { ponder } from "ponder:registry"; +import { + handleABIChanged, + handleAddrChanged, + handleAddressChanged, + handleContenthashChanged, + handleDNSRecordChanged, + handleDNSRecordDeleted, + handleDNSZonehashChanged, + handleInterfaceChanged, + handleNameChanged, + handlePubkeyChanged, + handleTextChanged, + handleVersionChanged, +} from "../../../handlers/Resolver"; import { PonderEnsIndexingHandlerModule } from "../../types"; -import { type NsType, ns } from "../ponder.config"; - -async function _handleAddrChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { a: address, node } = event.args; - await upsertAccount(context, address); - - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - addrId: address, - }); - - // materialize the resolved add to the domain iff this resolver is active - const domain = await context.db.find(domains, { id: node }); - if (domain?.resolverId === id) { - await context.db - .update(domains, { id: node }) - .set({ resolvedAddress: address }); - } - - // TODO: log ResolverEvent -} - -async function _handleAddressChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, coinType, newAddress } = event.args; - await upsertAccount(context, newAddress); - - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // upsert the new coinType - await context.db - .update(resolvers, { id }) - .set({ coinTypes: uniq([...resolver.coinTypes, coinType]) }); - - // TODO: log ResolverEvent -} - -async function _handleNameChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, name } = event.args; - if (hasNullByte(name)) return; - - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handleABIChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handlePubkeyChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handleTextChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, key } = event.args; - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // upsert new key - await context.db - .update(resolvers, { id }) - .set({ texts: uniq([...resolver.texts, key]) }); - - // TODO: log ResolverEvent -} - -async function _handleContenthashChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, hash } = event.args; - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - contentHash: hash, - }); - - // TODO: log ResolverEvent -} - -async function _handleInterfaceChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handleVersionChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // a version change nulls out the resolver - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - const domain = await context.db.find(domains, { id: node }); - if (!domain) throw new Error("domain expected"); - - // materialize the Domain's resolvedAddress field - if (domain.resolverId === id) { - await context.db - .update(domains, { id: node }) - .set({ resolvedAddress: null }); - } - - // clear out the resolver's info - await context.db.update(resolvers, { id }).set({ - addrId: null, - contentHash: null, - coinTypes: [], - texts: [], - }); - - // TODO: log ResolverEvent -} - -async function _handleDNSRecordChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // subgraph ignores -} - -async function _handleDNSRecordDeleted({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // subgraph ignores -} - -async function _handleDNSZonehashChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // subgraph ignores -} +import { ns } from "../ponder.config"; export function initResolverHandlers() { // New registry handlers - ponder.on(ns("Resolver:AddrChanged"), _handleAddrChanged); - ponder.on(ns("Resolver:AddressChanged"), _handleAddressChanged); - ponder.on(ns("Resolver:NameChanged"), _handleNameChanged); - ponder.on(ns("Resolver:ABIChanged"), _handleABIChanged); - ponder.on(ns("Resolver:PubkeyChanged"), _handlePubkeyChanged); - ponder.on(ns("Resolver:TextChanged"), _handleTextChanged); - ponder.on(ns("Resolver:ContenthashChanged"), _handleContenthashChanged); - ponder.on(ns("Resolver:InterfaceChanged"), _handleInterfaceChanged); - ponder.on(ns("Resolver:VersionChanged"), _handleVersionChanged); - ponder.on(ns("Resolver:DNSRecordChanged"), _handleDNSRecordChanged); - ponder.on(ns("Resolver:DNSRecordDeleted"), _handleDNSRecordDeleted); - ponder.on(ns("Resolver:DNSZonehashChanged"), _handleDNSZonehashChanged); - - // FIXME: make sure to use domain name in the indexing handler name - // ponder.on("eth.base.Resolver:AddrChanged", _handleAddrChanged); + ponder.on(ns("Resolver:AddrChanged"), handleAddrChanged); + ponder.on(ns("Resolver:AddressChanged"), handleAddressChanged); + ponder.on(ns("Resolver:NameChanged"), handleNameChanged); + ponder.on(ns("Resolver:ABIChanged"), handleABIChanged); + ponder.on(ns("Resolver:PubkeyChanged"), handlePubkeyChanged); + ponder.on(ns("Resolver:TextChanged"), handleTextChanged); + ponder.on(ns("Resolver:ContenthashChanged"), handleContenthashChanged); + ponder.on(ns("Resolver:InterfaceChanged"), handleInterfaceChanged); + ponder.on(ns("Resolver:VersionChanged"), handleVersionChanged); + ponder.on(ns("Resolver:DNSRecordChanged"), handleDNSRecordChanged); + ponder.on(ns("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); + ponder.on(ns("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/eth/handlers/Registry.ts b/src/ponder-ens-plugins/eth/handlers/Registry.ts index b7cc08cb..9bbaab51 100644 --- a/src/ponder-ens-plugins/eth/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth/handlers/Registry.ts @@ -1,16 +1,16 @@ -import { type Context, type Event, ponder } from "ponder:registry"; -import { resolvers } from "ponder:schema"; +import { type Context, ponder } from "ponder:registry"; import { domains } from "ponder:schema"; -import { type Hex, zeroAddress } from "viem"; +import { type Hex } from "viem"; import { - NAMEHASH_ZERO, - encodeLabelhash, - makeSubnodeNamehash, -} from "../../../lib/ens-helpers"; -import { makeResolverId } from "../../../lib/ids"; -import { upsertAccount } from "../../../lib/upserts"; + handleNewOwner, + handleNewResolver, + handleNewTTL, + handleTransfer, + setup, +} from "../../../handlers/Registry"; +import { makeSubnodeNamehash } from "../../../lib/ens-helpers"; import { PonderEnsIndexingHandlerModule } from "../../types"; -import { NsType, ns } from "../ponder.config"; +import { ns } from "../ponder.config"; // a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not async function isDomainMigrated(context: Context, node: Hex) { @@ -18,194 +18,8 @@ async function isDomainMigrated(context: Context, node: Hex) { return domain?.isMigrated ?? false; } -function isDomainEmpty(domain: typeof domains.$inferSelect) { - return ( - domain.resolverId === null && - domain.ownerId === zeroAddress && - domain.subdomainCount === 0 - ); -} - -// a more accurate name for the following function -// https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context: Context, - node: Hex -) { - const domain = await context.db.find(domains, { id: node }); - if (!domain) throw new Error(`Domain not found: ${node}`); - - if (isDomainEmpty(domain) && domain.parentId !== null) { - // decrement parent's subdomain count - await context.db - .update(domains, { id: domain.parentId }) - .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); - - // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.parentId - ); - } -} - -async function _handleTransfer({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, owner } = event.args; - - // ensure owner account - await upsertAccount(context, owner); - - // ensure domain & update owner - await context.db - .insert(domains) - .values([{ id: node, ownerId: owner, createdAt: event.block.timestamp }]) - .onConflictDoUpdate({ ownerId: owner }); - - // garbage collect newly 'empty' domain iff necessary - if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); - } - - // TODO: log DomainEvent -} - -const _handleNewOwner = - (isMigrated: boolean) => - async ({ - context, - event, - }: { - context: Context; - event: Event>; - }) => { - const { label, node, owner } = event.args; - - const subnode = makeSubnodeNamehash(node, label); - - // ensure owner - await upsertAccount(context, owner); - - // note that we set isMigrated so that if this domain is being interacted with on the new registry, its migration status is set here - let domain = await context.db.find(domains, { id: subnode }); - if (domain) { - // if the domain already exists, this is just an update of the owner record. - await context.db - .update(domains, { id: domain.id }) - .set({ ownerId: owner, isMigrated }); - } else { - // otherwise create the domain - domain = await context.db.insert(domains).values({ - id: subnode, - ownerId: owner, - parentId: node, - createdAt: event.block.timestamp, - isMigrated, - }); - - // and increment parent subdomainCount - await context.db - .update(domains, { id: node }) - .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); - } - - // if the domain doesn't yet have a name, construct it here - if (!domain.name) { - const parent = await context.db.find(domains, { id: node }); - - // TODO: implement sync rainbow table lookups - // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L111 - const labelName = encodeLabelhash(label); - const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - - await context.db - .update(domains, { id: domain.id }) - .set({ name, labelName }); - } - - // garbage collect newly 'empty' domain iff necessary - if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.id - ); - } - }; - -async function _handleNewTTL({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, ttl } = event.args; - - // TODO: handle the edge case in which the domain no longer exists? - // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L215 - // NOTE: i'm not sure this needs to be here, as domains are never deleted (??) - await context.db.update(domains, { id: node }).set({ ttl }); - - // TODO: log DomainEvent -} - -async function _handleNewResolver({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, resolver: resolverAddress } = event.args; - - // if zeroing out a domain's resolver, remove the reference instead of tracking a zeroAddress Resolver - // NOTE: old resolver resources are kept for event logs - if (event.args.resolver === zeroAddress) { - await context.db.update(domains, { id: node }).set({ resolverId: null }); - - // garbage collect newly 'empty' domain iff necessary - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, node); - } else { - // otherwise upsert the resolver - const resolverId = makeResolverId(node, resolverAddress); - - const resolver = await context.db - .insert(resolvers) - .values({ - id: resolverId, - domainId: event.args.node, - address: event.args.resolver, - }) - .onConflictDoNothing(); - - // update the domain to point to it, and denormalize the eth addr - await context.db - .update(domains, { id: node }) - .set({ resolverId, resolvedAddress: resolver?.addrId }); - } - - // TODO: log DomainEvent -} - function initRegistryHandlers() { - // setup on old registry - ponder.on(ns("RegistryOld:setup"), async ({ context }) => { - // ensure we have an account for the zeroAddress - await upsertAccount(context, zeroAddress); - - // ensure we have a root Domain, owned by the zeroAddress - await context.db.insert(domains).values({ - id: NAMEHASH_ZERO, - ownerId: zeroAddress, - createdAt: 0n, - isMigrated: false, - }); - }); + ponder.on(ns("RegistryOld:setup"), setup); // old registry functions are proxied to the current handlers // iff the domain has not yet been migrated @@ -213,7 +27,7 @@ function initRegistryHandlers() { const node = makeSubnodeNamehash(event.args.node, event.args.label); const isMigrated = await isDomainMigrated(context, node); if (isMigrated) return; - return _handleNewOwner(false)({ context, event }); + return handleNewOwner(false)({ context, event }); }); ponder.on(ns("RegistryOld:NewResolver"), async ({ context, event }) => { @@ -226,25 +40,25 @@ function initRegistryHandlers() { // otherwise, only handle iff not migrated const isMigrated = await isDomainMigrated(context, event.args.node); if (isMigrated) return; - return _handleNewResolver({ context, event }); + return handleNewResolver({ context, event }); }); ponder.on(ns("RegistryOld:NewTTL"), async ({ context, event }) => { const isMigrated = await isDomainMigrated(context, event.args.node); if (isMigrated) return; - return _handleNewTTL({ context, event }); + return handleNewTTL({ context, event }); }); ponder.on(ns("RegistryOld:Transfer"), async ({ context, event }) => { const isMigrated = await isDomainMigrated(context, event.args.node); if (isMigrated) return; - return _handleTransfer({ context, event }); + return handleTransfer({ context, event }); }); - ponder.on(ns("Registry:NewOwner"), _handleNewOwner(true)); - ponder.on(ns("Registry:NewResolver"), _handleNewResolver); - ponder.on(ns("Registry:NewTTL"), _handleNewTTL); - ponder.on(ns("Registry:Transfer"), _handleTransfer); + ponder.on(ns("Registry:NewOwner"), handleNewOwner(true)); + ponder.on(ns("Registry:NewResolver"), handleNewResolver); + ponder.on(ns("Registry:NewTTL"), handleNewTTL); + ponder.on(ns("Registry:Transfer"), handleTransfer); } export const handlerModule: Readonly = { diff --git a/src/ponder-ens-plugins/eth/handlers/Resolver.ts b/src/ponder-ens-plugins/eth/handlers/Resolver.ts index 3f76c2cb..ff0d6f5f 100644 --- a/src/ponder-ens-plugins/eth/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/eth/handlers/Resolver.ts @@ -1,321 +1,72 @@ -import { type Context, type Event, ponder } from "ponder:registry"; -import { domains, resolvers } from "ponder:schema"; -import { hasNullByte, uniq } from "../../../lib/helpers"; -import { makeResolverId } from "../../../lib/ids"; -import { upsertAccount, upsertResolver } from "../../../lib/upserts"; +import { ponder } from "ponder:registry"; +import { + handleABIChanged, + handleAddrChanged, + handleAddressChanged, + handleAuthorisationChanged, + handleContenthashChanged, + handleDNSRecordChanged, + handleDNSRecordDeleted, + handleDNSZonehashChanged, + handleInterfaceChanged, + handleNameChanged, + handlePubkeyChanged, + handleTextChanged, + handleVersionChanged, +} from "../../../handlers/Resolver"; import { PonderEnsIndexingHandlerModule } from "../../types"; -import { type NsType, ns } from "../ponder.config"; - -// there is a legacy resolver abi with different TextChanged events. -// luckily the subgraph doesn't care about the value parameter so we can use a union -// to unify the codepath -type AnyTextChangedEvent = - | Event< - NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> - > - | Event< - NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> - > - | Event< - NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> - > - | Event< - NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> - >; - -async function _handleAddrChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { a: address, node } = event.args; - await upsertAccount(context, address); - - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - addrId: address, - }); - - // materialize the resolved add to the domain iff this resolver is active - const domain = await context.db.find(domains, { id: node }); - if (domain?.resolverId === id) { - await context.db.update(domains, { id: node }).set({ resolvedAddress: address }); - } - - // TODO: log ResolverEvent -} - -async function _handleAddressChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, coinType, newAddress } = event.args; - await upsertAccount(context, newAddress); - - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // upsert the new coinType - await context.db - .update(resolvers, { id }) - .set({ coinTypes: uniq([...resolver.coinTypes, coinType]) }); - - // TODO: log ResolverEvent -} - -async function _handleNameChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, name } = event.args; - if (hasNullByte(name)) return; - - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handleABIChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handlePubkeyChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handleTextChanged({ - context, - event, -}: { - context: Context; - event: AnyTextChangedEvent; -}) { - const { node, key } = event.args; - const id = makeResolverId(node, event.log.address); - const resolver = await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // upsert new key - await context.db.update(resolvers, { id }).set({ texts: uniq([...resolver.texts, key]) }); - - // TODO: log ResolverEvent -} - -async function _handleContenthashChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node, hash } = event.args; - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - contentHash: hash, - }); - - // TODO: log ResolverEvent -} - -async function _handleInterfaceChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handleAuthorisationChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - await upsertResolver(context, { - id, - domainId: node, - address: event.log.address, - }); - - // TODO: log ResolverEvent -} - -async function _handleVersionChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // a version change nulls out the resolver - const { node } = event.args; - const id = makeResolverId(node, event.log.address); - const domain = await context.db.find(domains, { id: node }); - if (!domain) throw new Error("domain expected"); - - // materialize the Domain's resolvedAddress field - if (domain.resolverId === id) { - await context.db.update(domains, { id: node }).set({ resolvedAddress: null }); - } - - // clear out the resolver's info - await context.db.update(resolvers, { id }).set({ - addrId: null, - contentHash: null, - coinTypes: [], - texts: [], - }); - - // TODO: log ResolverEvent -} - -async function _handleDNSRecordChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // subgraph ignores -} - -async function _handleDNSRecordDeleted({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // subgraph ignores -} - -async function _handleDNSZonehashChanged({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - // subgraph ignores -} +import { ns } from "../ponder.config"; export function initResolverHandlers() { // Old registry handlers - ponder.on(ns("OldRegistryResolvers:AddrChanged"), _handleAddrChanged); - ponder.on(ns("OldRegistryResolvers:AddressChanged"), _handleAddressChanged); - ponder.on(ns("OldRegistryResolvers:NameChanged"), _handleNameChanged); - ponder.on(ns("OldRegistryResolvers:ABIChanged"), _handleABIChanged); - ponder.on(ns("OldRegistryResolvers:PubkeyChanged"), _handlePubkeyChanged); + ponder.on(ns("OldRegistryResolvers:AddrChanged"), handleAddrChanged); + ponder.on(ns("OldRegistryResolvers:AddressChanged"), handleAddressChanged); + ponder.on(ns("OldRegistryResolvers:NameChanged"), handleNameChanged); + ponder.on(ns("OldRegistryResolvers:ABIChanged"), handleABIChanged); + ponder.on(ns("OldRegistryResolvers:PubkeyChanged"), handlePubkeyChanged); ponder.on( ns( "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", ), - _handleTextChanged, + handleTextChanged, ); ponder.on( ns( "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", ), - _handleTextChanged, + handleTextChanged, ); - ponder.on(ns("OldRegistryResolvers:ContenthashChanged"), _handleContenthashChanged); - ponder.on(ns("OldRegistryResolvers:InterfaceChanged"), _handleInterfaceChanged); - ponder.on(ns("OldRegistryResolvers:AuthorisationChanged"), _handleAuthorisationChanged); - ponder.on(ns("OldRegistryResolvers:VersionChanged"), _handleVersionChanged); - ponder.on(ns("OldRegistryResolvers:DNSRecordChanged"), _handleDNSRecordChanged); - ponder.on(ns("OldRegistryResolvers:DNSRecordDeleted"), _handleDNSRecordDeleted); - ponder.on(ns("OldRegistryResolvers:DNSZonehashChanged"), _handleDNSZonehashChanged); + ponder.on(ns("OldRegistryResolvers:ContenthashChanged"), handleContenthashChanged); + ponder.on(ns("OldRegistryResolvers:InterfaceChanged"), handleInterfaceChanged); + ponder.on(ns("OldRegistryResolvers:AuthorisationChanged"), handleAuthorisationChanged); + ponder.on(ns("OldRegistryResolvers:VersionChanged"), handleVersionChanged); + ponder.on(ns("OldRegistryResolvers:DNSRecordChanged"), handleDNSRecordChanged); + ponder.on(ns("OldRegistryResolvers:DNSRecordDeleted"), handleDNSRecordDeleted); + ponder.on(ns("OldRegistryResolvers:DNSZonehashChanged"), handleDNSZonehashChanged); // New registry handlers - ponder.on(ns("Resolver:AddrChanged"), _handleAddrChanged); - ponder.on(ns("Resolver:AddressChanged"), _handleAddressChanged); - ponder.on(ns("Resolver:NameChanged"), _handleNameChanged); - ponder.on(ns("Resolver:ABIChanged"), _handleABIChanged); - ponder.on(ns("Resolver:PubkeyChanged"), _handlePubkeyChanged); + ponder.on(ns("Resolver:AddrChanged"), handleAddrChanged); + ponder.on(ns("Resolver:AddressChanged"), handleAddressChanged); + ponder.on(ns("Resolver:NameChanged"), handleNameChanged); + ponder.on(ns("Resolver:ABIChanged"), handleABIChanged); + ponder.on(ns("Resolver:PubkeyChanged"), handlePubkeyChanged); ponder.on( ns("Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"), - _handleTextChanged, + handleTextChanged, ); ponder.on( ns( "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", ), - _handleTextChanged, + handleTextChanged, ); - ponder.on(ns("Resolver:ContenthashChanged"), _handleContenthashChanged); - ponder.on(ns("Resolver:InterfaceChanged"), _handleInterfaceChanged); - ponder.on(ns("Resolver:AuthorisationChanged"), _handleAuthorisationChanged); - ponder.on(ns("Resolver:VersionChanged"), _handleVersionChanged); - ponder.on(ns("Resolver:DNSRecordChanged"), _handleDNSRecordChanged); - ponder.on(ns("Resolver:DNSRecordDeleted"), _handleDNSRecordDeleted); - ponder.on(ns("Resolver:DNSZonehashChanged"), _handleDNSZonehashChanged); + ponder.on(ns("Resolver:ContenthashChanged"), handleContenthashChanged); + ponder.on(ns("Resolver:InterfaceChanged"), handleInterfaceChanged); + ponder.on(ns("Resolver:AuthorisationChanged"), handleAuthorisationChanged); + ponder.on(ns("Resolver:VersionChanged"), handleVersionChanged); + ponder.on(ns("Resolver:DNSRecordChanged"), handleDNSRecordChanged); + ponder.on(ns("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); + ponder.on(ns("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); } export const handlerModule: Readonly = { From 3d181f9dcca50369c95710dcedfca96f90ac0b7e Mon Sep 17 00:00:00 2001 From: shrugs Date: Fri, 3 Jan 2025 13:31:42 -0600 Subject: [PATCH 07/30] feat: refactor plugin logic, DRY handler code --- ponder.config.ts | 47 ++++++----- src/handlers/Registry.ts | 2 +- src/handlers/Resolver.ts | 2 +- .../chain.ts => lib/plugins.ts} | 11 --- .../eth.base/handlers/Registrar.ts | 83 +++++++------------ .../eth.base/handlers/Registry.ts | 7 +- .../eth.base/handlers/Resolver.ts | 7 +- .../eth.base/ponder.config.ts | 61 +++++--------- .../eth.base/ponder.indexing.ts | 20 ----- .../eth/handlers/EthRegistrar.ts | 11 +-- .../eth/handlers/NameWrapper.ts | 6 +- .../eth/handlers/Registry.ts | 7 +- .../eth/handlers/Resolver.ts | 7 +- src/ponder-ens-plugins/eth/ponder.config.ts | 35 ++++---- src/ponder-ens-plugins/eth/ponder.indexing.ts | 20 ----- src/ponder-ens-plugins/main.ts | 18 ---- src/ponder-ens-plugins/types.ts | 18 ---- 17 files changed, 102 insertions(+), 260 deletions(-) rename src/{ponder-ens-plugins/chain.ts => lib/plugins.ts} (63%) delete mode 100644 src/ponder-ens-plugins/eth.base/ponder.indexing.ts delete mode 100644 src/ponder-ens-plugins/eth/ponder.indexing.ts delete mode 100644 src/ponder-ens-plugins/main.ts delete mode 100644 src/ponder-ens-plugins/types.ts diff --git a/ponder.config.ts b/ponder.config.ts index 817673ff..4835472a 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,25 +1,26 @@ -import { createConfig } from "ponder"; -import { config as baseConfig } from "./src/ponder-ens-plugins/eth.base/ponder.config"; -import { config as ethereumConfig } from "./src/ponder-ens-plugins/eth/ponder.config"; +import { + activate as activateBase, + config as baseConfig, +} from "./src/ponder-ens-plugins/eth.base/ponder.config"; +import { + activate as activateEth, + config as ethereumConfig, +} from "./src/ponder-ens-plugins/eth/ponder.config"; -console.log({ - networks: { - ...baseConfig.networks, - ...ethereumConfig.networks, - }, - contracts: { - ...baseConfig.contracts, - ...ethereumConfig.contracts, - }, -}); +type AllConfigs = typeof ethereumConfig & typeof baseConfig; -export default createConfig({ - networks: { - ...baseConfig.networks, - ...ethereumConfig.networks, - }, - contracts: { - ...baseConfig.contracts, - ...ethereumConfig.contracts, - }, -}); +// here we export only a single 'plugin's config, by type it as every config +// this makes all of the mapping types happy at typecheck-time, but only the relevant +// config is run at runtime +export default ((): AllConfigs => { + switch (process.env.INDEX_ENS_ROOT_NODE) { + case ".eth": + activateEth(); + return ethereumConfig as AllConfigs; + case ".base.eth": + activateBase(); + return baseConfig as AllConfigs; + default: + throw new Error(`Unsupported ENS_ROOT_NODE: ${process.env.INDEX_ENS_ROOT_NODE}`); + } +})(); diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index fe5471ef..fb12605b 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -5,8 +5,8 @@ import { type Hex, zeroAddress } from "viem"; import { mainnet } from "viem/chains"; import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; import { makeResolverId } from "../lib/ids"; +import { NsReturnType } from "../lib/plugins"; import { upsertAccount } from "../lib/upserts"; -import { NsReturnType } from "../ponder-ens-plugins/chain"; type NsType = NsReturnType; diff --git a/src/handlers/Resolver.ts b/src/handlers/Resolver.ts index b8355f8c..9d92d7b0 100644 --- a/src/handlers/Resolver.ts +++ b/src/handlers/Resolver.ts @@ -3,8 +3,8 @@ import { domains, resolvers } from "ponder:schema"; import { base, mainnet } from "viem/chains"; import { hasNullByte, uniq } from "../lib/helpers"; import { makeResolverId } from "../lib/ids"; +import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertResolver } from "../lib/upserts"; -import { NsReturnType } from "../ponder-ens-plugins/chain"; type NsType = NsReturnType; diff --git a/src/ponder-ens-plugins/chain.ts b/src/lib/plugins.ts similarity index 63% rename from src/ponder-ens-plugins/chain.ts rename to src/lib/plugins.ts index eb16db3a..03d9d296 100644 --- a/src/ponder-ens-plugins/chain.ts +++ b/src/lib/plugins.ts @@ -1,14 +1,3 @@ - -// TODO: change the chainId to the root path (i.e. ("base.eth")) -export function isChainIndexingActive(rootPath: `${string}.eth`) { - if (!process.env.INDEX_ENS_ROOT_NODE) { - console.warn("INDEX_ENS_ROOT_NODE is not set"); - return false; - } - - return process.env.INDEX_ENS_ROOT_NODE === rootPath; -} - // TODO: change the chainId to the root node value (i.e. namehash("base.eth")) export function createNs(chainId: ChainId) { /** Creates a name-spaced contract name */ diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index 6e866d1e..ffffcd07 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -8,8 +8,9 @@ import { tokenIdToLabel, } from "../../../lib/ens-helpers"; import { upsertAccount, upsertRegistration } from "../../../lib/upserts"; -import { PonderEnsIndexingHandlerModule } from "../../types"; -import { type NsType, ns } from "../ponder.config"; +import { ns } from "../ponder.config"; + +type NsType = ReturnType>; // all nodes referenced by EthRegistrar are parented to .eth const ROOT_NODE = NAMEHASH_BASE_ETH; @@ -41,11 +42,19 @@ async function handleNameRegistered({ labelName, }); - console.log('handleNameRegistered', { event: { - block: event.block.number, - tx: event.transaction.hash, - logIndex: event.log.logIndex, - }, id, owner, expires, label, node, labelName }); + console.log("handleNameRegistered", { + event: { + block: event.block.number, + tx: event.transaction.hash, + logIndex: event.log.logIndex, + }, + id, + owner, + expires, + label, + node, + labelName, + }); await context.db.update(domains, { id: node }).set({ registrantId: owner, @@ -63,12 +72,7 @@ async function handleNameRegisteredByController({ context: Context; event: Event>; }) { - return await setNamePreimage( - context, - event.args.name, - event.args.label, - null - ); + return await setNamePreimage(context, event.args.name, event.args.label, null); } async function handleNameRenewedByController({ @@ -78,20 +82,10 @@ async function handleNameRenewedByController({ context: Context; event: Event>; }) { - return await setNamePreimage( - context, - event.args.name, - event.args.label, - null - ); + return await setNamePreimage(context, event.args.name, event.args.label, null); } -async function setNamePreimage( - context: Context, - name: string, - label: Hex, - cost: bigint | null -) { +async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint | null) { if (!isLabelValid(name)) return; const node = makeSubnodeNamehash(ROOT_NODE, label); @@ -99,14 +93,10 @@ async function setNamePreimage( if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { - await context.db - .update(domains, { id: node }) - .set({ labelName: name, name: `${name}.eth` }); + await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}.eth` }); } - await context.db - .update(registrations, { id: label }) - .set({ labelName: name, cost }); + await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); } async function handleNameRenewed({ @@ -121,9 +111,7 @@ async function handleNameRenewed({ const label = tokenIdToLabel(id); const node = makeSubnodeNamehash(ROOT_NODE, label); - await context.db - .update(registrations, { id: label }) - .set({ expiryDate: expires }); + await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); await context.db .update(domains, { id: node }) @@ -149,35 +137,20 @@ async function handleNameTransferred({ const registration = await context.db.find(registrations, { id: label }); if (!registration) return; - await context.db - .update(registrations, { id: label }) - .set({ registrantId: to }); + await context.db.update(registrations, { id: label }).set({ registrantId: to }); await context.db.update(domains, { id: node }).set({ registrantId: to }); // TODO: log Event } -function initBaseRegistrarHandlers() { +export default function () { ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); - ponder.on( - ns("RegistrarController:NameRegistered"), - handleNameRegisteredByController - ); - ponder.on( - ns("RegistrarController:NameRenewed"), - handleNameRenewedByController - ); - - ponder.on( - ns("EARegistrarController:NameRegistered"), - handleNameRegisteredByController - ); -} + ponder.on(ns("RegistrarController:NameRegistered"), handleNameRegisteredByController); + ponder.on(ns("RegistrarController:NameRenewed"), handleNameRenewedByController); -export const handlerModule: Readonly = { - attachHandlers: initBaseRegistrarHandlers, -}; + ponder.on(ns("EARegistrarController:NameRegistered"), handleNameRegisteredByController); +} diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts index 36ad32fe..00008e02 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts @@ -6,17 +6,12 @@ import { handleTransfer, setup, } from "../../../handlers/Registry"; -import { PonderEnsIndexingHandlerModule } from "../../types"; import { ns } from "../ponder.config"; -function initRegistryHandlers() { +export default function () { ponder.on(ns("Registry:setup"), setup); ponder.on(ns("Registry:NewOwner"), handleNewOwner(true)); ponder.on(ns("Registry:NewResolver"), handleNewResolver); ponder.on(ns("Registry:NewTTL"), handleNewTTL); ponder.on(ns("Registry:Transfer"), handleTransfer); } - -export const handlerModule: Readonly = { - attachHandlers: initRegistryHandlers, -}; diff --git a/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts index c8748bf6..6395268c 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts @@ -13,10 +13,9 @@ import { handleTextChanged, handleVersionChanged, } from "../../../handlers/Resolver"; -import { PonderEnsIndexingHandlerModule } from "../../types"; import { ns } from "../ponder.config"; -export function initResolverHandlers() { +export default function () { // New registry handlers ponder.on(ns("Resolver:AddrChanged"), handleAddrChanged); ponder.on(ns("Resolver:AddressChanged"), handleAddressChanged); @@ -31,7 +30,3 @@ export function initResolverHandlers() { ponder.on(ns("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); ponder.on(ns("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); } - -export const handlerModule: Readonly = { - attachHandlers: initResolverHandlers, -}; diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index 1261cff5..efe5c626 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -1,62 +1,43 @@ -import { factory } from "ponder"; +import { createConfig, factory } from "ponder"; import { http, getAbiItem } from "viem"; -import { base, mainnet } from "viem/chains"; - -import { type NsReturnType, createNs } from "../chain"; - -// Replacing ABIs with the Base-specific ones, as per https://github.com/base-org/basenames?tab=readme-ov-file#architecture -// import { BaseRegistrar } from "./abis/BaseRegistrar"; -// import { EthRegistrarController } from "./abis/EthRegistrarController"; -// import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; -// import { LegacyPublicResolver } from "./abis/LegacyPublicResolver"; -// import { NameWrapper } from "./abis/NameWrapper"; -// import { Registry } from "./abis/Registry"; -// import { Resolver } from "./abis/Resolver"; +import { base } from "viem/chains"; +import { createNs } from "../../lib/plugins"; import { BaseRegistrar } from "./abis/BaseRegistrar"; +import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; - -// just for testing... -const END_BLOCK = undefined; export const ns = createNs(base.id); -export type NsType = NsReturnType; - -const REGISTRY_ADDRESS = '0xb94704422c2a1e396835a571837aa5ae53285a95'; +const REGISTRY_ADDRESS = "0xb94704422c2a1e396835a571837aa5ae53285a95"; const REGISTRY_START_BLOCK = 17571480; -const BASE_REGISTRAR_ADDRESS = '0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a'; +const BASE_REGISTRAR_ADDRESS = "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a"; const BASE_REGISTRAR_START_BLOCK = 17571486; -const REGISTRAR_CONTROLLER_ADDRESS = '0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5'; +const REGISTRAR_CONTROLLER_ADDRESS = "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; const REGISTRAR_CONTROLLER_START_BLOCK = 18619035; -const EA_REGISTRAR_CONTROLLER_ADDRESS = '0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda'; +const EA_REGISTRAR_CONTROLLER_ADDRESS = "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda"; const EA_REGISTRAR_CONTROLLER_START_BLOCK = 17575699; -const REVERSE_REGISTRAR_ADDRESS = '0x79ea96012eea67a83431f1701b3dff7e37f9e282'; +const REVERSE_REGISTRAR_ADDRESS = "0x79ea96012eea67a83431f1701b3dff7e37f9e282"; const REVERSE_REGISTRAR_START_BLOCK = 17571485; -const L1_RESOLVER_ADDRESS = '0xde9049636F4a1dfE0a64d1bFe3155C0A14C54F31'; +const L1_RESOLVER_ADDRESS = "0xde9049636F4a1dfE0a64d1bFe3155C0A14C54F31"; const L1_RESOLVER_START_BLOCK = 20420641; -const L2_RESOLVER_ADDRESS = '0xC6d566A56A1aFf6508b41f6c90ff131615583BCD'; +const L2_RESOLVER_ADDRESS = "0xC6d566A56A1aFf6508b41f6c90ff131615583BCD"; const L2_RESOLVER_START_BLOCK = 17575714; -export const config = Object.freeze({ +export const config = createConfig({ networks: { base: { chainId: base.id, transport: http(process.env[`PONDER_RPC_URL_${base.id}`]), }, - mainnet: { - chainId: mainnet.id, - transport: http(process.env[`PONDER_RPC_URL_${mainnet.id}`]), - }, }, contracts: { [ns("Registry")]: { @@ -64,7 +45,6 @@ export const config = Object.freeze({ abi: Registry, address: REGISTRY_ADDRESS, startBlock: REGISTRY_START_BLOCK, - endBlock: END_BLOCK, }, [ns("Resolver")]: { network: "base", @@ -75,7 +55,6 @@ export const config = Object.freeze({ parameter: "resolver", }), startBlock: L2_RESOLVER_START_BLOCK, - endBlock: END_BLOCK, }, ["eth.base.Resolver"]: { network: "base", @@ -86,28 +65,34 @@ export const config = Object.freeze({ parameter: "resolver", }), startBlock: L2_RESOLVER_START_BLOCK, - endBlock: END_BLOCK, }, [ns("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, address: BASE_REGISTRAR_ADDRESS, startBlock: BASE_REGISTRAR_START_BLOCK, - endBlock: END_BLOCK, }, [ns("EARegistrarController")]: { network: "base", abi: EarlyAccessRegistrarController, address: EA_REGISTRAR_CONTROLLER_ADDRESS, startBlock: EA_REGISTRAR_CONTROLLER_START_BLOCK, - endBlock: END_BLOCK, }, [ns("RegistrarController")]: { network: "base", abi: RegistrarController, address: REGISTRAR_CONTROLLER_ADDRESS, startBlock: REGISTRAR_CONTROLLER_START_BLOCK, - endBlock: END_BLOCK, }, }, -} as const); +}); + +export async function activate() { + const ponderIndexingModules = await Promise.all([ + import("./handlers/Registry"), + import("./handlers/Registrar"), + import("./handlers/Resolver"), + ]); + + ponderIndexingModules.map((m) => m.default()); +} diff --git a/src/ponder-ens-plugins/eth.base/ponder.indexing.ts b/src/ponder-ens-plugins/eth.base/ponder.indexing.ts deleted file mode 100644 index cda0f6e9..00000000 --- a/src/ponder-ens-plugins/eth.base/ponder.indexing.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isChainIndexingActive } from "../chain"; -import { PonderEnsModule } from "../types"; - -export default { - get canActivate() { - return isChainIndexingActive('base.eth'); - }, - - async activate() { - const ponderIndexingModules = await Promise.all([ - import("./handlers/Registry"), - import("./handlers/Registrar"), - import("./handlers/Resolver"), - ]); - - for (const { handlerModule } of ponderIndexingModules) { - handlerModule.attachHandlers(); - } - }, -} as PonderEnsModule; diff --git a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts index 0b94c18a..968b9df4 100644 --- a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts @@ -8,8 +8,9 @@ import { tokenIdToLabel, } from "../../../lib/ens-helpers"; import { upsertAccount, upsertRegistration } from "../../../lib/upserts"; -import { PonderEnsIndexingHandlerModule } from "../../types"; -import { type NsType, ns } from "../ponder.config"; +import { ns } from "../ponder.config"; + +type NsType = ReturnType>; // all nodes referenced by EthRegistrar are parented to .eth const ROOT_NODE = NAMEHASH_ETH; @@ -146,7 +147,7 @@ async function handleNameTransferred({ // TODO: log Event } -function initEthRegistrarHandlers() { +export default function () { ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); @@ -157,7 +158,3 @@ function initEthRegistrarHandlers() { ponder.on(ns("EthRegistrarController:NameRegistered"), handleNameRegisteredByController); ponder.on(ns("EthRegistrarController:NameRenewed"), handleNameRenewedByController); } - -export const handlerModule: Readonly = { - attachHandlers: initEthRegistrarHandlers, -}; diff --git a/src/ponder-ens-plugins/eth/handlers/NameWrapper.ts b/src/ponder-ens-plugins/eth/handlers/NameWrapper.ts index 38905854..8337712e 100644 --- a/src/ponder-ens-plugins/eth/handlers/NameWrapper.ts +++ b/src/ponder-ens-plugins/eth/handlers/NameWrapper.ts @@ -1,5 +1 @@ -import { PonderEnsIndexingHandlerModule } from "../../types"; - -export const handlerModule: Readonly = { - attachHandlers: () => {}, -}; +// diff --git a/src/ponder-ens-plugins/eth/handlers/Registry.ts b/src/ponder-ens-plugins/eth/handlers/Registry.ts index 9bbaab51..68559411 100644 --- a/src/ponder-ens-plugins/eth/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth/handlers/Registry.ts @@ -9,7 +9,6 @@ import { setup, } from "../../../handlers/Registry"; import { makeSubnodeNamehash } from "../../../lib/ens-helpers"; -import { PonderEnsIndexingHandlerModule } from "../../types"; import { ns } from "../ponder.config"; // a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not @@ -18,7 +17,7 @@ async function isDomainMigrated(context: Context, node: Hex) { return domain?.isMigrated ?? false; } -function initRegistryHandlers() { +export default function () { ponder.on(ns("RegistryOld:setup"), setup); // old registry functions are proxied to the current handlers @@ -60,7 +59,3 @@ function initRegistryHandlers() { ponder.on(ns("Registry:NewTTL"), handleNewTTL); ponder.on(ns("Registry:Transfer"), handleTransfer); } - -export const handlerModule: Readonly = { - attachHandlers: initRegistryHandlers, -}; diff --git a/src/ponder-ens-plugins/eth/handlers/Resolver.ts b/src/ponder-ens-plugins/eth/handlers/Resolver.ts index ff0d6f5f..369cab64 100644 --- a/src/ponder-ens-plugins/eth/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/eth/handlers/Resolver.ts @@ -14,10 +14,9 @@ import { handleTextChanged, handleVersionChanged, } from "../../../handlers/Resolver"; -import { PonderEnsIndexingHandlerModule } from "../../types"; import { ns } from "../ponder.config"; -export function initResolverHandlers() { +export default function () { // Old registry handlers ponder.on(ns("OldRegistryResolvers:AddrChanged"), handleAddrChanged); ponder.on(ns("OldRegistryResolvers:AddressChanged"), handleAddressChanged); @@ -68,7 +67,3 @@ export function initResolverHandlers() { ponder.on(ns("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); ponder.on(ns("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); } - -export const handlerModule: Readonly = { - attachHandlers: initResolverHandlers, -}; diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index cfeb1132..e740c507 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -1,8 +1,8 @@ -import { factory, mergeAbis } from "ponder"; +import { createConfig, factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; import { mainnet } from "viem/chains"; -import { NsReturnType, createNs } from "../chain"; +import { createNs } from "../../lib/plugins"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; @@ -11,9 +11,6 @@ import { NameWrapper } from "./abis/NameWrapper"; import { Registry } from "./abis/Registry"; import { Resolver } from "./abis/Resolver"; -// just for testing... -const END_BLOCK = 12_000_000; - const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; @@ -26,9 +23,7 @@ const NAME_WRAPPER_ADDRESS = "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401"; export const ns = createNs(mainnet.id); -export type NsType = NsReturnType; - -export const config = Object.freeze({ +export const config = createConfig({ networks: { mainnet: { chainId: 1, @@ -41,14 +36,12 @@ export const config = Object.freeze({ abi: Registry, address: REGISTRY_OLD_ADDRESS, startBlock: 3327417, - endBlock: END_BLOCK, }, [ns("Registry")]: { network: "mainnet", abi: Registry, address: REGISTRY_ADDRESS, startBlock: 9380380, - endBlock: END_BLOCK, }, [ns("OldRegistryResolvers")]: { network: "mainnet", @@ -59,7 +52,6 @@ export const config = Object.freeze({ parameter: "resolver", }), startBlock: 9380380, - endBlock: END_BLOCK, }, [ns("Resolver")]: { network: "mainnet", @@ -70,35 +62,40 @@ export const config = Object.freeze({ parameter: "resolver", }), startBlock: 9380380, - endBlock: END_BLOCK, }, [ns("BaseRegistrar")]: { network: "mainnet", abi: BaseRegistrar, address: BASE_REGISTRAR_ADDRESS, startBlock: 9380410, - endBlock: END_BLOCK, }, [ns("EthRegistrarControllerOld")]: { network: "mainnet", abi: EthRegistrarControllerOld, address: ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS, startBlock: 9380471, - endBlock: END_BLOCK, }, [ns("EthRegistrarController")]: { network: "mainnet", abi: EthRegistrarController, address: ETH_REGISTRAR_CONTROLLER_ADDRESS, - startBlock: Math.min(16925618, END_BLOCK), - endBlock: END_BLOCK, + startBlock: 16925618, }, [ns("NameWrapper")]: { network: "mainnet", abi: NameWrapper, address: NAME_WRAPPER_ADDRESS, - startBlock: Math.min(16925608, END_BLOCK), - endBlock: END_BLOCK, + startBlock: 16925608, }, }, -} as const); +}); + +export async function activate() { + const ponderIndexingModules = await Promise.all([ + import("./handlers/Registry"), + import("./handlers/EthRegistrar"), + import("./handlers/Resolver"), + ]); + + ponderIndexingModules.map((m) => m.default()); +} diff --git a/src/ponder-ens-plugins/eth/ponder.indexing.ts b/src/ponder-ens-plugins/eth/ponder.indexing.ts deleted file mode 100644 index b6091ccb..00000000 --- a/src/ponder-ens-plugins/eth/ponder.indexing.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isChainIndexingActive } from "../chain"; -import { PonderEnsModule } from "../types"; - -export default { - get canActivate() { - return isChainIndexingActive('.eth'); - }, - - async activate() { - const ponderIndexingModules = await Promise.all([ - import("./handlers/Registry"), - import("./handlers/EthRegistrar"), - import("./handlers/Resolver"), - ]); - - for (const { handlerModule } of ponderIndexingModules) { - handlerModule.attachHandlers(); - } - }, -} as PonderEnsModule; diff --git a/src/ponder-ens-plugins/main.ts b/src/ponder-ens-plugins/main.ts deleted file mode 100644 index ad9bb3bf..00000000 --- a/src/ponder-ens-plugins/main.ts +++ /dev/null @@ -1,18 +0,0 @@ -import basePlugin from "./eth.base/ponder.indexing"; -import ethereumPlugin from "./eth/ponder.indexing"; - -/** - * Main entry point for the Ponder ENS plugins. - * It tries to activate all the enlisted plugins. - */ -function main() { - const plugins = [basePlugin, ethereumPlugin]; - - for (const plugin of plugins) { - if (plugin.canActivate) { - plugin.activate(); - } - } -} - -main(); diff --git a/src/ponder-ens-plugins/types.ts b/src/ponder-ens-plugins/types.ts deleted file mode 100644 index 0d262a1a..00000000 --- a/src/ponder-ens-plugins/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface PonderEnsModule { - /** - * Check if the module can be activated - */ - get canActivate(): boolean; - - /** - * Runs the module activation logic - */ - activate(): Promise; -} - -export interface PonderEnsIndexingHandlerModule { - /** - * Attaches indexing handlers for the module - */ - attachHandlers(): void; -} From 2d079a74d10f1382719acff7a94ef2f963e4d1f2 Mon Sep 17 00:00:00 2001 From: shrugs Date: Fri, 3 Jan 2025 13:52:00 -0600 Subject: [PATCH 08/30] chore: inline some addreses --- src/handlers/Registry.ts | 2 +- .../eth.base/handlers/Registrar.ts | 3 +-- src/ponder-ens-plugins/eth.base/ponder.config.ts | 10 ---------- src/ponder-ens-plugins/eth/ponder.config.ts | 13 ++++--------- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index fb12605b..55e3ae36 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -14,7 +14,7 @@ export async function setup({ context }: { context: Context }) { // ensure we have an account for the zeroAddress await upsertAccount(context, zeroAddress); - // ensure we have a root Domain, owned by the zeroAddress + // ensure we have a root Domain, owned by the zeroAddress, that is default not migrated await context.db.insert(domains).values({ id: NAMEHASH_ZERO, ownerId: zeroAddress, diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index ffffcd07..d15fe388 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -149,8 +149,7 @@ export default function () { ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); + ponder.on(ns("EARegistrarController:NameRegistered"), handleNameRegisteredByController); ponder.on(ns("RegistrarController:NameRegistered"), handleNameRegisteredByController); ponder.on(ns("RegistrarController:NameRenewed"), handleNameRenewedByController); - - ponder.on(ns("EARegistrarController:NameRegistered"), handleNameRegisteredByController); } diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index efe5c626..fa679ba2 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -56,16 +56,6 @@ export const config = createConfig({ }), startBlock: L2_RESOLVER_START_BLOCK, }, - ["eth.base.Resolver"]: { - network: "base", - abi: L2Resolver, - address: factory({ - address: L2_RESOLVER_ADDRESS, - event: getAbiItem({ abi: Registry, name: "NewResolver" }), - parameter: "resolver", - }), - startBlock: L2_RESOLVER_START_BLOCK, - }, [ns("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index e740c507..0bc6cccc 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -16,11 +16,6 @@ const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; -const BASE_REGISTRAR_ADDRESS = "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85"; -const ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS = "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5"; -const ETH_REGISTRAR_CONTROLLER_ADDRESS = "0x253553366Da8546fC250F225fe3d25d0C782303b"; -const NAME_WRAPPER_ADDRESS = "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401"; - export const ns = createNs(mainnet.id); export const config = createConfig({ @@ -66,25 +61,25 @@ export const config = createConfig({ [ns("BaseRegistrar")]: { network: "mainnet", abi: BaseRegistrar, - address: BASE_REGISTRAR_ADDRESS, + address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 9380410, }, [ns("EthRegistrarControllerOld")]: { network: "mainnet", abi: EthRegistrarControllerOld, - address: ETH_REGISTRAR_CONTROLLER_OLD_ADDRESS, + address: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", startBlock: 9380471, }, [ns("EthRegistrarController")]: { network: "mainnet", abi: EthRegistrarController, - address: ETH_REGISTRAR_CONTROLLER_ADDRESS, + address: "0x253553366Da8546fC250F225fe3d25d0C782303b", startBlock: 16925618, }, [ns("NameWrapper")]: { network: "mainnet", abi: NameWrapper, - address: NAME_WRAPPER_ADDRESS, + address: "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401", startBlock: 16925608, }, }, From 32912d53c54427d203c8f148b86921c1f0522cb8 Mon Sep 17 00:00:00 2001 From: shrugs Date: Fri, 3 Jan 2025 14:20:02 -0600 Subject: [PATCH 09/30] refactor: DRY up registrar code --- src/handlers/Registrar.ts | 127 ++++++++++++ .../eth.base/handlers/Registrar.ts | 191 +++++------------- .../eth/handlers/EthRegistrar.ts | 185 ++++------------- 3 files changed, 208 insertions(+), 295 deletions(-) create mode 100644 src/handlers/Registrar.ts diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts new file mode 100644 index 00000000..fcc15056 --- /dev/null +++ b/src/handlers/Registrar.ts @@ -0,0 +1,127 @@ +import { type Context, type Event } from "ponder:registry"; +import { domains, registrations } from "ponder:schema"; +import type { Hex } from "viem"; +import { base, mainnet } from "viem/chains"; +import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; +import { NsReturnType } from "../lib/plugins"; +import { upsertAccount, upsertRegistration } from "../lib/upserts"; + +type NsType = NsReturnType; + +const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds + +export const makeRegistryHandlers = (root: Hex) => { + async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { + if (!isLabelValid(name)) return; + + const node = makeSubnodeNamehash(root, label); + const domain = await context.db.find(domains, { id: node }); + if (!domain) throw new Error("domain expected"); + + if (domain.labelName !== name) { + await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}.eth` }); + } + + await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); + } + + return { + async handleNameRegistered({ + context, + event, + }: { + context: Context; + event: Event>; + }) { + const { id, owner, expires } = event.args; + + await upsertAccount(context, owner); + + const label = tokenIdToLabel(id); + const node = makeSubnodeNamehash(root, label); + + // TODO: materialze labelName via rainbow tables ala Registry.ts + const labelName = undefined; + + await upsertRegistration(context, { + id: label, + domainId: node, + registrationDate: event.block.timestamp, + expiryDate: expires, + registrantId: owner, + labelName, + }); + + await context.db.update(domains, { id: node }).set({ + registrantId: owner, + expiryDate: expires + GRACE_PERIOD_SECONDS, + labelName, + }); + + // TODO: log Event + }, + + async handleNameRegisteredByController({ + context, + args: { name, label, cost }, + }: { + context: Context; + args: { name: string; label: Hex; cost: bigint }; + }) { + return await setNamePreimage(context, name, label, cost); + }, + + async handleNameRenewedByController({ + context, + args: { name, label, cost }, + }: { + context: Context; + args: { name: string; label: Hex; cost: bigint }; + }) { + return await setNamePreimage(context, name, label, cost); + }, + + async handleNameRenewed({ + context, + event, + }: { + context: Context; + event: Event>; + }) { + const { id, expires } = event.args; + + const label = tokenIdToLabel(id); + const node = makeSubnodeNamehash(root, label); + + await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); + + await context.db + .update(domains, { id: node }) + .set({ expiryDate: expires + GRACE_PERIOD_SECONDS }); + + // TODO: log Event + }, + + async handleNameTransferred({ + context, + args: { tokenId, from, to }, + }: { + context: Context; + args: Event>["args"]; + }) { + await upsertAccount(context, to); + + const label = tokenIdToLabel(tokenId); + const node = makeSubnodeNamehash(root, label); + + const registration = await context.db.find(registrations, { id: label }); + if (!registration) return; + + await context.db.update(registrations, { id: label }).set({ registrantId: to }); + + await context.db.update(domains, { id: node }).set({ registrantId: to }); + + // TODO: log Event + }, + }; +}; diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index d15fe388..ac47b134 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -1,155 +1,58 @@ -import { type Context, type Event, EventNames, ponder } from "ponder:registry"; -import { domains, registrations } from "ponder:schema"; -import type { Hex } from "viem"; -import { - NAMEHASH_BASE_ETH, - isLabelValid, - makeSubnodeNamehash, - tokenIdToLabel, -} from "../../../lib/ens-helpers"; -import { upsertAccount, upsertRegistration } from "../../../lib/upserts"; +import { ponder } from "ponder:registry"; +import { makeRegistryHandlers } from "../../../handlers/Registrar"; +import { NAMEHASH_BASE_ETH } from "../../../lib/ens-helpers"; import { ns } from "../ponder.config"; -type NsType = ReturnType>; +const { + handleNameRegistered, + handleNameRegisteredByController, + handleNameRenewedByController, + handleNameRenewed, + handleNameTransferred, +} = makeRegistryHandlers(NAMEHASH_BASE_ETH); -// all nodes referenced by EthRegistrar are parented to .eth -const ROOT_NODE = NAMEHASH_BASE_ETH; -const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds - -async function handleNameRegistered({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { id, owner, expires } = event.args; - - await upsertAccount(context, owner); - - const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(ROOT_NODE, label); - - // TODO: materialze labelName via rainbow tables ala Registry.ts - const labelName = undefined; +export default function () { + ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); + ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); - await upsertRegistration(context, { - id: label, - domainId: node, - registrationDate: event.block.timestamp, - expiryDate: expires, - registrantId: owner, - labelName, + // Base's BaseRegistrar uses `id` instead of `tokenId` + ponder.on(ns("BaseRegistrar:Transfer"), async ({ context, event }) => { + return await handleNameTransferred({ + context, + args: { + ...event.args, + tokenId: event.args.id, + }, + }); }); - console.log("handleNameRegistered", { - event: { - block: event.block.number, - tx: event.transaction.hash, - logIndex: event.log.logIndex, - }, - id, - owner, - expires, - label, - node, - labelName, + ponder.on(ns("EARegistrarController:NameRegistered"), async ({ context, event }) => { + return handleNameRegisteredByController({ + context, + args: { + ...event.args, + cost: 0n, + }, + }); }); - await context.db.update(domains, { id: node }).set({ - registrantId: owner, - expiryDate: expires + GRACE_PERIOD_SECONDS, - labelName, + ponder.on(ns("RegistrarController:NameRegistered"), async ({ context, event }) => { + return handleNameRegisteredByController({ + context, + args: { + ...event.args, + cost: 0n, + }, + }); }); - // TODO: log Event -} - -async function handleNameRegisteredByController({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - return await setNamePreimage(context, event.args.name, event.args.label, null); -} - -async function handleNameRenewedByController({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - return await setNamePreimage(context, event.args.name, event.args.label, null); -} - -async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint | null) { - if (!isLabelValid(name)) return; - - const node = makeSubnodeNamehash(ROOT_NODE, label); - const domain = await context.db.find(domains, { id: node }); - if (!domain) throw new Error("domain expected"); - - if (domain.labelName !== name) { - await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}.eth` }); - } - - await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); -} - -async function handleNameRenewed({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { id, expires } = event.args; - - const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(ROOT_NODE, label); - - await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); - - await context.db - .update(domains, { id: node }) - .set({ expiryDate: expires + GRACE_PERIOD_SECONDS }); - - // TODO: log Event -} - -async function handleNameTransferred({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { id: tokenId, from, to } = event.args; - - await upsertAccount(context, to); - - const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(ROOT_NODE, label); - - const registration = await context.db.find(registrations, { id: label }); - if (!registration) return; - - await context.db.update(registrations, { id: label }).set({ registrantId: to }); - - await context.db.update(domains, { id: node }).set({ registrantId: to }); - - // TODO: log Event -} - -export default function () { - ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); - ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); - ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); - - ponder.on(ns("EARegistrarController:NameRegistered"), handleNameRegisteredByController); - ponder.on(ns("RegistrarController:NameRegistered"), handleNameRegisteredByController); - ponder.on(ns("RegistrarController:NameRenewed"), handleNameRenewedByController); + ponder.on(ns("RegistrarController:NameRenewed"), async ({ context, event }) => { + return handleNameRenewedByController({ + context, + args: { + ...event.args, + cost: 0n, + }, + }); + }); } diff --git a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts index 968b9df4..da2f0c91 100644 --- a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts @@ -1,160 +1,43 @@ -import { type Context, type Event, ponder } from "ponder:registry"; -import { domains, registrations } from "ponder:schema"; -import type { Hex } from "viem"; -import { - NAMEHASH_ETH, - isLabelValid, - makeSubnodeNamehash, - tokenIdToLabel, -} from "../../../lib/ens-helpers"; -import { upsertAccount, upsertRegistration } from "../../../lib/upserts"; +import { ponder } from "ponder:registry"; +import { makeRegistryHandlers } from "../../../handlers/Registrar"; +import { NAMEHASH_ETH } from "../../../lib/ens-helpers"; import { ns } from "../ponder.config"; -type NsType = ReturnType>; - -// all nodes referenced by EthRegistrar are parented to .eth -const ROOT_NODE = NAMEHASH_ETH; -const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds - -async function handleNameRegistered({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { id, owner, expires } = event.args; - - await upsertAccount(context, owner); - - const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(ROOT_NODE, label); - - // TODO: materialze labelName via rainbow tables ala Registry.ts - const labelName = undefined; - - await upsertRegistration(context, { - id: label, - domainId: node, - registrationDate: event.block.timestamp, - expiryDate: expires, - registrantId: owner, - labelName, - }); - - await context.db.update(domains, { id: node }).set({ - registrantId: owner, - expiryDate: expires + GRACE_PERIOD_SECONDS, - labelName, - }); - - // TODO: log Event -} - -async function handleNameRegisteredByControllerOld({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - return await setNamePreimage(context, event.args.name, event.args.label, event.args.cost); -} - -async function handleNameRegisteredByController({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - return await setNamePreimage( - context, - event.args.name, - event.args.label, - event.args.baseCost + event.args.premium, - ); -} - -async function handleNameRenewedByController({ - context, - event, -}: { - context: Context; - event: - | Event> - | Event>; -}) { - return await setNamePreimage(context, event.args.name, event.args.label, event.args.cost); -} - -async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { - if (!isLabelValid(name)) return; - - const node = makeSubnodeNamehash(ROOT_NODE, label); - const domain = await context.db.find(domains, { id: node }); - if (!domain) throw new Error("domain expected"); - - if (domain.labelName !== name) { - await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}.eth` }); - } - - await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); -} - -async function handleNameRenewed({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { id, expires } = event.args; - - const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(ROOT_NODE, label); - - await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); - - await context.db - .update(domains, { id: node }) - .set({ expiryDate: expires + GRACE_PERIOD_SECONDS }); - - // TODO: log Event -} - -async function handleNameTransferred({ - context, - event, -}: { - context: Context; - event: Event>; -}) { - const { tokenId, from, to } = event.args; - - await upsertAccount(context, to); - - const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(ROOT_NODE, label); - - const registration = await context.db.find(registrations, { id: label }); - if (!registration) return; - - await context.db.update(registrations, { id: label }).set({ registrantId: to }); - - await context.db.update(domains, { id: node }).set({ registrantId: to }); - - // TODO: log Event -} +const { + handleNameRegistered, + handleNameRegisteredByController, + handleNameRenewedByController, + handleNameRenewed, + handleNameTransferred, +} = makeRegistryHandlers(NAMEHASH_ETH); export default function () { ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); - ponder.on(ns("BaseRegistrar:Transfer"), handleNameTransferred); - ponder.on(ns("EthRegistrarControllerOld:NameRegistered"), handleNameRegisteredByControllerOld); - ponder.on(ns("EthRegistrarControllerOld:NameRenewed"), handleNameRenewedByController); + ponder.on(ns("BaseRegistrar:Transfer"), async ({ context, event }) => { + return await handleNameTransferred({ context, args: event.args }); + }); + + ponder.on(ns("EthRegistrarControllerOld:NameRegistered"), async ({ context, event }) => { + // the old registrar controller just had `cost` param + return await handleNameRegisteredByController({ context, args: event.args }); + }); + ponder.on(ns("EthRegistrarControllerOld:NameRenewed"), async ({ context, event }) => { + return await handleNameRenewedByController({ context, args: event.args }); + }); - ponder.on(ns("EthRegistrarController:NameRegistered"), handleNameRegisteredByController); - ponder.on(ns("EthRegistrarController:NameRenewed"), handleNameRenewedByController); + ponder.on(ns("EthRegistrarController:NameRegistered"), async ({ context, event }) => { + // the new registrar controller uses baseCost + premium to compute cost + return await handleNameRegisteredByController({ + context, + args: { + ...event.args, + cost: event.args.baseCost + event.args.premium, + }, + }); + }); + ponder.on(ns("EthRegistrarController:NameRenewed"), async ({ context, event }) => { + return await handleNameRenewedByController({ context, args: event.args }); + }); } From a88933e047c7754d8cf3d91a5142ca4b64515565 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 3 Jan 2025 22:33:05 +0100 Subject: [PATCH 10/30] feat(ponder-ens-plugin): use domain name path as a namespace --- .env.local.example | 2 +- ponder.config.ts | 2 +- src/handlers/Registrar.ts | 2 +- src/handlers/Registry.ts | 3 +-- src/handlers/Resolver.ts | 4 ++-- src/lib/plugins.ts | 14 +++++++++----- src/ponder-ens-plugins/eth.base/ponder.config.ts | 6 ++++-- src/ponder-ens-plugins/eth/ponder.config.ts | 7 ++++--- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.env.local.example b/.env.local.example index cc3c2294..5e8e6446 100644 --- a/.env.local.example +++ b/.env.local.example @@ -6,7 +6,7 @@ PONDER_RPC_URL_59144=https://linea-rpc.publicnode.com # ponder indexer ens deployment configuration -INDEX_ENS_ROOT_NODE=base.eth +INDEX_ENS_ROOT_NODE=.base.eth # ponder indexer database configuration diff --git a/ponder.config.ts b/ponder.config.ts index 4835472a..fa746e5f 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -21,6 +21,6 @@ export default ((): AllConfigs => { activateBase(); return baseConfig as AllConfigs; default: - throw new Error(`Unsupported ENS_ROOT_NODE: ${process.env.INDEX_ENS_ROOT_NODE}`); + throw new Error(`Unsupported INDEX_ENS_ROOT_NODE: ${process.env.INDEX_ENS_ROOT_NODE}`); } })(); diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index fcc15056..3b1ca2d1 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -6,7 +6,7 @@ import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-he import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertRegistration } from "../lib/upserts"; -type NsType = NsReturnType; +type NsType = NsReturnType; const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index 55e3ae36..9dd31128 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -2,13 +2,12 @@ import { type Context, type Event } from "ponder:registry"; import { resolvers } from "ponder:schema"; import { domains } from "ponder:schema"; import { type Hex, zeroAddress } from "viem"; -import { mainnet } from "viem/chains"; import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; import { makeResolverId } from "../lib/ids"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount } from "../lib/upserts"; -type NsType = NsReturnType; +type NsType = NsReturnType; export async function setup({ context }: { context: Context }) { // ensure we have an account for the zeroAddress diff --git a/src/handlers/Resolver.ts b/src/handlers/Resolver.ts index 9d92d7b0..68da7a3e 100644 --- a/src/handlers/Resolver.ts +++ b/src/handlers/Resolver.ts @@ -6,7 +6,7 @@ import { makeResolverId } from "../lib/ids"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertResolver } from "../lib/upserts"; -type NsType = NsReturnType; +type NsType = NsReturnType; // there is a legacy resolver abi with different TextChanged events. // luckily the subgraph doesn't care about the value parameter so we can use a union @@ -18,7 +18,7 @@ type AnyTextChangedEvent = | Event< NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> > - | Event> + | Event> | Event< NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> > diff --git a/src/lib/plugins.ts b/src/lib/plugins.ts index 03d9d296..6f494512 100644 --- a/src/lib/plugins.ts +++ b/src/lib/plugins.ts @@ -1,14 +1,18 @@ // TODO: change the chainId to the root node value (i.e. namehash("base.eth")) -export function createNs(chainId: ChainId) { +export function createNs(domain: Domain) { /** Creates a name-spaced contract name */ return function ns( contractName: ContractName, - ): NsReturnType { - return `Chain${chainId}_${contractName}` as NsReturnType; + ): NsReturnType { + return `${domain}/${contractName}` as NsReturnType; }; } export type NsReturnType< ContractName extends string, - ChainId extends number, -> = `Chain${ChainId}_${ContractName}`; + Domain extends EnsRootDomain, +> = `${Domain}/${ContractName}`; + +export const EnsRootDomains = ['/eth', '/eth/base'] as const + +export type EnsRootDomain = typeof EnsRootDomains[number] \ No newline at end of file diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index fa679ba2..b5da26d9 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -2,14 +2,16 @@ import { createConfig, factory } from "ponder"; import { http, getAbiItem } from "viem"; import { base } from "viem/chains"; -import { createNs } from "../../lib/plugins"; +import { createNs, NsReturnType } from "../../lib/plugins"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -export const ns = createNs(base.id); +const rootDomain = '/eth/base' as const +export const ns = createNs(rootDomain); +export type NsType = NsReturnType; const REGISTRY_ADDRESS = "0xb94704422c2a1e396835a571837aa5ae53285a95"; const REGISTRY_START_BLOCK = 17571480; diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index 0bc6cccc..1569302a 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -1,8 +1,7 @@ import { createConfig, factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; -import { mainnet } from "viem/chains"; -import { createNs } from "../../lib/plugins"; +import { createNs, NsReturnType } from "../../lib/plugins"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; @@ -16,7 +15,9 @@ const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; -export const ns = createNs(mainnet.id); +const rootDomain = '/eth' as const +export const ns = createNs(rootDomain); +export type NsType = NsReturnType; export const config = createConfig({ networks: { From 7e5af0e0d5054fc2723bc37eab3edc7ad6c9eb00 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Fri, 3 Jan 2025 22:36:51 +0100 Subject: [PATCH 11/30] chore(ponder-ens-plugin): use biomejs formatter --- src/handlers/Registry.ts | 2 +- src/handlers/Resolver.ts | 2 +- src/lib/plugins.ts | 4 +-- .../eth.base/abis/BaseRegistrar.ts | 12 ++------ .../eth.base/abis/EARegistrarController.ts | 28 +++++-------------- .../eth.base/abis/L2Resolver.ts | 12 ++------ .../eth.base/abis/RegistrarController.ts | 28 +++++-------------- .../eth.base/abis/ReverseRegistrar.ts | 12 ++------ .../eth.base/ponder.config.ts | 10 ++++--- src/ponder-ens-plugins/eth/ponder.config.ts | 4 +-- 10 files changed, 35 insertions(+), 79 deletions(-) diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index 9dd31128..35ea1ab2 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -7,7 +7,7 @@ import { makeResolverId } from "../lib/ids"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount } from "../lib/upserts"; -type NsType = NsReturnType; +type NsType = NsReturnType; export async function setup({ context }: { context: Context }) { // ensure we have an account for the zeroAddress diff --git a/src/handlers/Resolver.ts b/src/handlers/Resolver.ts index 68da7a3e..138728bc 100644 --- a/src/handlers/Resolver.ts +++ b/src/handlers/Resolver.ts @@ -6,7 +6,7 @@ import { makeResolverId } from "../lib/ids"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertResolver } from "../lib/upserts"; -type NsType = NsReturnType; +type NsType = NsReturnType; // there is a legacy resolver abi with different TextChanged events. // luckily the subgraph doesn't care about the value parameter so we can use a union diff --git a/src/lib/plugins.ts b/src/lib/plugins.ts index 6f494512..bab5ee99 100644 --- a/src/lib/plugins.ts +++ b/src/lib/plugins.ts @@ -13,6 +13,6 @@ export type NsReturnType< Domain extends EnsRootDomain, > = `${Domain}/${ContractName}`; -export const EnsRootDomains = ['/eth', '/eth/base'] as const +export const EnsRootDomains = ["/eth", "/eth/base"] as const; -export type EnsRootDomain = typeof EnsRootDomains[number] \ No newline at end of file +export type EnsRootDomain = (typeof EnsRootDomains)[number]; diff --git a/src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts b/src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts index ecfd073c..0cf2d6ab 100644 --- a/src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts +++ b/src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts @@ -298,9 +298,7 @@ export const BaseRegistrar = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "completeOwnershipHandover", outputs: [], stateMutability: "payable", @@ -373,9 +371,7 @@ export const BaseRegistrar = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "ownershipHandoverExpiresAt", outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], stateMutability: "view", @@ -505,9 +501,7 @@ export const BaseRegistrar = [ type: "function", }, { - inputs: [ - { internalType: "string", name: "collectionURI_", type: "string" }, - ], + inputs: [{ internalType: "string", name: "collectionURI_", type: "string" }], name: "setContractURI", outputs: [], stateMutability: "nonpayable", diff --git a/src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts b/src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts index f485bc16..2923cbc1 100644 --- a/src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts +++ b/src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts @@ -295,9 +295,7 @@ export const EarlyAccessRegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "completeOwnershipHandover", outputs: [], stateMutability: "payable", @@ -340,9 +338,7 @@ export const EarlyAccessRegistrarController = [ { inputs: [{ internalType: "address", name: "registrant", type: "address" }], name: "discountedRegistrants", - outputs: [ - { internalType: "bool", name: "hasRegisteredWithDiscount", type: "bool" }, - ], + outputs: [{ internalType: "bool", name: "hasRegisteredWithDiscount", type: "bool" }], stateMutability: "view", type: "function", }, @@ -382,9 +378,7 @@ export const EarlyAccessRegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address[]", name: "addresses", type: "address[]" }, - ], + inputs: [{ internalType: "address[]", name: "addresses", type: "address[]" }], name: "hasRegisteredWithDiscount", outputs: [{ internalType: "bool", name: "", type: "bool" }], stateMutability: "view", @@ -398,9 +392,7 @@ export const EarlyAccessRegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "ownershipHandoverExpiresAt", outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], stateMutability: "view", @@ -416,9 +408,7 @@ export const EarlyAccessRegistrarController = [ { inputs: [], name: "prices", - outputs: [ - { internalType: "contract IPriceOracle", name: "", type: "address" }, - ], + outputs: [{ internalType: "contract IPriceOracle", name: "", type: "address" }], stateMutability: "view", type: "function", }, @@ -480,9 +470,7 @@ export const EarlyAccessRegistrarController = [ { inputs: [], name: "reverseRegistrar", - outputs: [ - { internalType: "contract IReverseRegistrar", name: "", type: "address" }, - ], + outputs: [{ internalType: "contract IReverseRegistrar", name: "", type: "address" }], stateMutability: "view", type: "function", }, @@ -524,9 +512,7 @@ export const EarlyAccessRegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "paymentReceiver_", type: "address" }, - ], + inputs: [{ internalType: "address", name: "paymentReceiver_", type: "address" }], name: "setPaymentReceiver", outputs: [], stateMutability: "nonpayable", diff --git a/src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts b/src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts index 1219260d..63e1c09d 100644 --- a/src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts +++ b/src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts @@ -359,9 +359,7 @@ export const L2Resolver = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "completeOwnershipHandover", outputs: [], stateMutability: "payable", @@ -465,9 +463,7 @@ export const L2Resolver = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "ownershipHandoverExpiresAt", outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], stateMutability: "view", @@ -636,9 +632,7 @@ export const L2Resolver = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "reverseRegistrar_", type: "address" }, - ], + inputs: [{ internalType: "address", name: "reverseRegistrar_", type: "address" }], name: "setReverseRegistrar", outputs: [], stateMutability: "nonpayable", diff --git a/src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts b/src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts index e301be80..eed59ccb 100644 --- a/src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts +++ b/src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts @@ -315,9 +315,7 @@ export const RegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "completeOwnershipHandover", outputs: [], stateMutability: "payable", @@ -360,9 +358,7 @@ export const RegistrarController = [ { inputs: [{ internalType: "address", name: "registrant", type: "address" }], name: "discountedRegistrants", - outputs: [ - { internalType: "bool", name: "hasRegisteredWithDiscount", type: "bool" }, - ], + outputs: [{ internalType: "bool", name: "hasRegisteredWithDiscount", type: "bool" }], stateMutability: "view", type: "function", }, @@ -402,9 +398,7 @@ export const RegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address[]", name: "addresses", type: "address[]" }, - ], + inputs: [{ internalType: "address[]", name: "addresses", type: "address[]" }], name: "hasRegisteredWithDiscount", outputs: [{ internalType: "bool", name: "", type: "bool" }], stateMutability: "view", @@ -425,9 +419,7 @@ export const RegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "ownershipHandoverExpiresAt", outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], stateMutability: "view", @@ -443,9 +435,7 @@ export const RegistrarController = [ { inputs: [], name: "prices", - outputs: [ - { internalType: "contract IPriceOracle", name: "", type: "address" }, - ], + outputs: [{ internalType: "contract IPriceOracle", name: "", type: "address" }], stateMutability: "view", type: "function", }, @@ -538,9 +528,7 @@ export const RegistrarController = [ { inputs: [], name: "reverseRegistrar", - outputs: [ - { internalType: "contract IReverseRegistrar", name: "", type: "address" }, - ], + outputs: [{ internalType: "contract IReverseRegistrar", name: "", type: "address" }], stateMutability: "view", type: "function", }, @@ -589,9 +577,7 @@ export const RegistrarController = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "paymentReceiver_", type: "address" }, - ], + inputs: [{ internalType: "address", name: "paymentReceiver_", type: "address" }], name: "setPaymentReceiver", outputs: [], stateMutability: "nonpayable", diff --git a/src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts b/src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts index b1354f1e..cc67d88c 100644 --- a/src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts +++ b/src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts @@ -137,9 +137,7 @@ export const ReverseRegistrar = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "completeOwnershipHandover", outputs: [], stateMutability: "payable", @@ -155,9 +153,7 @@ export const ReverseRegistrar = [ { inputs: [], name: "defaultResolver", - outputs: [ - { internalType: "contract NameResolver", name: "", type: "address" }, - ], + outputs: [{ internalType: "contract NameResolver", name: "", type: "address" }], stateMutability: "view", type: "function", }, @@ -176,9 +172,7 @@ export const ReverseRegistrar = [ type: "function", }, { - inputs: [ - { internalType: "address", name: "pendingOwner", type: "address" }, - ], + inputs: [{ internalType: "address", name: "pendingOwner", type: "address" }], name: "ownershipHandoverExpiresAt", outputs: [{ internalType: "uint256", name: "result", type: "uint256" }], stateMutability: "view", diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index b5da26d9..c97ecc64 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -2,14 +2,14 @@ import { createConfig, factory } from "ponder"; import { http, getAbiItem } from "viem"; import { base } from "viem/chains"; -import { createNs, NsReturnType } from "../../lib/plugins"; +import { NsReturnType, createNs } from "../../lib/plugins"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -const rootDomain = '/eth/base' as const +const rootDomain = "/eth/base" as const; export const ns = createNs(rootDomain); export type NsType = NsReturnType; @@ -19,10 +19,12 @@ const REGISTRY_START_BLOCK = 17571480; const BASE_REGISTRAR_ADDRESS = "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a"; const BASE_REGISTRAR_START_BLOCK = 17571486; -const REGISTRAR_CONTROLLER_ADDRESS = "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; +const REGISTRAR_CONTROLLER_ADDRESS = + "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; const REGISTRAR_CONTROLLER_START_BLOCK = 18619035; -const EA_REGISTRAR_CONTROLLER_ADDRESS = "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda"; +const EA_REGISTRAR_CONTROLLER_ADDRESS = + "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda"; const EA_REGISTRAR_CONTROLLER_START_BLOCK = 17575699; const REVERSE_REGISTRAR_ADDRESS = "0x79ea96012eea67a83431f1701b3dff7e37f9e282"; diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index 1569302a..5c667c16 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -1,7 +1,7 @@ import { createConfig, factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; -import { createNs, NsReturnType } from "../../lib/plugins"; +import { NsReturnType, createNs } from "../../lib/plugins"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; @@ -15,7 +15,7 @@ const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; -const rootDomain = '/eth' as const +const rootDomain = "/eth" as const; export const ns = createNs(rootDomain); export type NsType = NsReturnType; From cd152a6e2a45247890ce0cdc14651918d1d5bc62 Mon Sep 17 00:00:00 2001 From: shrugs Date: Fri, 3 Jan 2025 16:10:06 -0600 Subject: [PATCH 12/30] fix: tidy registrar handlers --- .../eth.base/handlers/Registrar.ts | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index ac47b134..47e496e8 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -11,6 +11,8 @@ const { handleNameTransferred, } = makeRegistryHandlers(NAMEHASH_BASE_ETH); +// support NameRegisteredWithRecord ? + export default function () { ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); @@ -19,40 +21,28 @@ export default function () { ponder.on(ns("BaseRegistrar:Transfer"), async ({ context, event }) => { return await handleNameTransferred({ context, - args: { - ...event.args, - tokenId: event.args.id, - }, + args: { ...event.args, tokenId: event.args.id }, }); }); ponder.on(ns("EARegistrarController:NameRegistered"), async ({ context, event }) => { return handleNameRegisteredByController({ context, - args: { - ...event.args, - cost: 0n, - }, + args: { ...event.args, cost: 0n }, }); }); ponder.on(ns("RegistrarController:NameRegistered"), async ({ context, event }) => { return handleNameRegisteredByController({ context, - args: { - ...event.args, - cost: 0n, - }, + args: { ...event.args, cost: 0n }, }); }); ponder.on(ns("RegistrarController:NameRenewed"), async ({ context, event }) => { return handleNameRenewedByController({ context, - args: { - ...event.args, - cost: 0n, - }, + args: { ...event.args, cost: 0n }, }); }); } From dc0bfdd566c551b5c88b8c51898aa8ed52a3a306 Mon Sep 17 00:00:00 2001 From: shrugs Date: Fri, 3 Jan 2025 19:04:20 -0600 Subject: [PATCH 13/30] fix: handle preminted domain in base node --- src/handlers/Registrar.ts | 1 - .../eth.base/handlers/Registrar.ts | 33 +++++++++++++++++-- .../eth.base/ponder.config.ts | 6 ++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index 3b1ca2d1..8c5c0066 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -1,7 +1,6 @@ import { type Context, type Event } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; import type { Hex } from "viem"; -import { base, mainnet } from "viem/chains"; import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertRegistration } from "../lib/upserts"; diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index 47e496e8..5a734399 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -1,6 +1,8 @@ import { ponder } from "ponder:registry"; +import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { NAMEHASH_BASE_ETH } from "../../../lib/ens-helpers"; +import { NAMEHASH_BASE_ETH, makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; +import { upsertAccount } from "../../../lib/upserts"; import { ns } from "../ponder.config"; const { @@ -11,10 +13,31 @@ const { handleNameTransferred, } = makeRegistryHandlers(NAMEHASH_BASE_ETH); -// support NameRegisteredWithRecord ? +// support NameRegisteredWithRecord export default function () { - ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); + ponder.on(ns("BaseRegistrar:NameRegistered"), async ({ context, event }) => { + // base has 'preminted' names via Registrar#registerOnly, which explicitly does not update Registry. + // this breaks a subgraph assumption, as it expects a domain to exist (via Registry:NewOwner) before + // any Registrar:NameRegistered events. in the future we will likely happily upsert domains, but + // in order to avoid prematurely drifting from subgraph equivalancy, we upsert the domain here, + // allowing the base indexer to progress. + const { id, owner } = event.args; + const label = tokenIdToLabel(id); + const node = makeSubnodeNamehash(NAMEHASH_BASE_ETH, label); + await upsertAccount(context, owner); + await context.db + .insert(domains) + .values({ + id: node, + ownerId: owner, + createdAt: event.block.timestamp, + }) + .onConflictDoNothing(); + + // after ensuring the domain exists, continue with the standard handler + return handleNameRegistered({ context, event }); + }); ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); // Base's BaseRegistrar uses `id` instead of `tokenId` @@ -26,6 +49,8 @@ export default function () { }); ponder.on(ns("EARegistrarController:NameRegistered"), async ({ context, event }) => { + // TODO: registration expected here + return handleNameRegisteredByController({ context, args: { ...event.args, cost: 0n }, @@ -33,6 +58,8 @@ export default function () { }); ponder.on(ns("RegistrarController:NameRegistered"), async ({ context, event }) => { + // TODO: registration expected here + return handleNameRegisteredByController({ context, args: { ...event.args, cost: 0n }, diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index c97ecc64..1a493778 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -19,12 +19,10 @@ const REGISTRY_START_BLOCK = 17571480; const BASE_REGISTRAR_ADDRESS = "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a"; const BASE_REGISTRAR_START_BLOCK = 17571486; -const REGISTRAR_CONTROLLER_ADDRESS = - "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; +const REGISTRAR_CONTROLLER_ADDRESS = "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; const REGISTRAR_CONTROLLER_START_BLOCK = 18619035; -const EA_REGISTRAR_CONTROLLER_ADDRESS = - "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda"; +const EA_REGISTRAR_CONTROLLER_ADDRESS = "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda"; const EA_REGISTRAR_CONTROLLER_START_BLOCK = 17575699; const REVERSE_REGISTRAR_ADDRESS = "0x79ea96012eea67a83431f1701b3dff7e37f9e282"; From 77c1ec29f9750d0465f9a5d84b18177e65aec050 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Sat, 4 Jan 2025 09:28:31 +0100 Subject: [PATCH 14/30] fix(base.eth): handle `NameRegisteredWithRecord` event This event is emitted by the `BaseRegistrar` during a registration from Bases RegistrarControllers --- src/handlers/Registrar.ts | 2 +- .../eth.base/handlers/Registrar.ts | 9 ++-- .../eth.base/ponder.config.ts | 41 +++++-------------- src/ponder-ens-plugins/eth/ponder.config.ts | 8 ++-- 4 files changed, 21 insertions(+), 39 deletions(-) diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index 8c5c0066..5cff2b5b 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -30,7 +30,7 @@ export const makeRegistryHandlers = (root: Hex) => { event, }: { context: Context; - event: Event>; + event: Omit>, "name">; }) { const { id, owner, expires } = event.args; diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index 5a734399..cd5267ce 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -1,5 +1,5 @@ import { ponder } from "ponder:registry"; -import { domains } from "ponder:schema"; +import { domains, registrations } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; import { NAMEHASH_BASE_ETH, makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; import { upsertAccount } from "../../../lib/upserts"; @@ -13,9 +13,12 @@ const { handleNameTransferred, } = makeRegistryHandlers(NAMEHASH_BASE_ETH); -// support NameRegisteredWithRecord - export default function () { + // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers + ponder.on(ns("BaseRegistrar:NameRegisteredWithRecord"), async ({ context, event }) => + handleNameRegistered({ context, event }), + ); + ponder.on(ns("BaseRegistrar:NameRegistered"), async ({ context, event }) => { // base has 'preminted' names via Registrar#registerOnly, which explicitly does not update Registry. // this breaks a subgraph assumption, as it expects a domain to exist (via Registry:NewOwner) before diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index 1a493778..ed44bd9c 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -13,27 +13,6 @@ const rootDomain = "/eth/base" as const; export const ns = createNs(rootDomain); export type NsType = NsReturnType; -const REGISTRY_ADDRESS = "0xb94704422c2a1e396835a571837aa5ae53285a95"; -const REGISTRY_START_BLOCK = 17571480; - -const BASE_REGISTRAR_ADDRESS = "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a"; -const BASE_REGISTRAR_START_BLOCK = 17571486; - -const REGISTRAR_CONTROLLER_ADDRESS = "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5"; -const REGISTRAR_CONTROLLER_START_BLOCK = 18619035; - -const EA_REGISTRAR_CONTROLLER_ADDRESS = "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda"; -const EA_REGISTRAR_CONTROLLER_START_BLOCK = 17575699; - -const REVERSE_REGISTRAR_ADDRESS = "0x79ea96012eea67a83431f1701b3dff7e37f9e282"; -const REVERSE_REGISTRAR_START_BLOCK = 17571485; - -const L1_RESOLVER_ADDRESS = "0xde9049636F4a1dfE0a64d1bFe3155C0A14C54F31"; -const L1_RESOLVER_START_BLOCK = 20420641; - -const L2_RESOLVER_ADDRESS = "0xC6d566A56A1aFf6508b41f6c90ff131615583BCD"; -const L2_RESOLVER_START_BLOCK = 17575714; - export const config = createConfig({ networks: { base: { @@ -45,36 +24,36 @@ export const config = createConfig({ [ns("Registry")]: { network: "base", abi: Registry, - address: REGISTRY_ADDRESS, - startBlock: REGISTRY_START_BLOCK, + address: "0xb94704422c2a1e396835a571837aa5ae53285a95", + startBlock: 17571480, }, [ns("Resolver")]: { network: "base", abi: L2Resolver, address: factory({ - address: L2_RESOLVER_ADDRESS, + address: "0xb94704422c2a1e396835a571837aa5ae53285a95", event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - startBlock: L2_RESOLVER_START_BLOCK, + startBlock: 17575714, }, [ns("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, - address: BASE_REGISTRAR_ADDRESS, - startBlock: BASE_REGISTRAR_START_BLOCK, + address: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a", + startBlock: 17571486, }, [ns("EARegistrarController")]: { network: "base", abi: EarlyAccessRegistrarController, - address: EA_REGISTRAR_CONTROLLER_ADDRESS, - startBlock: EA_REGISTRAR_CONTROLLER_START_BLOCK, + address: "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda", + startBlock: 17575699, }, [ns("RegistrarController")]: { network: "base", abi: RegistrarController, - address: REGISTRAR_CONTROLLER_ADDRESS, - startBlock: REGISTRAR_CONTROLLER_START_BLOCK, + address: "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5", + startBlock: 18619035, }, }, }); diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index 5c667c16..30311437 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -30,20 +30,20 @@ export const config = createConfig({ [ns("RegistryOld")]: { network: "mainnet", abi: Registry, - address: REGISTRY_OLD_ADDRESS, + address: "0x314159265dd8dbb310642f98f50c066173c1259b", startBlock: 3327417, }, [ns("Registry")]: { network: "mainnet", abi: Registry, - address: REGISTRY_ADDRESS, + address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", startBlock: 9380380, }, [ns("OldRegistryResolvers")]: { network: "mainnet", abi: RESOLVER_ABI, address: factory({ - address: REGISTRY_OLD_ADDRESS, + address: "0x314159265dd8dbb310642f98f50c066173c1259b", event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), @@ -53,7 +53,7 @@ export const config = createConfig({ network: "mainnet", abi: RESOLVER_ABI, address: factory({ - address: REGISTRY_ADDRESS, + address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), From ff0b2af6129e6123dbdab33a4fcaa99eb36cc1a3 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Sun, 5 Jan 2025 19:53:54 +0100 Subject: [PATCH 15/30] fix(deps): update ponder version This one includes a fix for duplicated log entries error --- package.json | 2 +- pnpm-lock.yaml | 30 +++++++++---------- .../eth.base/ponder.config.ts | 18 +++++++---- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index bfa13d0f..fa393d41 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "hono": "^4.6.14", - "ponder": "^0.8.8", + "ponder": "^0.8.13", "viem": "^2.21.57" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f073c5f..c4953c80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^4.6.14 version: 4.6.14 ponder: - specifier: ^0.8.8 - version: 0.8.8(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)) + specifier: ^0.8.13 + version: 0.8.13(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)) viem: specifier: ^2.21.57 version: 2.21.57(typescript@5.7.2) @@ -103,8 +103,8 @@ packages: peerDependencies: commander: ~12.1.0 - '@electric-sql/pglite@0.2.15': - resolution: {integrity: sha512-Jiq31Dnk+rg8rMhcSxs4lQvHTyizNo5b269c1gCC3ldQ0sCLrNVPGzy+KnmonKy1ZArTUuXZf23/UamzFMKVaA==} + '@electric-sql/pglite@0.2.13': + resolution: {integrity: sha512-YRY806NnScVqa21/1L1vaysSQ+0/cAva50z7vlwzaGiBOTS9JhdzIRHN0KfgMhobFAphbznZJ7urMso4RtMBIQ==} '@envelop/core@5.0.2': resolution: {integrity: sha512-tVL6OrMe6UjqLosiE+EH9uxh2TQC0469GwF4tE014ugRaDDKKVWwFwZe0TBMlcyHKh5MD4ZxktWo/1hqUxIuhw==} @@ -1381,8 +1381,8 @@ packages: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} hasBin: true - ponder@0.8.8: - resolution: {integrity: sha512-dz1PJ3a8m4/TZbB4/CwyHYxS6ky8HxDvvl4n4uctKa81ZFjR+YjHU3hfQFimTt2GpNi0vq4ZxCr9bzc20aSafw==} + ponder@0.8.13: + resolution: {integrity: sha512-knA9W3gYQCbGrCfS8wMIhK7PC6xH9bf9qU+DfQRPRRmFP6V4UL/rqoOtPETYuIm04F9Ljowhc3bGOvFp/lPc3g==} engines: {node: '>=18.14'} hasBin: true peerDependencies: @@ -1839,7 +1839,7 @@ snapshots: dependencies: commander: 12.1.0 - '@electric-sql/pglite@0.2.15': {} + '@electric-sql/pglite@0.2.13': {} '@envelop/core@5.0.2': dependencies: @@ -2392,9 +2392,9 @@ snapshots: dotenv@16.4.7: {} - drizzle-orm@0.36.4(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(kysely@0.26.3)(pg@8.13.1)(react@18.3.1): + drizzle-orm@0.36.4(@electric-sql/pglite@0.2.13)(@opentelemetry/api@1.9.0)(kysely@0.26.3)(pg@8.13.1)(react@18.3.1): optionalDependencies: - '@electric-sql/pglite': 0.2.15 + '@electric-sql/pglite': 0.2.13 '@opentelemetry/api': 1.9.0 kysely: 0.26.3 pg: 8.13.1 @@ -2742,9 +2742,9 @@ snapshots: optionalDependencies: pg: 8.13.1 - kysely-pglite@0.6.1(@electric-sql/pglite@0.2.15)(kysely@0.26.3)(pg@8.13.1): + kysely-pglite@0.6.1(@electric-sql/pglite@0.2.13)(kysely@0.26.3)(pg@8.13.1): dependencies: - '@electric-sql/pglite': 0.2.15 + '@electric-sql/pglite': 0.2.13 '@oclif/core': 4.2.0 '@repeaterjs/repeater': 3.0.6 '@sindresorhus/is': 7.0.1 @@ -2934,11 +2934,11 @@ snapshots: sonic-boom: 3.8.1 thread-stream: 2.7.0 - ponder@0.8.8(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)): + ponder@0.8.13(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)): dependencies: '@babel/code-frame': 7.26.2 '@commander-js/extra-typings': 12.1.0(commander@12.1.0) - '@electric-sql/pglite': 0.2.15 + '@electric-sql/pglite': 0.2.13 '@escape.tech/graphql-armor-max-aliases': 2.6.0 '@escape.tech/graphql-armor-max-depth': 2.4.0 '@escape.tech/graphql-armor-max-tokens': 2.5.0 @@ -2950,7 +2950,7 @@ snapshots: dataloader: 2.2.3 detect-package-manager: 3.0.2 dotenv: 16.4.7 - drizzle-orm: 0.36.4(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(kysely@0.26.3)(pg@8.13.1)(react@18.3.1) + drizzle-orm: 0.36.4(@electric-sql/pglite@0.2.13)(@opentelemetry/api@1.9.0)(kysely@0.26.3)(pg@8.13.1)(react@18.3.1) glob: 10.4.5 graphql: 16.10.0 graphql-yoga: 5.10.8(graphql@16.10.0) @@ -2958,7 +2958,7 @@ snapshots: http-terminator: 3.2.0 ink: 4.4.1(react@18.3.1) kysely: 0.26.3 - kysely-pglite: 0.6.1(@electric-sql/pglite@0.2.15)(kysely@0.26.3)(pg@8.13.1) + kysely-pglite: 0.6.1(@electric-sql/pglite@0.2.13)(kysely@0.26.3)(pg@8.13.1) pg: 8.13.1 pg-connection-string: 2.7.0 picocolors: 1.1.1 diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index ed44bd9c..641cd998 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -13,6 +13,9 @@ const rootDomain = "/eth/base" as const; export const ns = createNs(rootDomain); export type NsType = NsReturnType; +const START_BLOCK = undefined; // 17607350; +const END_BLOCK = undefined; // 17607351; + export const config = createConfig({ networks: { base: { @@ -25,7 +28,8 @@ export const config = createConfig({ network: "base", abi: Registry, address: "0xb94704422c2a1e396835a571837aa5ae53285a95", - startBlock: 17571480, + startBlock: START_BLOCK || 17571480, + endBlock: END_BLOCK, }, [ns("Resolver")]: { network: "base", @@ -35,25 +39,29 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - startBlock: 17575714, + startBlock: START_BLOCK || 17575714, + endBlock: END_BLOCK, }, [ns("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, address: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a", - startBlock: 17571486, + startBlock: START_BLOCK || 17571486, + endBlock: END_BLOCK, }, [ns("EARegistrarController")]: { network: "base", abi: EarlyAccessRegistrarController, address: "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda", - startBlock: 17575699, + startBlock: START_BLOCK || 17575699, + endBlock: END_BLOCK, }, [ns("RegistrarController")]: { network: "base", abi: RegistrarController, address: "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5", - startBlock: 18619035, + startBlock: START_BLOCK || 18619035, + endBlock: END_BLOCK, }, }, }); From b3c17e525f180e0e3956e92bcfba3347cd3bc204 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Sun, 5 Jan 2025 22:15:54 +0100 Subject: [PATCH 16/30] fix: use selected root domain name Make the root domain name based of the env var --- src/handlers/Registrar.ts | 18 +++++++++++------- src/lib/ens-helpers.ts | 6 ++++-- .../eth.base/handlers/Registrar.ts | 8 ++++---- .../eth/handlers/EthRegistrar.ts | 3 +-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index 5cff2b5b..4668f679 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -1,7 +1,7 @@ import { type Context, type Event } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; import type { Hex } from "viem"; -import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; +import { ENS_ROOT_DOMAIN_NAME, NAMEHASH_ROOT, isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertRegistration } from "../lib/upserts"; @@ -9,16 +9,20 @@ type NsType = NsReturnType; const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds -export const makeRegistryHandlers = (root: Hex) => { +export const makeRegistryHandlers = () => { + if (!ENS_ROOT_DOMAIN_NAME || !ENS_ROOT_DOMAIN_NAME.endsWith('.eth')) { + throw new Error("ENS_ROOT_DOMAIN_NAME expected to end with '.eth'"); + } + async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; - const node = makeSubnodeNamehash(root, label); + const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { - await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}.eth` }); + await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}${ENS_ROOT_DOMAIN_NAME}` }); } await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); @@ -37,7 +41,7 @@ export const makeRegistryHandlers = (root: Hex) => { await upsertAccount(context, owner); const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(root, label); + const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); // TODO: materialze labelName via rainbow tables ala Registry.ts const labelName = undefined; @@ -90,7 +94,7 @@ export const makeRegistryHandlers = (root: Hex) => { const { id, expires } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(root, label); + const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); @@ -111,7 +115,7 @@ export const makeRegistryHandlers = (root: Hex) => { await upsertAccount(context, to); const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(root, label); + const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); const registration = await context.db.find(registrations, { id: label }); if (!registration) return; diff --git a/src/lib/ens-helpers.ts b/src/lib/ens-helpers.ts index a0605bfd..bdd7558e 100644 --- a/src/lib/ens-helpers.ts +++ b/src/lib/ens-helpers.ts @@ -1,9 +1,11 @@ import { type Hex, concat, keccak256, namehash, toHex } from "viem"; +// TODO: add input validation +export const ENS_ROOT_DOMAIN_NAME = process.env.INDEX_ENS_ROOT_NODE! as `.${string}.eth`; + // TODO: pull from ens utils lib or something export const NAMEHASH_ZERO = namehash(""); -export const NAMEHASH_ETH = namehash("eth"); -export const NAMEHASH_BASE_ETH = namehash("base.eth"); +export const NAMEHASH_ROOT = namehash(ENS_ROOT_DOMAIN_NAME.slice(1)); // TODO: this should probably be a part of some ens util lib export const makeSubnodeNamehash = (node: Hex, label: Hex) => keccak256(concat([node, label])); diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index cd5267ce..8bd3a39c 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -1,7 +1,7 @@ import { ponder } from "ponder:registry"; -import { domains, registrations } from "ponder:schema"; +import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { NAMEHASH_BASE_ETH, makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; +import { NAMEHASH_ROOT, makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; import { upsertAccount } from "../../../lib/upserts"; import { ns } from "../ponder.config"; @@ -11,7 +11,7 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(NAMEHASH_BASE_ETH); +} = makeRegistryHandlers(); export default function () { // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers @@ -27,7 +27,7 @@ export default function () { // allowing the base indexer to progress. const { id, owner } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(NAMEHASH_BASE_ETH, label); + const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); await upsertAccount(context, owner); await context.db .insert(domains) diff --git a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts index da2f0c91..a39cb243 100644 --- a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts @@ -1,6 +1,5 @@ import { ponder } from "ponder:registry"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { NAMEHASH_ETH } from "../../../lib/ens-helpers"; import { ns } from "../ponder.config"; const { @@ -9,7 +8,7 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(NAMEHASH_ETH); +} = makeRegistryHandlers(); export default function () { ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); From 0e35dd879107563904c1ecabd87bd9a84a278c4a Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 6 Jan 2025 12:03:15 +0100 Subject: [PATCH 17/30] refactor: rename consts to apply feedback renames NAMEHASH to NODE --- .env.local.example | 4 +-- README.md | 2 +- ponder.config.ts | 15 ++++++----- src/handlers/Registrar.ts | 26 +++++++++++-------- src/handlers/Registry.ts | 4 +-- src/lib/ens-helpers.ts | 23 +++++++++++++--- .../eth.base/handlers/Registrar.ts | 9 ++++--- .../eth.base/ponder.config.ts | 8 +++--- .../eth/handlers/EthRegistrar.ts | 4 +-- src/ponder-ens-plugins/eth/ponder.config.ts | 9 +++---- 10 files changed, 64 insertions(+), 40 deletions(-) diff --git a/.env.local.example b/.env.local.example index 5e8e6446..f39ea2b0 100644 --- a/.env.local.example +++ b/.env.local.example @@ -6,10 +6,10 @@ PONDER_RPC_URL_59144=https://linea-rpc.publicnode.com # ponder indexer ens deployment configuration -INDEX_ENS_ROOT_NODE=.base.eth +INDEX_BASENAME=.base.eth # ponder indexer database configuration -## one schema name per chain ID, i.e. ponder_ens_${INDEX_ENS_ROOT_NODE} +## one schema name per chain ID, i.e. ponder_ens_${INDEX_BASENAME} DATABASE_SCHEMA=ponder_ens_base.eth DATABASE_URL=postgresql://dbuser:abcd1234@localhost:5432/my_database diff --git a/README.md b/README.md index 08be0e8e..d9b3ba79 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ various resources use both null and zeroAddress to indicate emptiness, this is h ### ens indexing plugin -l2 ens deployments are very similar — write plugin to make configuring source addresses easy and pass node that domains in these handlers are implicitly parented to (assuming that l2 deployments make nodes against the NAMEHASH_ZERO i.e. every name is basically a 2LD) +l2 ens deployments are very similar — write plugin to make configuring source addresses easy and pass node that domains in these handlers are implicitly parented to (assuming that l2 deployments make nodes against the ROOT_NODE i.e. every name is basically a 2LD) ### registry diff --git a/ponder.config.ts b/ponder.config.ts index fa746e5f..24e6dd33 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,10 +1,13 @@ +import { BASENAME } from "./src/lib/ens-helpers"; import { activate as activateBase, - config as baseConfig, + baseName as baseBaseName, + config as baseConfig } from "./src/ponder-ens-plugins/eth.base/ponder.config"; import { activate as activateEth, - config as ethereumConfig, + baseName as ethBaseName, + config as ethereumConfig } from "./src/ponder-ens-plugins/eth/ponder.config"; type AllConfigs = typeof ethereumConfig & typeof baseConfig; @@ -13,14 +16,14 @@ type AllConfigs = typeof ethereumConfig & typeof baseConfig; // this makes all of the mapping types happy at typecheck-time, but only the relevant // config is run at runtime export default ((): AllConfigs => { - switch (process.env.INDEX_ENS_ROOT_NODE) { - case ".eth": + switch (BASENAME) { + case ethBaseName: activateEth(); return ethereumConfig as AllConfigs; - case ".base.eth": + case baseBaseName: activateBase(); return baseConfig as AllConfigs; default: - throw new Error(`Unsupported INDEX_ENS_ROOT_NODE: ${process.env.INDEX_ENS_ROOT_NODE}`); + throw new Error(`Unsupported base name: ${BASENAME}`); } })(); diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index 4668f679..654493b9 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -1,7 +1,7 @@ import { type Context, type Event } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; -import type { Hex } from "viem"; -import { ENS_ROOT_DOMAIN_NAME, NAMEHASH_ROOT, isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; +import { type Hex, namehash } from "viem"; +import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertRegistration } from "../lib/upserts"; @@ -9,26 +9,30 @@ type NsType = NsReturnType; const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds -export const makeRegistryHandlers = () => { - if (!ENS_ROOT_DOMAIN_NAME || !ENS_ROOT_DOMAIN_NAME.endsWith('.eth')) { - throw new Error("ENS_ROOT_DOMAIN_NAME expected to end with '.eth'"); - } +export const makeRegistryHandlers = (baseName: `.${`${string}`}eth`) => { + const baseNameNode = namehash(baseName.slice(1)); async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; - const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); + const node = makeSubnodeNamehash(baseNameNode, label); const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { - await context.db.update(domains, { id: node }).set({ labelName: name, name: `${name}${ENS_ROOT_DOMAIN_NAME}` }); + await context.db + .update(domains, { id: node }) + .set({ labelName: name, name: `${name}${baseName}` }); } await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); } return { + get baseNameNode() { + return baseNameNode; + }, + async handleNameRegistered({ context, event, @@ -41,7 +45,7 @@ export const makeRegistryHandlers = () => { await upsertAccount(context, owner); const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); + const node = makeSubnodeNamehash(baseNameNode, label); // TODO: materialze labelName via rainbow tables ala Registry.ts const labelName = undefined; @@ -94,7 +98,7 @@ export const makeRegistryHandlers = () => { const { id, expires } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); + const node = makeSubnodeNamehash(baseNameNode, label); await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); @@ -115,7 +119,7 @@ export const makeRegistryHandlers = () => { await upsertAccount(context, to); const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); + const node = makeSubnodeNamehash(baseNameNode, label); const registration = await context.db.find(registrations, { id: label }); if (!registration) return; diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index 35ea1ab2..810c2310 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -2,7 +2,7 @@ import { type Context, type Event } from "ponder:registry"; import { resolvers } from "ponder:schema"; import { domains } from "ponder:schema"; import { type Hex, zeroAddress } from "viem"; -import { NAMEHASH_ZERO, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; +import { ROOT_NODE, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; import { makeResolverId } from "../lib/ids"; import { NsReturnType } from "../lib/plugins"; import { upsertAccount } from "../lib/upserts"; @@ -15,7 +15,7 @@ export async function setup({ context }: { context: Context }) { // ensure we have a root Domain, owned by the zeroAddress, that is default not migrated await context.db.insert(domains).values({ - id: NAMEHASH_ZERO, + id: ROOT_NODE, ownerId: zeroAddress, createdAt: 0n, isMigrated: false, diff --git a/src/lib/ens-helpers.ts b/src/lib/ens-helpers.ts index bdd7558e..e60a5662 100644 --- a/src/lib/ens-helpers.ts +++ b/src/lib/ens-helpers.ts @@ -1,11 +1,26 @@ import { type Hex, concat, keccak256, namehash, toHex } from "viem"; -// TODO: add input validation -export const ENS_ROOT_DOMAIN_NAME = process.env.INDEX_ENS_ROOT_NODE! as `.${string}.eth`; +class BaseName { + private constructor(private readonly name: `.${string}eth`) {} + + static parse(name: string | undefined = "") { + if (!name.startsWith(".")) throw new Error(`BASE NAME should start with '.'`); + + if (!name.endsWith(".eth")) throw new Error(`BASE NAME should end with '.eth'`); + + return new BaseName(name as `.${string}eth`); + } + + toString() { + return this.name; + } +} + +export const BASENAME = BaseName.parse(process.env.INDEX_BASENAME).toString(); // TODO: pull from ens utils lib or something -export const NAMEHASH_ZERO = namehash(""); -export const NAMEHASH_ROOT = namehash(ENS_ROOT_DOMAIN_NAME.slice(1)); +export const ROOT_NODE = namehash(""); +export const BASENAME_NODE = namehash(BASENAME.slice(1)); // TODO: this should probably be a part of some ens util lib export const makeSubnodeNamehash = (node: Hex, label: Hex) => keccak256(concat([node, label])); diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index 8bd3a39c..fab6eaec 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -1,9 +1,9 @@ import { ponder } from "ponder:registry"; import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { NAMEHASH_ROOT, makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; +import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; import { upsertAccount } from "../../../lib/upserts"; -import { ns } from "../ponder.config"; +import { baseName, ns } from "../ponder.config"; const { handleNameRegistered, @@ -11,7 +11,8 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(); + baseNameNode, +} = makeRegistryHandlers(baseName); export default function () { // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers @@ -27,7 +28,7 @@ export default function () { // allowing the base indexer to progress. const { id, owner } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(NAMEHASH_ROOT, label); + const node = makeSubnodeNamehash(baseNameNode, label); await upsertAccount(context, owner); await context.db .insert(domains) diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index 641cd998..aa4fe16f 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -9,9 +9,11 @@ import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -const rootDomain = "/eth/base" as const; -export const ns = createNs(rootDomain); -export type NsType = NsReturnType; +export const baseName = ".base.eth" as const; + +const nestedNamespace = "/eth/base" as const; +export const ns = createNs(nestedNamespace); +export type NsType = NsReturnType; const START_BLOCK = undefined; // 17607350; const END_BLOCK = undefined; // 17607351; diff --git a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts index a39cb243..4a771386 100644 --- a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts @@ -1,6 +1,6 @@ import { ponder } from "ponder:registry"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { ns } from "../ponder.config"; +import { baseName, ns } from "../ponder.config"; const { handleNameRegistered, @@ -8,7 +8,7 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(); +} = makeRegistryHandlers(baseName); export default function () { ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index 30311437..7e979123 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -12,12 +12,11 @@ import { Resolver } from "./abis/Resolver"; const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); -const REGISTRY_OLD_ADDRESS = "0x314159265dd8dbb310642f98f50c066173c1259b"; -const REGISTRY_ADDRESS = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; +export const baseName = ".eth" as const; -const rootDomain = "/eth" as const; -export const ns = createNs(rootDomain); -export type NsType = NsReturnType; +const nestedNamespace = "/eth" as const; +export const ns = createNs(nestedNamespace); +export type NsType = NsReturnType; export const config = createConfig({ networks: { From e5d7541d9c182b725420a36229415c55734cd6f2 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Mon, 6 Jan 2025 12:18:57 +0100 Subject: [PATCH 18/30] docs(base.eth): update readme --- src/ponder-ens-plugins/eth.base/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ponder-ens-plugins/eth.base/README.md b/src/ponder-ens-plugins/eth.base/README.md index 9af78654..1f56a46c 100644 --- a/src/ponder-ens-plugins/eth.base/README.md +++ b/src/ponder-ens-plugins/eth.base/README.md @@ -1,9 +1,9 @@ -# Base ENS plugin for Ponder indexer +# 'base.eth' plugin for Ponder indexer This plugin contains configuration required to run blockchain indexing with [the Ponder app](https://ponder.sh/). It includes relevant ABI files, contract addresses and the block numbers those contracts were deployed at on a selected network. ## Architecture -The Base implementation for ENS protocol has custom naming convention, which was described here: +The Basenames contracts manage subnames of "base.eth". All contracts and their interactions were described here: https://github.com/base-org/basenames?tab=readme-ov-file#architecture From 067f57030236542e15c5557195c65e8911e6c771 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 7 Jan 2025 20:29:34 +0100 Subject: [PATCH 19/30] refactor(ponder-plugins): simplify architecture The generic handlers have now the inlined args types to avoid importing subname-specifc event types. Also, we now use the `INDEX_SUBNAME` env var to specify the subname to index, i.e. `eth` or `base.eth` (note: no starting dot character). --- .env.local.example | 4 +- package.json | 2 +- pnpm-lock.yaml | 10 +- ponder.config.ts | 28 ++--- src/handlers/Registrar.ts | 41 ++++--- src/handlers/Registry.ts | 30 +++-- src/handlers/Resolver.ts | 110 ++++++++++++------ src/lib/ens-helpers.ts | 13 +-- src/lib/plugins.ts | 18 --- src/lib/ponder-plugin-utils.ts | 64 ++++++++++ .../eth.base/handlers/Registrar.ts | 22 ++-- .../eth.base/handlers/Registry.ts | 14 +-- .../eth.base/handlers/Resolver.ts | 2 +- .../eth.base/ponder.config.ts | 36 +++--- .../eth/handlers/EthRegistrar.ts | 55 +++++---- .../eth/handlers/Registry.ts | 22 ++-- .../eth/handlers/Resolver.ts | 63 +++++----- src/ponder-ens-plugins/eth/ponder.config.ts | 24 ++-- 18 files changed, 331 insertions(+), 227 deletions(-) delete mode 100644 src/lib/plugins.ts create mode 100644 src/lib/ponder-plugin-utils.ts diff --git a/.env.local.example b/.env.local.example index f39ea2b0..65690ce7 100644 --- a/.env.local.example +++ b/.env.local.example @@ -6,10 +6,10 @@ PONDER_RPC_URL_59144=https://linea-rpc.publicnode.com # ponder indexer ens deployment configuration -INDEX_BASENAME=.base.eth +INDEX_SUBNAME=base.eth # ponder indexer database configuration -## one schema name per chain ID, i.e. ponder_ens_${INDEX_BASENAME} +## one schema name per chain ID, i.e. ponder_ens_${INDEX_SUBNAME} DATABASE_SCHEMA=ponder_ens_base.eth DATABASE_URL=postgresql://dbuser:abcd1234@localhost:5432/my_database diff --git a/package.json b/package.json index fa393d41..ffa63380 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "hono": "^4.6.14", - "ponder": "^0.8.13", + "ponder": "^0.8.17", "viem": "^2.21.57" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4953c80..88e44b13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^4.6.14 version: 4.6.14 ponder: - specifier: ^0.8.13 - version: 0.8.13(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)) + specifier: ^0.8.17 + version: 0.8.17(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)) viem: specifier: ^2.21.57 version: 2.21.57(typescript@5.7.2) @@ -1381,8 +1381,8 @@ packages: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} hasBin: true - ponder@0.8.13: - resolution: {integrity: sha512-knA9W3gYQCbGrCfS8wMIhK7PC6xH9bf9qU+DfQRPRRmFP6V4UL/rqoOtPETYuIm04F9Ljowhc3bGOvFp/lPc3g==} + ponder@0.8.17: + resolution: {integrity: sha512-p0gvs0CJpdJ6sf5OOQYXaIfmIeUVoTMkCbPVAJ1jK1O2m62ZnTlxpnGrPp5ZAWYxdlCSQQCpZpNhdsYGejGK+g==} engines: {node: '>=18.14'} hasBin: true peerDependencies: @@ -2934,7 +2934,7 @@ snapshots: sonic-boom: 3.8.1 thread-stream: 2.7.0 - ponder@0.8.13(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)): + ponder@0.8.17(@opentelemetry/api@1.9.0)(@types/node@20.17.10)(hono@4.6.14)(typescript@5.7.2)(viem@2.21.57(typescript@5.7.2)): dependencies: '@babel/code-frame': 7.26.2 '@commander-js/extra-typings': 12.1.0(commander@12.1.0) diff --git a/ponder.config.ts b/ponder.config.ts index 24e6dd33..24492f24 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,29 +1,29 @@ -import { BASENAME } from "./src/lib/ens-helpers"; +import { INDEXED_SUBNAME } from "./src/lib/ens-helpers"; import { - activate as activateBase, - baseName as baseBaseName, - config as baseConfig + activate as activateEthBase, + config as ethBaseConfig, + indexedSubname as ethBaseIndexedSubname, } from "./src/ponder-ens-plugins/eth.base/ponder.config"; import { activate as activateEth, - baseName as ethBaseName, - config as ethereumConfig + config as ethConfig, + indexedSubname as ethIndexedSubname, } from "./src/ponder-ens-plugins/eth/ponder.config"; -type AllConfigs = typeof ethereumConfig & typeof baseConfig; +type AllConfigs = typeof ethConfig & typeof ethBaseConfig; // here we export only a single 'plugin's config, by type it as every config // this makes all of the mapping types happy at typecheck-time, but only the relevant // config is run at runtime export default ((): AllConfigs => { - switch (BASENAME) { - case ethBaseName: + switch (INDEXED_SUBNAME) { + case ethIndexedSubname: activateEth(); - return ethereumConfig as AllConfigs; - case baseBaseName: - activateBase(); - return baseConfig as AllConfigs; + return ethConfig as AllConfigs; + case ethBaseIndexedSubname: + activateEthBase(); + return ethBaseConfig as AllConfigs; default: - throw new Error(`Unsupported base name: ${BASENAME}`); + throw new Error(`Unsupported base name: ${INDEXED_SUBNAME}`); } })(); diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index 654493b9..cf2e8ca6 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -1,36 +1,36 @@ -import { type Context, type Event } from "ponder:registry"; +import { type Context } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; import { type Hex, namehash } from "viem"; import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; -import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertRegistration } from "../lib/upserts"; -type NsType = NsReturnType; - const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds -export const makeRegistryHandlers = (baseName: `.${`${string}`}eth`) => { - const baseNameNode = namehash(baseName.slice(1)); +/** + * A factory function that returns Ponder indexing handlers for a specified index name/subname. + */ +export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { + const indexedSubnameNode = namehash(indexedSubname); async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; - const node = makeSubnodeNamehash(baseNameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { await context.db .update(domains, { id: node }) - .set({ labelName: name, name: `${name}${baseName}` }); + .set({ labelName: name, name: `${name}${indexedSubname}` }); } await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); } return { - get baseNameNode() { - return baseNameNode; + get indexedSubnameNode() { + return indexedSubnameNode; }, async handleNameRegistered({ @@ -38,14 +38,17 @@ export const makeRegistryHandlers = (baseName: `.${`${string}`}eth`) => { event, }: { context: Context; - event: Omit>, "name">; + event: { + block: { timestamp: bigint }; + args: { id: bigint; owner: Hex; expires: bigint }; + }; }) { const { id, owner, expires } = event.args; await upsertAccount(context, owner); const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(baseNameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); // TODO: materialze labelName via rainbow tables ala Registry.ts const labelName = undefined; @@ -93,12 +96,14 @@ export const makeRegistryHandlers = (baseName: `.${`${string}`}eth`) => { event, }: { context: Context; - event: Event>; + event: { + args: { id: bigint; expires: bigint }; + }; }) { const { id, expires } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(baseNameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); @@ -114,12 +119,16 @@ export const makeRegistryHandlers = (baseName: `.${`${string}`}eth`) => { args: { tokenId, from, to }, }: { context: Context; - args: Event>["args"]; + args: { + tokenId: bigint; + from: Hex; + to: Hex; + }; }) { await upsertAccount(context, to); const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(baseNameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); const registration = await context.db.find(registrations, { id: label }); if (!registration) return; diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index 810c2310..fb15b83d 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -1,19 +1,19 @@ -import { type Context, type Event } from "ponder:registry"; +import { type Context } from "ponder:registry"; import { resolvers } from "ponder:schema"; import { domains } from "ponder:schema"; import { type Hex, zeroAddress } from "viem"; import { ROOT_NODE, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; import { makeResolverId } from "../lib/ids"; -import { NsReturnType } from "../lib/plugins"; import { upsertAccount } from "../lib/upserts"; -type NsType = NsReturnType; - -export async function setup({ context }: { context: Context }) { +/** + * Initialize the ENS root node with the zeroAddress as the owner. + */ +export async function handleRootNodeCreation({ context }: { context: Context }) { // ensure we have an account for the zeroAddress await upsertAccount(context, zeroAddress); - // ensure we have a root Domain, owned by the zeroAddress, that is default not migrated + // initialize the ENS root to be owned by the zeroAddress and not migrated await context.db.insert(domains).values({ id: ROOT_NODE, ownerId: zeroAddress, @@ -50,7 +50,10 @@ export async function handleTransfer({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; owner: Hex }; + block: { timestamp: bigint }; + }; }) { const { node, owner } = event.args; @@ -78,7 +81,10 @@ export const handleNewOwner = event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; label: Hex; owner: Hex }; + block: { timestamp: bigint }; + }; }) => { const { label, node, owner } = event.args; @@ -131,7 +137,9 @@ export async function handleNewTTL({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; ttl: bigint }; + }; }) { const { node, ttl } = event.args; @@ -148,7 +156,9 @@ export async function handleNewResolver({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; resolver: Hex }; + }; }) { const { node, resolver: resolverAddress } = event.args; diff --git a/src/handlers/Resolver.ts b/src/handlers/Resolver.ts index 138728bc..3c355f88 100644 --- a/src/handlers/Resolver.ts +++ b/src/handlers/Resolver.ts @@ -1,37 +1,19 @@ import { type Context, type Event } from "ponder:registry"; import { domains, resolvers } from "ponder:schema"; -import { base, mainnet } from "viem/chains"; +import { Hex } from "viem"; import { hasNullByte, uniq } from "../lib/helpers"; import { makeResolverId } from "../lib/ids"; -import { NsReturnType } from "../lib/plugins"; import { upsertAccount, upsertResolver } from "../lib/upserts"; -type NsType = NsReturnType; - -// there is a legacy resolver abi with different TextChanged events. -// luckily the subgraph doesn't care about the value parameter so we can use a union -// to unify the codepath -type AnyTextChangedEvent = - | Event< - NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> - > - | Event< - NsType<"Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> - > - | Event> - | Event< - NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"> - > - | Event< - NsType<"OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)"> - >; - export async function handleAddrChanged({ context, event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; a: Hex }; + log: { address: Hex }; + }; }) { const { a: address, node } = event.args; await upsertAccount(context, address); @@ -58,7 +40,10 @@ export async function handleAddressChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; coinType: bigint; newAddress: Hex }; + log: { address: Hex }; + }; }) { const { node, coinType, newAddress } = event.args; await upsertAccount(context, newAddress); @@ -83,7 +68,10 @@ export async function handleNameChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; name: string }; + log: { address: Hex }; + }; }) { const { node, name } = event.args; if (hasNullByte(name)) return; @@ -103,7 +91,10 @@ export async function handleABIChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex }; + log: { address: Hex }; + }; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -121,7 +112,10 @@ export async function handlePubkeyChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex }; + log: { address: Hex }; + }; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -139,7 +133,10 @@ export async function handleTextChanged({ event, }: { context: Context; - event: AnyTextChangedEvent; + event: { + args: { node: Hex; indexedKey: string; key: string; value?: string }; + log: { address: Hex }; + }; }) { const { node, key } = event.args; const id = makeResolverId(node, event.log.address); @@ -160,7 +157,10 @@ export async function handleContenthashChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { node: Hex; hash: Hex }; + log: { address: Hex }; + }; }) { const { node, hash } = event.args; const id = makeResolverId(node, event.log.address); @@ -179,7 +179,14 @@ export async function handleInterfaceChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { + node: Hex; + interfaceID: Hex; + implementer: Hex; + }; + log: { address: Hex }; + }; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -197,7 +204,15 @@ export async function handleAuthorisationChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { + node: Hex; + owner: Hex; + target: Hex; + isAuthorised: boolean; + }; + log: { address: Hex }; + }; }) { const { node } = event.args; const id = makeResolverId(node, event.log.address); @@ -215,7 +230,13 @@ export async function handleVersionChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { + node: Hex; + newVersion: bigint; + }; + log: { address: Hex }; + }; }) { // a version change nulls out the resolver const { node } = event.args; @@ -244,7 +265,14 @@ export async function handleDNSRecordChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { + node: Hex; + name: Hex; + resource: number; + record: Hex; + }; + }; }) { // subgraph ignores } @@ -254,7 +282,14 @@ export async function handleDNSRecordDeleted({ event, }: { context: Context; - event: Event>; + event: { + args: { + node: Hex; + name: Hex; + resource: number; + record?: Hex; + }; + }; }) { // subgraph ignores } @@ -264,7 +299,12 @@ export async function handleDNSZonehashChanged({ event, }: { context: Context; - event: Event>; + event: { + args: { + node: Hex; + zonehash: Hex; + }; + }; }) { // subgraph ignores } diff --git a/src/lib/ens-helpers.ts b/src/lib/ens-helpers.ts index e60a5662..21a4f156 100644 --- a/src/lib/ens-helpers.ts +++ b/src/lib/ens-helpers.ts @@ -1,14 +1,12 @@ import { type Hex, concat, keccak256, namehash, toHex } from "viem"; -class BaseName { - private constructor(private readonly name: `.${string}eth`) {} +class IndexedSubname { + private constructor(private readonly name: `${string}eth`) {} static parse(name: string | undefined = "") { - if (!name.startsWith(".")) throw new Error(`BASE NAME should start with '.'`); + if (!name.endsWith("eth")) throw new Error(`IndexedSubname should end with 'eth'`); - if (!name.endsWith(".eth")) throw new Error(`BASE NAME should end with '.eth'`); - - return new BaseName(name as `.${string}eth`); + return new IndexedSubname(name as `${string}eth`); } toString() { @@ -16,11 +14,10 @@ class BaseName { } } -export const BASENAME = BaseName.parse(process.env.INDEX_BASENAME).toString(); +export const INDEXED_SUBNAME = IndexedSubname.parse(process.env.INDEX_SUBNAME).toString(); // TODO: pull from ens utils lib or something export const ROOT_NODE = namehash(""); -export const BASENAME_NODE = namehash(BASENAME.slice(1)); // TODO: this should probably be a part of some ens util lib export const makeSubnodeNamehash = (node: Hex, label: Hex) => keccak256(concat([node, label])); diff --git a/src/lib/plugins.ts b/src/lib/plugins.ts deleted file mode 100644 index bab5ee99..00000000 --- a/src/lib/plugins.ts +++ /dev/null @@ -1,18 +0,0 @@ -// TODO: change the chainId to the root node value (i.e. namehash("base.eth")) -export function createNs(domain: Domain) { - /** Creates a name-spaced contract name */ - return function ns( - contractName: ContractName, - ): NsReturnType { - return `${domain}/${contractName}` as NsReturnType; - }; -} - -export type NsReturnType< - ContractName extends string, - Domain extends EnsRootDomain, -> = `${Domain}/${ContractName}`; - -export const EnsRootDomains = ["/eth", "/eth/base"] as const; - -export type EnsRootDomain = (typeof EnsRootDomains)[number]; diff --git a/src/lib/ponder-plugin-utils.ts b/src/lib/ponder-plugin-utils.ts new file mode 100644 index 00000000..dc3618e4 --- /dev/null +++ b/src/lib/ponder-plugin-utils.ts @@ -0,0 +1,64 @@ +/** + * A factory function that returns a function to create a name-spaced contract name for Ponder indexing handlers. + * + * Ponder config requires a flat dictionary of contract config entires, where each entry has its unique name and set of EVM event names derived from the contract's ABI. + * Ponder will use contract names and their respective event names to create names for indexing handlers. + * For example, a contract named `Registry` includes events: `NewResolver` and `NewTTL`. Ponder will create indexing handlers named `Registry:NewResolver` and `Registry:NewTTL`. + * + * However, in some cases, we may want to create a name-spaced contract name to distinguish between contracts having the same name, but handling different implementations. + * + * Let's say we have two contracts named `Registry`. One handles the `eth` name, and the other handles the `base.eth` subname. We need to create a name-spaced contract name to avoid conflicts. + * We could use the actual name/subname as a prefix, like `eth/Registry` and `base.eth/Registry`. We cannot do that, though, as Ponder does not support dots and colons in its indexing handler names. + * + * We need to use a different separator, in this case, a forward slash in a path-like format. + * + * @param subname + * + * @example + * ```ts + * const ethNs = createPonderNamespace("base.eth"); + * const baseEthNs = createPonderNamespace("base.eth"); + * + * ethNs("Registry"); // returns "/eth/Registry" + * baseEthNs("Registry"); // returns "/base/eth/Registry" + * ``` + */ +export function createPonderNamespace(subname: Subname) { + const path = transformDomain(subname) satisfies PonderNsPath; + + /** Creates a name-spaced contract name */ + return function ponderNamespace( + contractName: ContractName, + ): PonderNamespaceReturnType { + return `${path}/${contractName}`; + }; +} + +type TransformDomain = T extends `${infer Sub}.${infer Rest}` + ? `/${TransformDomain}/${Sub}` + : `/${T}`; + +/** + * Transforms a domain name into a path-like format + * + * @param domain + * @returns path-like format of the reversed domain + * + * @example + * ```ts + * transformDomain("base.eth"); // returns "/eth/base" + **/ +function transformDomain(domain: T): TransformDomain { + const parts = domain.split(".").reverse(); + return `/${parts.join("/")}` as TransformDomain; +} + +/** The return type of the `ponderNamespace` function */ +export type PonderNamespaceReturnType< + ContractName extends string, + NsPath extends PonderNsPath, +> = `${NsPath}/${ContractName}`; + +type PonderNsPath = `` | `/${string}` | `/${string}${T}`; + +type EthSubname = `${string}eth`; diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index fab6eaec..54bac89c 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -3,7 +3,7 @@ import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; import { upsertAccount } from "../../../lib/upserts"; -import { baseName, ns } from "../ponder.config"; +import { indexedSubname, ponderNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -11,16 +11,16 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, - baseNameNode, -} = makeRegistryHandlers(baseName); + indexedSubnameNode, +} = makeRegistryHandlers(indexedSubname); export default function () { // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers - ponder.on(ns("BaseRegistrar:NameRegisteredWithRecord"), async ({ context, event }) => + ponder.on(ponderNamespace("BaseRegistrar:NameRegisteredWithRecord"), async ({ context, event }) => handleNameRegistered({ context, event }), ); - ponder.on(ns("BaseRegistrar:NameRegistered"), async ({ context, event }) => { + ponder.on(ponderNamespace("BaseRegistrar:NameRegistered"), async ({ context, event }) => { // base has 'preminted' names via Registrar#registerOnly, which explicitly does not update Registry. // this breaks a subgraph assumption, as it expects a domain to exist (via Registry:NewOwner) before // any Registrar:NameRegistered events. in the future we will likely happily upsert domains, but @@ -28,7 +28,7 @@ export default function () { // allowing the base indexer to progress. const { id, owner } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(baseNameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); await upsertAccount(context, owner); await context.db .insert(domains) @@ -42,17 +42,17 @@ export default function () { // after ensuring the domain exists, continue with the standard handler return handleNameRegistered({ context, event }); }); - ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); + ponder.on(ponderNamespace("BaseRegistrar:NameRenewed"), handleNameRenewed); // Base's BaseRegistrar uses `id` instead of `tokenId` - ponder.on(ns("BaseRegistrar:Transfer"), async ({ context, event }) => { + ponder.on(ponderNamespace("BaseRegistrar:Transfer"), async ({ context, event }) => { return await handleNameTransferred({ context, args: { ...event.args, tokenId: event.args.id }, }); }); - ponder.on(ns("EARegistrarController:NameRegistered"), async ({ context, event }) => { + ponder.on(ponderNamespace("EARegistrarController:NameRegistered"), async ({ context, event }) => { // TODO: registration expected here return handleNameRegisteredByController({ @@ -61,7 +61,7 @@ export default function () { }); }); - ponder.on(ns("RegistrarController:NameRegistered"), async ({ context, event }) => { + ponder.on(ponderNamespace("RegistrarController:NameRegistered"), async ({ context, event }) => { // TODO: registration expected here return handleNameRegisteredByController({ @@ -70,7 +70,7 @@ export default function () { }); }); - ponder.on(ns("RegistrarController:NameRenewed"), async ({ context, event }) => { + ponder.on(ponderNamespace("RegistrarController:NameRenewed"), async ({ context, event }) => { return handleNameRenewedByController({ context, args: { ...event.args, cost: 0n }, diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts index 00008e02..0b6c8b6a 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts @@ -3,15 +3,15 @@ import { handleNewOwner, handleNewResolver, handleNewTTL, + handleRootNodeCreation, handleTransfer, - setup, } from "../../../handlers/Registry"; -import { ns } from "../ponder.config"; +import { ponderNamespace } from "../ponder.config"; export default function () { - ponder.on(ns("Registry:setup"), setup); - ponder.on(ns("Registry:NewOwner"), handleNewOwner(true)); - ponder.on(ns("Registry:NewResolver"), handleNewResolver); - ponder.on(ns("Registry:NewTTL"), handleNewTTL); - ponder.on(ns("Registry:Transfer"), handleTransfer); + ponder.on(ponderNamespace("Registry:setup"), handleRootNodeCreation); + ponder.on(ponderNamespace("Registry:NewOwner"), handleNewOwner(true)); + ponder.on(ponderNamespace("Registry:NewResolver"), handleNewResolver); + ponder.on(ponderNamespace("Registry:NewTTL"), handleNewTTL); + ponder.on(ponderNamespace("Registry:Transfer"), handleTransfer); } diff --git a/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts index 6395268c..2fdba189 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts @@ -13,7 +13,7 @@ import { handleTextChanged, handleVersionChanged, } from "../../../handlers/Resolver"; -import { ns } from "../ponder.config"; +import { ponderNamespace as ns } from "../ponder.config"; export default function () { // New registry handlers diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index aa4fe16f..230ac129 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -2,21 +2,16 @@ import { createConfig, factory } from "ponder"; import { http, getAbiItem } from "viem"; import { base } from "viem/chains"; -import { NsReturnType, createNs } from "../../lib/plugins"; +import { createPonderNamespace } from "../../lib/ponder-plugin-utils"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -export const baseName = ".base.eth" as const; +export const indexedSubname = "base.eth" as const; -const nestedNamespace = "/eth/base" as const; -export const ns = createNs(nestedNamespace); -export type NsType = NsReturnType; - -const START_BLOCK = undefined; // 17607350; -const END_BLOCK = undefined; // 17607351; +export const ponderNamespace = createPonderNamespace(indexedSubname); export const config = createConfig({ networks: { @@ -26,14 +21,13 @@ export const config = createConfig({ }, }, contracts: { - [ns("Registry")]: { + [ponderNamespace("Registry")]: { network: "base", abi: Registry, address: "0xb94704422c2a1e396835a571837aa5ae53285a95", - startBlock: START_BLOCK || 17571480, - endBlock: END_BLOCK, + startBlock: 17571480, }, - [ns("Resolver")]: { + [ponderNamespace("Resolver")]: { network: "base", abi: L2Resolver, address: factory({ @@ -41,29 +35,25 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - startBlock: START_BLOCK || 17575714, - endBlock: END_BLOCK, + startBlock: 17575714, }, - [ns("BaseRegistrar")]: { + [ponderNamespace("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, address: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a", - startBlock: START_BLOCK || 17571486, - endBlock: END_BLOCK, + startBlock: 17571486, }, - [ns("EARegistrarController")]: { + [ponderNamespace("EARegistrarController")]: { network: "base", abi: EarlyAccessRegistrarController, address: "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda", - startBlock: START_BLOCK || 17575699, - endBlock: END_BLOCK, + startBlock: 17575699, }, - [ns("RegistrarController")]: { + [ponderNamespace("RegistrarController")]: { network: "base", abi: RegistrarController, address: "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5", - startBlock: START_BLOCK || 18619035, - endBlock: END_BLOCK, + startBlock: 18619035, }, }, }); diff --git a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts index 4a771386..76a651ba 100644 --- a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts @@ -1,6 +1,6 @@ import { ponder } from "ponder:registry"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { baseName, ns } from "../ponder.config"; +import { indexedSubname, ponderNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -8,35 +8,44 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(baseName); +} = makeRegistryHandlers(indexedSubname); export default function () { - ponder.on(ns("BaseRegistrar:NameRegistered"), handleNameRegistered); - ponder.on(ns("BaseRegistrar:NameRenewed"), handleNameRenewed); + ponder.on(ponderNamespace("BaseRegistrar:NameRegistered"), handleNameRegistered); + ponder.on(ponderNamespace("BaseRegistrar:NameRenewed"), handleNameRenewed); - ponder.on(ns("BaseRegistrar:Transfer"), async ({ context, event }) => { + ponder.on(ponderNamespace("BaseRegistrar:Transfer"), async ({ context, event }) => { return await handleNameTransferred({ context, args: event.args }); }); - ponder.on(ns("EthRegistrarControllerOld:NameRegistered"), async ({ context, event }) => { - // the old registrar controller just had `cost` param - return await handleNameRegisteredByController({ context, args: event.args }); - }); - ponder.on(ns("EthRegistrarControllerOld:NameRenewed"), async ({ context, event }) => { - return await handleNameRenewedByController({ context, args: event.args }); - }); + ponder.on( + ponderNamespace("EthRegistrarControllerOld:NameRegistered"), + async ({ context, event }) => { + // the old registrar controller just had `cost` param + return await handleNameRegisteredByController({ context, args: event.args }); + }, + ); + ponder.on( + ponderNamespace("EthRegistrarControllerOld:NameRenewed"), + async ({ context, event }) => { + return await handleNameRenewedByController({ context, args: event.args }); + }, + ); - ponder.on(ns("EthRegistrarController:NameRegistered"), async ({ context, event }) => { - // the new registrar controller uses baseCost + premium to compute cost - return await handleNameRegisteredByController({ - context, - args: { - ...event.args, - cost: event.args.baseCost + event.args.premium, - }, - }); - }); - ponder.on(ns("EthRegistrarController:NameRenewed"), async ({ context, event }) => { + ponder.on( + ponderNamespace("EthRegistrarController:NameRegistered"), + async ({ context, event }) => { + // the new registrar controller uses baseCost + premium to compute cost + return await handleNameRegisteredByController({ + context, + args: { + ...event.args, + cost: event.args.baseCost + event.args.premium, + }, + }); + }, + ); + ponder.on(ponderNamespace("EthRegistrarController:NameRenewed"), async ({ context, event }) => { return await handleNameRenewedByController({ context, args: event.args }); }); } diff --git a/src/ponder-ens-plugins/eth/handlers/Registry.ts b/src/ponder-ens-plugins/eth/handlers/Registry.ts index 68559411..8f1a92ea 100644 --- a/src/ponder-ens-plugins/eth/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth/handlers/Registry.ts @@ -5,11 +5,11 @@ import { handleNewOwner, handleNewResolver, handleNewTTL, + handleRootNodeCreation, handleTransfer, - setup, } from "../../../handlers/Registry"; import { makeSubnodeNamehash } from "../../../lib/ens-helpers"; -import { ns } from "../ponder.config"; +import { ponderNamespace } from "../ponder.config"; // a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not async function isDomainMigrated(context: Context, node: Hex) { @@ -18,18 +18,18 @@ async function isDomainMigrated(context: Context, node: Hex) { } export default function () { - ponder.on(ns("RegistryOld:setup"), setup); + ponder.on(ponderNamespace("RegistryOld:setup"), handleRootNodeCreation); // old registry functions are proxied to the current handlers // iff the domain has not yet been migrated - ponder.on(ns("RegistryOld:NewOwner"), async ({ context, event }) => { + ponder.on(ponderNamespace("RegistryOld:NewOwner"), async ({ context, event }) => { const node = makeSubnodeNamehash(event.args.node, event.args.label); const isMigrated = await isDomainMigrated(context, node); if (isMigrated) return; return handleNewOwner(false)({ context, event }); }); - ponder.on(ns("RegistryOld:NewResolver"), async ({ context, event }) => { + ponder.on(ponderNamespace("RegistryOld:NewResolver"), async ({ context, event }) => { // NOTE: the subgraph makes an exception for the root node here // but i don't know that that's necessary, as in ponder our root node starts out // unmigrated and once the NewOwner event is emitted by the new registry, @@ -42,20 +42,20 @@ export default function () { return handleNewResolver({ context, event }); }); - ponder.on(ns("RegistryOld:NewTTL"), async ({ context, event }) => { + ponder.on(ponderNamespace("RegistryOld:NewTTL"), async ({ context, event }) => { const isMigrated = await isDomainMigrated(context, event.args.node); if (isMigrated) return; return handleNewTTL({ context, event }); }); - ponder.on(ns("RegistryOld:Transfer"), async ({ context, event }) => { + ponder.on(ponderNamespace("RegistryOld:Transfer"), async ({ context, event }) => { const isMigrated = await isDomainMigrated(context, event.args.node); if (isMigrated) return; return handleTransfer({ context, event }); }); - ponder.on(ns("Registry:NewOwner"), handleNewOwner(true)); - ponder.on(ns("Registry:NewResolver"), handleNewResolver); - ponder.on(ns("Registry:NewTTL"), handleNewTTL); - ponder.on(ns("Registry:Transfer"), handleTransfer); + ponder.on(ponderNamespace("Registry:NewOwner"), handleNewOwner(true)); + ponder.on(ponderNamespace("Registry:NewResolver"), handleNewResolver); + ponder.on(ponderNamespace("Registry:NewTTL"), handleNewTTL); + ponder.on(ponderNamespace("Registry:Transfer"), handleTransfer); } diff --git a/src/ponder-ens-plugins/eth/handlers/Resolver.ts b/src/ponder-ens-plugins/eth/handlers/Resolver.ts index 369cab64..122eb7d0 100644 --- a/src/ponder-ens-plugins/eth/handlers/Resolver.ts +++ b/src/ponder-ens-plugins/eth/handlers/Resolver.ts @@ -14,56 +14,61 @@ import { handleTextChanged, handleVersionChanged, } from "../../../handlers/Resolver"; -import { ns } from "../ponder.config"; +import { ponderNamespace } from "../ponder.config"; export default function () { // Old registry handlers - ponder.on(ns("OldRegistryResolvers:AddrChanged"), handleAddrChanged); - ponder.on(ns("OldRegistryResolvers:AddressChanged"), handleAddressChanged); - ponder.on(ns("OldRegistryResolvers:NameChanged"), handleNameChanged); - ponder.on(ns("OldRegistryResolvers:ABIChanged"), handleABIChanged); - ponder.on(ns("OldRegistryResolvers:PubkeyChanged"), handlePubkeyChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:AddrChanged"), handleAddrChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:AddressChanged"), handleAddressChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:NameChanged"), handleNameChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:ABIChanged"), handleABIChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:PubkeyChanged"), handlePubkeyChanged); ponder.on( - ns( + ponderNamespace( "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", ), handleTextChanged, ); ponder.on( - ns( + ponderNamespace( "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", ), handleTextChanged, ); - ponder.on(ns("OldRegistryResolvers:ContenthashChanged"), handleContenthashChanged); - ponder.on(ns("OldRegistryResolvers:InterfaceChanged"), handleInterfaceChanged); - ponder.on(ns("OldRegistryResolvers:AuthorisationChanged"), handleAuthorisationChanged); - ponder.on(ns("OldRegistryResolvers:VersionChanged"), handleVersionChanged); - ponder.on(ns("OldRegistryResolvers:DNSRecordChanged"), handleDNSRecordChanged); - ponder.on(ns("OldRegistryResolvers:DNSRecordDeleted"), handleDNSRecordDeleted); - ponder.on(ns("OldRegistryResolvers:DNSZonehashChanged"), handleDNSZonehashChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:ContenthashChanged"), handleContenthashChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:InterfaceChanged"), handleInterfaceChanged); + ponder.on( + ponderNamespace("OldRegistryResolvers:AuthorisationChanged"), + handleAuthorisationChanged, + ); + ponder.on(ponderNamespace("OldRegistryResolvers:VersionChanged"), handleVersionChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:DNSRecordChanged"), handleDNSRecordChanged); + ponder.on(ponderNamespace("OldRegistryResolvers:DNSRecordDeleted"), handleDNSRecordDeleted); + ponder.on(ponderNamespace("OldRegistryResolvers:DNSZonehashChanged"), handleDNSZonehashChanged); // New registry handlers - ponder.on(ns("Resolver:AddrChanged"), handleAddrChanged); - ponder.on(ns("Resolver:AddressChanged"), handleAddressChanged); - ponder.on(ns("Resolver:NameChanged"), handleNameChanged); - ponder.on(ns("Resolver:ABIChanged"), handleABIChanged); - ponder.on(ns("Resolver:PubkeyChanged"), handlePubkeyChanged); + ponder.on(ponderNamespace("Resolver:AddrChanged"), handleAddrChanged); + ponder.on(ponderNamespace("Resolver:AddressChanged"), handleAddressChanged); + ponder.on(ponderNamespace("Resolver:NameChanged"), handleNameChanged); + ponder.on(ponderNamespace("Resolver:ABIChanged"), handleABIChanged); + ponder.on(ponderNamespace("Resolver:PubkeyChanged"), handlePubkeyChanged); ponder.on( - ns("Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)"), + ponderNamespace( + "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", + ), handleTextChanged, ); ponder.on( - ns( + ponderNamespace( "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", ), handleTextChanged, ); - ponder.on(ns("Resolver:ContenthashChanged"), handleContenthashChanged); - ponder.on(ns("Resolver:InterfaceChanged"), handleInterfaceChanged); - ponder.on(ns("Resolver:AuthorisationChanged"), handleAuthorisationChanged); - ponder.on(ns("Resolver:VersionChanged"), handleVersionChanged); - ponder.on(ns("Resolver:DNSRecordChanged"), handleDNSRecordChanged); - ponder.on(ns("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); - ponder.on(ns("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); + ponder.on(ponderNamespace("Resolver:ContenthashChanged"), handleContenthashChanged); + ponder.on(ponderNamespace("Resolver:InterfaceChanged"), handleInterfaceChanged); + ponder.on(ponderNamespace("Resolver:AuthorisationChanged"), handleAuthorisationChanged); + ponder.on(ponderNamespace("Resolver:VersionChanged"), handleVersionChanged); + ponder.on(ponderNamespace("Resolver:DNSRecordChanged"), handleDNSRecordChanged); + ponder.on(ponderNamespace("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); + ponder.on(ponderNamespace("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); } diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index 7e979123..97e2f4ac 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -1,7 +1,7 @@ import { createConfig, factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; -import { NsReturnType, createNs } from "../../lib/plugins"; +import { createPonderNamespace } from "../../lib/ponder-plugin-utils"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; @@ -12,11 +12,9 @@ import { Resolver } from "./abis/Resolver"; const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); -export const baseName = ".eth" as const; +export const indexedSubname = "eth"; -const nestedNamespace = "/eth" as const; -export const ns = createNs(nestedNamespace); -export type NsType = NsReturnType; +export const ponderNamespace = createPonderNamespace(indexedSubname); export const config = createConfig({ networks: { @@ -26,19 +24,19 @@ export const config = createConfig({ }, }, contracts: { - [ns("RegistryOld")]: { + [ponderNamespace("RegistryOld")]: { network: "mainnet", abi: Registry, address: "0x314159265dd8dbb310642f98f50c066173c1259b", startBlock: 3327417, }, - [ns("Registry")]: { + [ponderNamespace("Registry")]: { network: "mainnet", abi: Registry, address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", startBlock: 9380380, }, - [ns("OldRegistryResolvers")]: { + [ponderNamespace("OldRegistryResolvers")]: { network: "mainnet", abi: RESOLVER_ABI, address: factory({ @@ -48,7 +46,7 @@ export const config = createConfig({ }), startBlock: 9380380, }, - [ns("Resolver")]: { + [ponderNamespace("Resolver")]: { network: "mainnet", abi: RESOLVER_ABI, address: factory({ @@ -58,25 +56,25 @@ export const config = createConfig({ }), startBlock: 9380380, }, - [ns("BaseRegistrar")]: { + [ponderNamespace("BaseRegistrar")]: { network: "mainnet", abi: BaseRegistrar, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 9380410, }, - [ns("EthRegistrarControllerOld")]: { + [ponderNamespace("EthRegistrarControllerOld")]: { network: "mainnet", abi: EthRegistrarControllerOld, address: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", startBlock: 9380471, }, - [ns("EthRegistrarController")]: { + [ponderNamespace("EthRegistrarController")]: { network: "mainnet", abi: EthRegistrarController, address: "0x253553366Da8546fC250F225fe3d25d0C782303b", startBlock: 16925618, }, - [ns("NameWrapper")]: { + [ponderNamespace("NameWrapper")]: { network: "mainnet", abi: NameWrapper, address: "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401", From e3b62c0d50d5cc8a9bbee0c220d3faf52b0604b0 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 7 Jan 2025 22:56:53 +0100 Subject: [PATCH 20/30] refactor(ponder-plugins): use `managed subname` term When referring to a variable holding any of: `eth`, `base.eth`, `linea.eth` --- ponder.config.ts | 8 ++++---- src/handlers/Registrar.ts | 18 +++++++++--------- src/lib/ens-helpers.ts | 8 ++++---- .../eth.base/handlers/Registrar.ts | 8 ++++---- .../eth.base/ponder.config.ts | 4 ++-- .../eth/handlers/EthRegistrar.ts | 4 ++-- src/ponder-ens-plugins/eth/ponder.config.ts | 4 ++-- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/ponder.config.ts b/ponder.config.ts index 24492f24..811495a6 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -2,12 +2,12 @@ import { INDEXED_SUBNAME } from "./src/lib/ens-helpers"; import { activate as activateEthBase, config as ethBaseConfig, - indexedSubname as ethBaseIndexedSubname, + managedSubname as ethBaseManagedSubname, } from "./src/ponder-ens-plugins/eth.base/ponder.config"; import { activate as activateEth, config as ethConfig, - indexedSubname as ethIndexedSubname, + managedSubname as ethManagedSubname, } from "./src/ponder-ens-plugins/eth/ponder.config"; type AllConfigs = typeof ethConfig & typeof ethBaseConfig; @@ -17,10 +17,10 @@ type AllConfigs = typeof ethConfig & typeof ethBaseConfig; // config is run at runtime export default ((): AllConfigs => { switch (INDEXED_SUBNAME) { - case ethIndexedSubname: + case ethManagedSubname: activateEth(); return ethConfig as AllConfigs; - case ethBaseIndexedSubname: + case ethBaseManagedSubname: activateEthBase(); return ethBaseConfig as AllConfigs; default: diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index cf2e8ca6..cfe18eae 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -9,28 +9,28 @@ const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds /** * A factory function that returns Ponder indexing handlers for a specified index name/subname. */ -export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { - const indexedSubnameNode = namehash(indexedSubname); +export const makeRegistryHandlers = (managedSubname: `${string}eth`) => { + const managedSubnameNode = namehash(managedSubname); async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(managedSubnameNode, label); const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { await context.db .update(domains, { id: node }) - .set({ labelName: name, name: `${name}${indexedSubname}` }); + .set({ labelName: name, name: `${name}${managedSubname}` }); } await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); } return { - get indexedSubnameNode() { - return indexedSubnameNode; + get managedSubnameNode() { + return managedSubnameNode; }, async handleNameRegistered({ @@ -48,7 +48,7 @@ export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { await upsertAccount(context, owner); const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(managedSubnameNode, label); // TODO: materialze labelName via rainbow tables ala Registry.ts const labelName = undefined; @@ -103,7 +103,7 @@ export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { const { id, expires } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(managedSubnameNode, label); await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); @@ -128,7 +128,7 @@ export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { await upsertAccount(context, to); const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(managedSubnameNode, label); const registration = await context.db.find(registrations, { id: label }); if (!registration) return; diff --git a/src/lib/ens-helpers.ts b/src/lib/ens-helpers.ts index 21a4f156..472e32ed 100644 --- a/src/lib/ens-helpers.ts +++ b/src/lib/ens-helpers.ts @@ -1,12 +1,12 @@ import { type Hex, concat, keccak256, namehash, toHex } from "viem"; -class IndexedSubname { +class ManagedSubname { private constructor(private readonly name: `${string}eth`) {} static parse(name: string | undefined = "") { - if (!name.endsWith("eth")) throw new Error(`IndexedSubname should end with 'eth'`); + if (!name.endsWith("eth")) throw new Error(`ManagedSubname should end with 'eth'`); - return new IndexedSubname(name as `${string}eth`); + return new ManagedSubname(name as `${string}eth`); } toString() { @@ -14,7 +14,7 @@ class IndexedSubname { } } -export const INDEXED_SUBNAME = IndexedSubname.parse(process.env.INDEX_SUBNAME).toString(); +export const INDEXED_SUBNAME = ManagedSubname.parse(process.env.INDEX_SUBNAME).toString(); // TODO: pull from ens utils lib or something export const ROOT_NODE = namehash(""); diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts index 54bac89c..1e547e5d 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts @@ -3,7 +3,7 @@ import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; import { upsertAccount } from "../../../lib/upserts"; -import { indexedSubname, ponderNamespace } from "../ponder.config"; +import { managedSubname, ponderNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -11,8 +11,8 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, - indexedSubnameNode, -} = makeRegistryHandlers(indexedSubname); + managedSubnameNode, +} = makeRegistryHandlers(managedSubname); export default function () { // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers @@ -28,7 +28,7 @@ export default function () { // allowing the base indexer to progress. const { id, owner } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(managedSubnameNode, label); await upsertAccount(context, owner); await context.db .insert(domains) diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/ponder-ens-plugins/eth.base/ponder.config.ts index 230ac129..62c2056a 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/ponder-ens-plugins/eth.base/ponder.config.ts @@ -9,9 +9,9 @@ import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -export const indexedSubname = "base.eth" as const; +export const managedSubname = "base.eth" as const; -export const ponderNamespace = createPonderNamespace(indexedSubname); +export const ponderNamespace = createPonderNamespace(managedSubname); export const config = createConfig({ networks: { diff --git a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts index 76a651ba..9ccd8342 100644 --- a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts +++ b/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts @@ -1,6 +1,6 @@ import { ponder } from "ponder:registry"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { indexedSubname, ponderNamespace } from "../ponder.config"; +import { managedSubname, ponderNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -8,7 +8,7 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(indexedSubname); +} = makeRegistryHandlers(managedSubname); export default function () { ponder.on(ponderNamespace("BaseRegistrar:NameRegistered"), handleNameRegistered); diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/ponder-ens-plugins/eth/ponder.config.ts index 97e2f4ac..0ab6b895 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/ponder-ens-plugins/eth/ponder.config.ts @@ -12,9 +12,9 @@ import { Resolver } from "./abis/Resolver"; const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); -export const indexedSubname = "eth"; +export const managedSubname = "eth"; -export const ponderNamespace = createPonderNamespace(indexedSubname); +export const ponderNamespace = createPonderNamespace(managedSubname); export const config = createConfig({ networks: { From aad651cf532f1d89b214695994bb2d8e2574b7e3 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Tue, 7 Jan 2025 23:05:54 +0100 Subject: [PATCH 21/30] refactor(ponder-plugins): make generic handlers to use ponder types --- src/handlers/Registrar.ts | 3 +- src/handlers/Registry.ts | 45 +++++++++++++------ src/handlers/Resolver.ts | 23 +++++----- .../eth.base/handlers/Registry.ts | 4 +- .../eth/handlers/Registry.ts | 4 +- 5 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index cfe18eae..af2da5e3 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -1,5 +1,6 @@ import { type Context } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; +import { Block } from "ponder"; import { type Hex, namehash } from "viem"; import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; import { upsertAccount, upsertRegistration } from "../lib/upserts"; @@ -39,7 +40,7 @@ export const makeRegistryHandlers = (managedSubname: `${string}eth`) => { }: { context: Context; event: { - block: { timestamp: bigint }; + block: Block; args: { id: bigint; owner: Hex; expires: bigint }; }; }) { diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index fb15b83d..f9a697d4 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -1,15 +1,19 @@ -import { type Context } from "ponder:registry"; -import { resolvers } from "ponder:schema"; -import { domains } from "ponder:schema"; +import { Context } from "ponder:registry"; +import { domains, resolvers } from "ponder:schema"; +import { Block } from "ponder"; import { type Hex, zeroAddress } from "viem"; -import { ROOT_NODE, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; +import { + ROOT_NODE, + encodeLabelhash, + makeSubnodeNamehash, +} from "../lib/ens-helpers"; import { makeResolverId } from "../lib/ids"; import { upsertAccount } from "../lib/upserts"; /** * Initialize the ENS root node with the zeroAddress as the owner. */ -export async function handleRootNodeCreation({ context }: { context: Context }) { +export async function setupRootNode({ context }: { context: Context }) { // ensure we have an account for the zeroAddress await upsertAccount(context, zeroAddress); @@ -24,13 +28,18 @@ export async function handleRootNodeCreation({ context }: { context: Context }) function isDomainEmpty(domain: typeof domains.$inferSelect) { return ( - domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 + domain.resolverId === null && + domain.ownerId === zeroAddress && + domain.subdomainCount === 0 ); } // a more accurate name for the following function // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context: Context, + node: Hex +) { const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error(`Domain not found: ${node}`); @@ -41,7 +50,10 @@ async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Con .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); + return recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.parentId + ); } } @@ -52,7 +64,7 @@ export async function handleTransfer({ context: Context; event: { args: { node: Hex; owner: Hex }; - block: { timestamp: bigint }; + block: Block; }; }) { const { node, owner } = event.args; @@ -83,7 +95,7 @@ export const handleNewOwner = context: Context; event: { args: { node: Hex; label: Hex; owner: Hex }; - block: { timestamp: bigint }; + block: Block; }; }) => { const { label, node, owner } = event.args; @@ -97,7 +109,9 @@ export const handleNewOwner = let domain = await context.db.find(domains, { id: subnode }); if (domain) { // if the domain already exists, this is just an update of the owner record. - await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); + await context.db + .update(domains, { id: domain.id }) + .set({ ownerId: owner, isMigrated }); } else { // otherwise create the domain domain = await context.db.insert(domains).values({ @@ -123,12 +137,17 @@ export const handleNewOwner = const labelName = encodeLabelhash(label); const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - await context.db.update(domains, { id: domain.id }).set({ name, labelName }); + await context.db + .update(domains, { id: domain.id }) + .set({ name, labelName }); } // garbage collect newly 'empty' domain iff necessary if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); + await recursivelyRemoveEmptyDomainFromParentSubdomainCount( + context, + domain.id + ); } }; diff --git a/src/handlers/Resolver.ts b/src/handlers/Resolver.ts index 3c355f88..1b0708e6 100644 --- a/src/handlers/Resolver.ts +++ b/src/handlers/Resolver.ts @@ -1,5 +1,6 @@ -import { type Context, type Event } from "ponder:registry"; +import { type Context } from "ponder:registry"; import { domains, resolvers } from "ponder:schema"; +import { Log } from "ponder"; import { Hex } from "viem"; import { hasNullByte, uniq } from "../lib/helpers"; import { makeResolverId } from "../lib/ids"; @@ -12,7 +13,7 @@ export async function handleAddrChanged({ context: Context; event: { args: { node: Hex; a: Hex }; - log: { address: Hex }; + log: Log; }; }) { const { a: address, node } = event.args; @@ -42,7 +43,7 @@ export async function handleAddressChanged({ context: Context; event: { args: { node: Hex; coinType: bigint; newAddress: Hex }; - log: { address: Hex }; + log: Log; }; }) { const { node, coinType, newAddress } = event.args; @@ -70,7 +71,7 @@ export async function handleNameChanged({ context: Context; event: { args: { node: Hex; name: string }; - log: { address: Hex }; + log: Log; }; }) { const { node, name } = event.args; @@ -93,7 +94,7 @@ export async function handleABIChanged({ context: Context; event: { args: { node: Hex }; - log: { address: Hex }; + log: Log; }; }) { const { node } = event.args; @@ -114,7 +115,7 @@ export async function handlePubkeyChanged({ context: Context; event: { args: { node: Hex }; - log: { address: Hex }; + log: Log; }; }) { const { node } = event.args; @@ -135,7 +136,7 @@ export async function handleTextChanged({ context: Context; event: { args: { node: Hex; indexedKey: string; key: string; value?: string }; - log: { address: Hex }; + log: Log; }; }) { const { node, key } = event.args; @@ -159,7 +160,7 @@ export async function handleContenthashChanged({ context: Context; event: { args: { node: Hex; hash: Hex }; - log: { address: Hex }; + log: Log; }; }) { const { node, hash } = event.args; @@ -185,7 +186,7 @@ export async function handleInterfaceChanged({ interfaceID: Hex; implementer: Hex; }; - log: { address: Hex }; + log: Log; }; }) { const { node } = event.args; @@ -211,7 +212,7 @@ export async function handleAuthorisationChanged({ target: Hex; isAuthorised: boolean; }; - log: { address: Hex }; + log: Log; }; }) { const { node } = event.args; @@ -235,7 +236,7 @@ export async function handleVersionChanged({ node: Hex; newVersion: bigint; }; - log: { address: Hex }; + log: Log; }; }) { // a version change nulls out the resolver diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts index 0b6c8b6a..1a3be61a 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth.base/handlers/Registry.ts @@ -3,13 +3,13 @@ import { handleNewOwner, handleNewResolver, handleNewTTL, - handleRootNodeCreation, handleTransfer, + setupRootNode, } from "../../../handlers/Registry"; import { ponderNamespace } from "../ponder.config"; export default function () { - ponder.on(ponderNamespace("Registry:setup"), handleRootNodeCreation); + ponder.on(ponderNamespace("Registry:setup"), setupRootNode); ponder.on(ponderNamespace("Registry:NewOwner"), handleNewOwner(true)); ponder.on(ponderNamespace("Registry:NewResolver"), handleNewResolver); ponder.on(ponderNamespace("Registry:NewTTL"), handleNewTTL); diff --git a/src/ponder-ens-plugins/eth/handlers/Registry.ts b/src/ponder-ens-plugins/eth/handlers/Registry.ts index 8f1a92ea..b4ba0066 100644 --- a/src/ponder-ens-plugins/eth/handlers/Registry.ts +++ b/src/ponder-ens-plugins/eth/handlers/Registry.ts @@ -5,8 +5,8 @@ import { handleNewOwner, handleNewResolver, handleNewTTL, - handleRootNodeCreation, handleTransfer, + setupRootNode, } from "../../../handlers/Registry"; import { makeSubnodeNamehash } from "../../../lib/ens-helpers"; import { ponderNamespace } from "../ponder.config"; @@ -18,7 +18,7 @@ async function isDomainMigrated(context: Context, node: Hex) { } export default function () { - ponder.on(ponderNamespace("RegistryOld:setup"), handleRootNodeCreation); + ponder.on(ponderNamespace("RegistryOld:setup"), setupRootNode); // old registry functions are proxied to the current handlers // iff the domain has not yet been migrated From 00c99355bee182697ba84e7c3c9f7aa1bc1635b3 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 07:45:44 +0100 Subject: [PATCH 22/30] chore(code-style): apply `biome format --fix` --- biome.json | 2 +- src/handlers/Registry.ts | 33 +++++++-------------------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/biome.json b/biome.json index 86c37ba0..b08a5735 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,7 @@ }, "files": { "ignoreUnknown": false, - "ignore": [] + "ignore": [".ponder", "generated"] }, "formatter": { "enabled": true, diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index f9a697d4..a921c573 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -2,11 +2,7 @@ import { Context } from "ponder:registry"; import { domains, resolvers } from "ponder:schema"; import { Block } from "ponder"; import { type Hex, zeroAddress } from "viem"; -import { - ROOT_NODE, - encodeLabelhash, - makeSubnodeNamehash, -} from "../lib/ens-helpers"; +import { ROOT_NODE, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; import { makeResolverId } from "../lib/ids"; import { upsertAccount } from "../lib/upserts"; @@ -28,18 +24,13 @@ export async function setupRootNode({ context }: { context: Context }) { function isDomainEmpty(domain: typeof domains.$inferSelect) { return ( - domain.resolverId === null && - domain.ownerId === zeroAddress && - domain.subdomainCount === 0 + domain.resolverId === null && domain.ownerId === zeroAddress && domain.subdomainCount === 0 ); } // a more accurate name for the following function // https://github.com/ensdomains/ens-subgraph/blob/master/src/ensRegistry.ts#L64 -async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context: Context, - node: Hex -) { +async function recursivelyRemoveEmptyDomainFromParentSubdomainCount(context: Context, node: Hex) { const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error(`Domain not found: ${node}`); @@ -50,10 +41,7 @@ async function recursivelyRemoveEmptyDomainFromParentSubdomainCount( .set((row) => ({ subdomainCount: row.subdomainCount - 1 })); // recurse to parent - return recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.parentId - ); + return recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.parentId); } } @@ -109,9 +97,7 @@ export const handleNewOwner = let domain = await context.db.find(domains, { id: subnode }); if (domain) { // if the domain already exists, this is just an update of the owner record. - await context.db - .update(domains, { id: domain.id }) - .set({ ownerId: owner, isMigrated }); + await context.db.update(domains, { id: domain.id }).set({ ownerId: owner, isMigrated }); } else { // otherwise create the domain domain = await context.db.insert(domains).values({ @@ -137,17 +123,12 @@ export const handleNewOwner = const labelName = encodeLabelhash(label); const name = parent?.name ? `${labelName}.${parent.name}` : labelName; - await context.db - .update(domains, { id: domain.id }) - .set({ name, labelName }); + await context.db.update(domains, { id: domain.id }).set({ name, labelName }); } // garbage collect newly 'empty' domain iff necessary if (owner === zeroAddress) { - await recursivelyRemoveEmptyDomainFromParentSubdomainCount( - context, - domain.id - ); + await recursivelyRemoveEmptyDomainFromParentSubdomainCount(context, domain.id); } }; From a39200fcf4c28d0f34dd4e97524526832a66c231 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 07:56:35 +0100 Subject: [PATCH 23/30] fix(ci): run static analysis Allows to automatically catch code-related issues on the CI --- .github/workflows/static-analysis.yml | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/static-analysis.yml diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 00000000..14e879d3 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,31 @@ +name: Static Analysis + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + biome-ci: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install + + - name: Run Biome CI + run: pnpm biome ci From 9eb9eef494a0a444dc32018badec79540e286a35 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 15:58:32 +0100 Subject: [PATCH 24/30] refactor(plugins): rename plugins directory --- .env.local.example | 22 +++++++++++-------- ponder.config.ts | 10 ++++----- src/handlers/Registrar.ts | 4 ++-- src/handlers/Registry.ts | 2 +- .../{ens-helpers.ts => subname-helpers.ts} | 2 +- src/plugins/README.md | 8 +++++++ .../eth.base => plugins/base.eth}/README.md | 0 .../base.eth}/abis/BaseRegistrar.ts | 0 .../base.eth}/abis/EARegistrarController.ts | 0 .../base.eth}/abis/L1Resolver.ts | 0 .../base.eth}/abis/L2Resolver.ts | 0 .../base.eth}/abis/RegistrarController.ts | 0 .../base.eth}/abis/Registry.ts | 0 .../base.eth}/abis/ReverseRegistrar.ts | 0 .../base.eth}/handlers/Registrar.ts | 2 +- .../base.eth}/handlers/Registry.ts | 0 .../base.eth}/handlers/Resolver.ts | 0 .../base.eth}/ponder.config.ts | 2 +- .../eth/abis/BaseRegistrar.ts | 0 .../eth/abis/EthRegistrarController.ts | 0 .../eth/abis/EthRegistrarControllerOld.ts | 0 .../eth/abis/LegacyPublicResolver.ts | 0 .../eth/abis/NameWrapper.ts | 0 .../eth/abis/Registry.ts | 0 .../eth/abis/Resolver.ts | 0 .../eth/handlers/EthRegistrar.ts | 0 .../eth/handlers/NameWrapper.ts | 0 .../eth/handlers/Registry.ts | 2 +- .../eth/handlers/Resolver.ts | 0 .../eth/ponder.config.ts | 5 +++-- 30 files changed, 36 insertions(+), 23 deletions(-) rename src/lib/{ens-helpers.ts => subname-helpers.ts} (93%) create mode 100644 src/plugins/README.md rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/README.md (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/abis/BaseRegistrar.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/abis/EARegistrarController.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/abis/L1Resolver.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/abis/L2Resolver.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/abis/RegistrarController.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/abis/Registry.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/abis/ReverseRegistrar.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/handlers/Registrar.ts (99%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/handlers/Registry.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/handlers/Resolver.ts (100%) rename src/{ponder-ens-plugins/eth.base => plugins/base.eth}/ponder.config.ts (96%) rename src/{ponder-ens-plugins => plugins}/eth/abis/BaseRegistrar.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/abis/EthRegistrarController.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/abis/EthRegistrarControllerOld.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/abis/LegacyPublicResolver.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/abis/NameWrapper.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/abis/Registry.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/abis/Resolver.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/handlers/EthRegistrar.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/handlers/NameWrapper.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/handlers/Registry.ts (97%) rename src/{ponder-ens-plugins => plugins}/eth/handlers/Resolver.ts (100%) rename src/{ponder-ens-plugins => plugins}/eth/ponder.config.ts (95%) diff --git a/.env.local.example b/.env.local.example index 65690ce7..6252eb77 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,15 +1,19 @@ -# RPC urls, follow the format RPC_URL_{chainId}={rpcUrl} +# RPC configuration +# Follow the format: RPC_URL_{chainId}={rpcUrl} -PONDER_RPC_URL_1=https://ethereum-rpc.publicnode.com -PONDER_RPC_URL_8453=https://base-rpc.publicnode.com -PONDER_RPC_URL_59144=https://linea-rpc.publicnode.com +RPC_URL_1=https://ethereum-rpc.publicnode.com +RPC_URL_8453=https://base-rpc.publicnode.com +RPC_URL_59144=https://linea-rpc.publicnode.com -# ponder indexer ens deployment configuration +# Identify which indexer plugin to activate (see `src/plugins` for available plugins) -INDEX_SUBNAME=base.eth +ACTIVE_PLUGIN=base.eth -# ponder indexer database configuration +# Database configuration -## one schema name per chain ID, i.e. ponder_ens_${INDEX_SUBNAME} -DATABASE_SCHEMA=ponder_ens_base.eth +# This is where the indexer will create the tables defined in ponder.schema.ts +# No two indexer instances can use the same database schema at the same time. This prevents data corruption. +# @link https://ponder.sh/docs/api-reference/database#database-schema-rules +DATABASE_SCHEMA=subname_index_base.eth +# The indexer will use Postgres with that as the connection string. If not defined, the indexer will use PSlite. DATABASE_URL=postgresql://dbuser:abcd1234@localhost:5432/my_database diff --git a/ponder.config.ts b/ponder.config.ts index 811495a6..87a4199f 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,14 +1,14 @@ -import { INDEXED_SUBNAME } from "./src/lib/ens-helpers"; +import { ACTIVE_PLUGIN } from "./src/lib/subname-helpers"; import { activate as activateEthBase, config as ethBaseConfig, managedSubname as ethBaseManagedSubname, -} from "./src/ponder-ens-plugins/eth.base/ponder.config"; +} from "./src/plugins/base.eth/ponder.config"; import { activate as activateEth, config as ethConfig, managedSubname as ethManagedSubname, -} from "./src/ponder-ens-plugins/eth/ponder.config"; +} from "./src/plugins/eth/ponder.config"; type AllConfigs = typeof ethConfig & typeof ethBaseConfig; @@ -16,7 +16,7 @@ type AllConfigs = typeof ethConfig & typeof ethBaseConfig; // this makes all of the mapping types happy at typecheck-time, but only the relevant // config is run at runtime export default ((): AllConfigs => { - switch (INDEXED_SUBNAME) { + switch (ACTIVE_PLUGIN) { case ethManagedSubname: activateEth(); return ethConfig as AllConfigs; @@ -24,6 +24,6 @@ export default ((): AllConfigs => { activateEthBase(); return ethBaseConfig as AllConfigs; default: - throw new Error(`Unsupported base name: ${INDEXED_SUBNAME}`); + throw new Error(`Unsupported ACTIVE_PLUGIN: ${ACTIVE_PLUGIN}`); } })(); diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index af2da5e3..7a6568e8 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -2,13 +2,13 @@ import { type Context } from "ponder:registry"; import { domains, registrations } from "ponder:schema"; import { Block } from "ponder"; import { type Hex, namehash } from "viem"; -import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/ens-helpers"; +import { isLabelValid, makeSubnodeNamehash, tokenIdToLabel } from "../lib/subname-helpers"; import { upsertAccount, upsertRegistration } from "../lib/upserts"; const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds /** - * A factory function that returns Ponder indexing handlers for a specified index name/subname. + * A factory function that returns Ponder indexing handlers for a specified subname. */ export const makeRegistryHandlers = (managedSubname: `${string}eth`) => { const managedSubnameNode = namehash(managedSubname); diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index a921c573..36de3a79 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -2,8 +2,8 @@ import { Context } from "ponder:registry"; import { domains, resolvers } from "ponder:schema"; import { Block } from "ponder"; import { type Hex, zeroAddress } from "viem"; -import { ROOT_NODE, encodeLabelhash, makeSubnodeNamehash } from "../lib/ens-helpers"; import { makeResolverId } from "../lib/ids"; +import { ROOT_NODE, encodeLabelhash, makeSubnodeNamehash } from "../lib/subname-helpers"; import { upsertAccount } from "../lib/upserts"; /** diff --git a/src/lib/ens-helpers.ts b/src/lib/subname-helpers.ts similarity index 93% rename from src/lib/ens-helpers.ts rename to src/lib/subname-helpers.ts index 472e32ed..54af2f2c 100644 --- a/src/lib/ens-helpers.ts +++ b/src/lib/subname-helpers.ts @@ -14,7 +14,7 @@ class ManagedSubname { } } -export const INDEXED_SUBNAME = ManagedSubname.parse(process.env.INDEX_SUBNAME).toString(); +export const ACTIVE_PLUGIN = ManagedSubname.parse(process.env.ACTIVE_PLUGIN).toString(); // TODO: pull from ens utils lib or something export const ROOT_NODE = namehash(""); diff --git a/src/plugins/README.md b/src/plugins/README.md new file mode 100644 index 00000000..991fc8fc --- /dev/null +++ b/src/plugins/README.md @@ -0,0 +1,8 @@ +# Indexer plugins + +This directory contains plugins which allow defining subname-specific processing of blockchain events. +Only one plugin can be active at a time. Use the `ACTIVE_PLUGIN` env variable to select the active plugin, for example: + +``` +ACTIVE_PLUGIN=base.eth +``` \ No newline at end of file diff --git a/src/ponder-ens-plugins/eth.base/README.md b/src/plugins/base.eth/README.md similarity index 100% rename from src/ponder-ens-plugins/eth.base/README.md rename to src/plugins/base.eth/README.md diff --git a/src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts b/src/plugins/base.eth/abis/BaseRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/abis/BaseRegistrar.ts rename to src/plugins/base.eth/abis/BaseRegistrar.ts diff --git a/src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts b/src/plugins/base.eth/abis/EARegistrarController.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/abis/EARegistrarController.ts rename to src/plugins/base.eth/abis/EARegistrarController.ts diff --git a/src/ponder-ens-plugins/eth.base/abis/L1Resolver.ts b/src/plugins/base.eth/abis/L1Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/abis/L1Resolver.ts rename to src/plugins/base.eth/abis/L1Resolver.ts diff --git a/src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts b/src/plugins/base.eth/abis/L2Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/abis/L2Resolver.ts rename to src/plugins/base.eth/abis/L2Resolver.ts diff --git a/src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts b/src/plugins/base.eth/abis/RegistrarController.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/abis/RegistrarController.ts rename to src/plugins/base.eth/abis/RegistrarController.ts diff --git a/src/ponder-ens-plugins/eth.base/abis/Registry.ts b/src/plugins/base.eth/abis/Registry.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/abis/Registry.ts rename to src/plugins/base.eth/abis/Registry.ts diff --git a/src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts b/src/plugins/base.eth/abis/ReverseRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/abis/ReverseRegistrar.ts rename to src/plugins/base.eth/abis/ReverseRegistrar.ts diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts b/src/plugins/base.eth/handlers/Registrar.ts similarity index 99% rename from src/ponder-ens-plugins/eth.base/handlers/Registrar.ts rename to src/plugins/base.eth/handlers/Registrar.ts index 1e547e5d..00d747a1 100644 --- a/src/ponder-ens-plugins/eth.base/handlers/Registrar.ts +++ b/src/plugins/base.eth/handlers/Registrar.ts @@ -1,7 +1,7 @@ import { ponder } from "ponder:registry"; import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/ens-helpers"; +import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/subname-helpers"; import { upsertAccount } from "../../../lib/upserts"; import { managedSubname, ponderNamespace } from "../ponder.config"; diff --git a/src/ponder-ens-plugins/eth.base/handlers/Registry.ts b/src/plugins/base.eth/handlers/Registry.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/handlers/Registry.ts rename to src/plugins/base.eth/handlers/Registry.ts diff --git a/src/ponder-ens-plugins/eth.base/handlers/Resolver.ts b/src/plugins/base.eth/handlers/Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/eth.base/handlers/Resolver.ts rename to src/plugins/base.eth/handlers/Resolver.ts diff --git a/src/ponder-ens-plugins/eth.base/ponder.config.ts b/src/plugins/base.eth/ponder.config.ts similarity index 96% rename from src/ponder-ens-plugins/eth.base/ponder.config.ts rename to src/plugins/base.eth/ponder.config.ts index 62c2056a..5d5d9638 100644 --- a/src/ponder-ens-plugins/eth.base/ponder.config.ts +++ b/src/plugins/base.eth/ponder.config.ts @@ -17,7 +17,7 @@ export const config = createConfig({ networks: { base: { chainId: base.id, - transport: http(process.env[`PONDER_RPC_URL_${base.id}`]), + transport: http(process.env[`RPC_URL_${base.id}`]), }, }, contracts: { diff --git a/src/ponder-ens-plugins/eth/abis/BaseRegistrar.ts b/src/plugins/eth/abis/BaseRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/eth/abis/BaseRegistrar.ts rename to src/plugins/eth/abis/BaseRegistrar.ts diff --git a/src/ponder-ens-plugins/eth/abis/EthRegistrarController.ts b/src/plugins/eth/abis/EthRegistrarController.ts similarity index 100% rename from src/ponder-ens-plugins/eth/abis/EthRegistrarController.ts rename to src/plugins/eth/abis/EthRegistrarController.ts diff --git a/src/ponder-ens-plugins/eth/abis/EthRegistrarControllerOld.ts b/src/plugins/eth/abis/EthRegistrarControllerOld.ts similarity index 100% rename from src/ponder-ens-plugins/eth/abis/EthRegistrarControllerOld.ts rename to src/plugins/eth/abis/EthRegistrarControllerOld.ts diff --git a/src/ponder-ens-plugins/eth/abis/LegacyPublicResolver.ts b/src/plugins/eth/abis/LegacyPublicResolver.ts similarity index 100% rename from src/ponder-ens-plugins/eth/abis/LegacyPublicResolver.ts rename to src/plugins/eth/abis/LegacyPublicResolver.ts diff --git a/src/ponder-ens-plugins/eth/abis/NameWrapper.ts b/src/plugins/eth/abis/NameWrapper.ts similarity index 100% rename from src/ponder-ens-plugins/eth/abis/NameWrapper.ts rename to src/plugins/eth/abis/NameWrapper.ts diff --git a/src/ponder-ens-plugins/eth/abis/Registry.ts b/src/plugins/eth/abis/Registry.ts similarity index 100% rename from src/ponder-ens-plugins/eth/abis/Registry.ts rename to src/plugins/eth/abis/Registry.ts diff --git a/src/ponder-ens-plugins/eth/abis/Resolver.ts b/src/plugins/eth/abis/Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/eth/abis/Resolver.ts rename to src/plugins/eth/abis/Resolver.ts diff --git a/src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts b/src/plugins/eth/handlers/EthRegistrar.ts similarity index 100% rename from src/ponder-ens-plugins/eth/handlers/EthRegistrar.ts rename to src/plugins/eth/handlers/EthRegistrar.ts diff --git a/src/ponder-ens-plugins/eth/handlers/NameWrapper.ts b/src/plugins/eth/handlers/NameWrapper.ts similarity index 100% rename from src/ponder-ens-plugins/eth/handlers/NameWrapper.ts rename to src/plugins/eth/handlers/NameWrapper.ts diff --git a/src/ponder-ens-plugins/eth/handlers/Registry.ts b/src/plugins/eth/handlers/Registry.ts similarity index 97% rename from src/ponder-ens-plugins/eth/handlers/Registry.ts rename to src/plugins/eth/handlers/Registry.ts index b4ba0066..d1fded23 100644 --- a/src/ponder-ens-plugins/eth/handlers/Registry.ts +++ b/src/plugins/eth/handlers/Registry.ts @@ -8,7 +8,7 @@ import { handleTransfer, setupRootNode, } from "../../../handlers/Registry"; -import { makeSubnodeNamehash } from "../../../lib/ens-helpers"; +import { makeSubnodeNamehash } from "../../../lib/subname-helpers"; import { ponderNamespace } from "../ponder.config"; // a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not diff --git a/src/ponder-ens-plugins/eth/handlers/Resolver.ts b/src/plugins/eth/handlers/Resolver.ts similarity index 100% rename from src/ponder-ens-plugins/eth/handlers/Resolver.ts rename to src/plugins/eth/handlers/Resolver.ts diff --git a/src/ponder-ens-plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts similarity index 95% rename from src/ponder-ens-plugins/eth/ponder.config.ts rename to src/plugins/eth/ponder.config.ts index 0ab6b895..5d8a2f94 100644 --- a/src/ponder-ens-plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -1,6 +1,7 @@ import { createConfig, factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; +import { mainnet } from "viem/chains"; import { createPonderNamespace } from "../../lib/ponder-plugin-utils"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; @@ -19,8 +20,8 @@ export const ponderNamespace = createPonderNamespace(managedSubname); export const config = createConfig({ networks: { mainnet: { - chainId: 1, - transport: http(process.env.PONDER_RPC_URL_1), + chainId: mainnet.id, + transport: http(process.env[`RPC_URL_${mainnet.id}`]), }, }, contracts: { From 728ec6212dd5efa344c76b271a7367a0f6824bb8 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 16:01:05 +0100 Subject: [PATCH 25/30] refactor(plugins): rename plugin helpers --- ponder.config.ts | 8 +-- src/handlers/Registrar.ts | 18 +++---- src/lib/ponder-plugin-utils.ts | 10 ++-- src/lib/subname-helpers.ts | 8 +-- src/plugins/base.eth/handlers/Registrar.ts | 22 ++++---- src/plugins/base.eth/handlers/Registry.ts | 12 ++--- src/plugins/base.eth/handlers/Resolver.ts | 2 +- src/plugins/base.eth/ponder.config.ts | 16 +++--- src/plugins/eth/handlers/EthRegistrar.ts | 18 +++---- src/plugins/eth/handlers/Registry.ts | 20 ++++---- src/plugins/eth/handlers/Resolver.ts | 58 +++++++++++----------- src/plugins/eth/ponder.config.ts | 22 ++++---- 12 files changed, 107 insertions(+), 107 deletions(-) diff --git a/ponder.config.ts b/ponder.config.ts index 87a4199f..eae080af 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -2,12 +2,12 @@ import { ACTIVE_PLUGIN } from "./src/lib/subname-helpers"; import { activate as activateEthBase, config as ethBaseConfig, - managedSubname as ethBaseManagedSubname, + indexedSubname as ethBaseIndexedSubname, } from "./src/plugins/base.eth/ponder.config"; import { activate as activateEth, config as ethConfig, - managedSubname as ethManagedSubname, + indexedSubname as ethIndexedSubname, } from "./src/plugins/eth/ponder.config"; type AllConfigs = typeof ethConfig & typeof ethBaseConfig; @@ -17,10 +17,10 @@ type AllConfigs = typeof ethConfig & typeof ethBaseConfig; // config is run at runtime export default ((): AllConfigs => { switch (ACTIVE_PLUGIN) { - case ethManagedSubname: + case ethIndexedSubname: activateEth(); return ethConfig as AllConfigs; - case ethBaseManagedSubname: + case ethBaseIndexedSubname: activateEthBase(); return ethBaseConfig as AllConfigs; default: diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index 7a6568e8..b181d461 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -10,28 +10,28 @@ const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds /** * A factory function that returns Ponder indexing handlers for a specified subname. */ -export const makeRegistryHandlers = (managedSubname: `${string}eth`) => { - const managedSubnameNode = namehash(managedSubname); +export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { + const indexedSubnameNode = namehash(indexedSubname); async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; - const node = makeSubnodeNamehash(managedSubnameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { await context.db .update(domains, { id: node }) - .set({ labelName: name, name: `${name}${managedSubname}` }); + .set({ labelName: name, name: `${name}${indexedSubname}` }); } await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); } return { - get managedSubnameNode() { - return managedSubnameNode; + get indexedSubnameNode() { + return indexedSubnameNode; }, async handleNameRegistered({ @@ -49,7 +49,7 @@ export const makeRegistryHandlers = (managedSubname: `${string}eth`) => { await upsertAccount(context, owner); const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(managedSubnameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); // TODO: materialze labelName via rainbow tables ala Registry.ts const labelName = undefined; @@ -104,7 +104,7 @@ export const makeRegistryHandlers = (managedSubname: `${string}eth`) => { const { id, expires } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(managedSubnameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); @@ -129,7 +129,7 @@ export const makeRegistryHandlers = (managedSubname: `${string}eth`) => { await upsertAccount(context, to); const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(managedSubnameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); const registration = await context.db.find(registrations, { id: label }); if (!registration) return; diff --git a/src/lib/ponder-plugin-utils.ts b/src/lib/ponder-plugin-utils.ts index dc3618e4..4d4e3dc2 100644 --- a/src/lib/ponder-plugin-utils.ts +++ b/src/lib/ponder-plugin-utils.ts @@ -16,18 +16,18 @@ * * @example * ```ts - * const ethNs = createPonderNamespace("base.eth"); - * const baseEthNs = createPonderNamespace("base.eth"); + * const ethNs = createPluginNamespace("base.eth"); + * const baseEthNs = createPluginNamespace("base.eth"); * * ethNs("Registry"); // returns "/eth/Registry" * baseEthNs("Registry"); // returns "/base/eth/Registry" * ``` */ -export function createPonderNamespace(subname: Subname) { +export function createPluginNamespace(subname: Subname) { const path = transformDomain(subname) satisfies PonderNsPath; /** Creates a name-spaced contract name */ - return function ponderNamespace( + return function pluginNamespace( contractName: ContractName, ): PonderNamespaceReturnType { return `${path}/${contractName}`; @@ -53,7 +53,7 @@ function transformDomain(domain: T): TransformDomain { return `/${parts.join("/")}` as TransformDomain; } -/** The return type of the `ponderNamespace` function */ +/** The return type of the `pluginNamespace` function */ export type PonderNamespaceReturnType< ContractName extends string, NsPath extends PonderNsPath, diff --git a/src/lib/subname-helpers.ts b/src/lib/subname-helpers.ts index 54af2f2c..4128c81c 100644 --- a/src/lib/subname-helpers.ts +++ b/src/lib/subname-helpers.ts @@ -1,12 +1,12 @@ import { type Hex, concat, keccak256, namehash, toHex } from "viem"; -class ManagedSubname { +class IndexedSubname { private constructor(private readonly name: `${string}eth`) {} static parse(name: string | undefined = "") { - if (!name.endsWith("eth")) throw new Error(`ManagedSubname should end with 'eth'`); + if (!name.endsWith("eth")) throw new Error(`IndexedSubname should end with 'eth'`); - return new ManagedSubname(name as `${string}eth`); + return new IndexedSubname(name as `${string}eth`); } toString() { @@ -14,7 +14,7 @@ class ManagedSubname { } } -export const ACTIVE_PLUGIN = ManagedSubname.parse(process.env.ACTIVE_PLUGIN).toString(); +export const ACTIVE_PLUGIN = IndexedSubname.parse(process.env.ACTIVE_PLUGIN).toString(); // TODO: pull from ens utils lib or something export const ROOT_NODE = namehash(""); diff --git a/src/plugins/base.eth/handlers/Registrar.ts b/src/plugins/base.eth/handlers/Registrar.ts index 00d747a1..26a58e07 100644 --- a/src/plugins/base.eth/handlers/Registrar.ts +++ b/src/plugins/base.eth/handlers/Registrar.ts @@ -3,7 +3,7 @@ import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/subname-helpers"; import { upsertAccount } from "../../../lib/upserts"; -import { managedSubname, ponderNamespace } from "../ponder.config"; +import { indexedSubname, pluginNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -11,16 +11,16 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, - managedSubnameNode, -} = makeRegistryHandlers(managedSubname); + indexedSubnameNode, +} = makeRegistryHandlers(indexedSubname); export default function () { // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers - ponder.on(ponderNamespace("BaseRegistrar:NameRegisteredWithRecord"), async ({ context, event }) => + ponder.on(pluginNamespace("BaseRegistrar:NameRegisteredWithRecord"), async ({ context, event }) => handleNameRegistered({ context, event }), ); - ponder.on(ponderNamespace("BaseRegistrar:NameRegistered"), async ({ context, event }) => { + ponder.on(pluginNamespace("BaseRegistrar:NameRegistered"), async ({ context, event }) => { // base has 'preminted' names via Registrar#registerOnly, which explicitly does not update Registry. // this breaks a subgraph assumption, as it expects a domain to exist (via Registry:NewOwner) before // any Registrar:NameRegistered events. in the future we will likely happily upsert domains, but @@ -28,7 +28,7 @@ export default function () { // allowing the base indexer to progress. const { id, owner } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(managedSubnameNode, label); + const node = makeSubnodeNamehash(indexedSubnameNode, label); await upsertAccount(context, owner); await context.db .insert(domains) @@ -42,17 +42,17 @@ export default function () { // after ensuring the domain exists, continue with the standard handler return handleNameRegistered({ context, event }); }); - ponder.on(ponderNamespace("BaseRegistrar:NameRenewed"), handleNameRenewed); + ponder.on(pluginNamespace("BaseRegistrar:NameRenewed"), handleNameRenewed); // Base's BaseRegistrar uses `id` instead of `tokenId` - ponder.on(ponderNamespace("BaseRegistrar:Transfer"), async ({ context, event }) => { + ponder.on(pluginNamespace("BaseRegistrar:Transfer"), async ({ context, event }) => { return await handleNameTransferred({ context, args: { ...event.args, tokenId: event.args.id }, }); }); - ponder.on(ponderNamespace("EARegistrarController:NameRegistered"), async ({ context, event }) => { + ponder.on(pluginNamespace("EARegistrarController:NameRegistered"), async ({ context, event }) => { // TODO: registration expected here return handleNameRegisteredByController({ @@ -61,7 +61,7 @@ export default function () { }); }); - ponder.on(ponderNamespace("RegistrarController:NameRegistered"), async ({ context, event }) => { + ponder.on(pluginNamespace("RegistrarController:NameRegistered"), async ({ context, event }) => { // TODO: registration expected here return handleNameRegisteredByController({ @@ -70,7 +70,7 @@ export default function () { }); }); - ponder.on(ponderNamespace("RegistrarController:NameRenewed"), async ({ context, event }) => { + ponder.on(pluginNamespace("RegistrarController:NameRenewed"), async ({ context, event }) => { return handleNameRenewedByController({ context, args: { ...event.args, cost: 0n }, diff --git a/src/plugins/base.eth/handlers/Registry.ts b/src/plugins/base.eth/handlers/Registry.ts index 1a3be61a..57f8fd50 100644 --- a/src/plugins/base.eth/handlers/Registry.ts +++ b/src/plugins/base.eth/handlers/Registry.ts @@ -6,12 +6,12 @@ import { handleTransfer, setupRootNode, } from "../../../handlers/Registry"; -import { ponderNamespace } from "../ponder.config"; +import { pluginNamespace } from "../ponder.config"; export default function () { - ponder.on(ponderNamespace("Registry:setup"), setupRootNode); - ponder.on(ponderNamespace("Registry:NewOwner"), handleNewOwner(true)); - ponder.on(ponderNamespace("Registry:NewResolver"), handleNewResolver); - ponder.on(ponderNamespace("Registry:NewTTL"), handleNewTTL); - ponder.on(ponderNamespace("Registry:Transfer"), handleTransfer); + ponder.on(pluginNamespace("Registry:setup"), setupRootNode); + ponder.on(pluginNamespace("Registry:NewOwner"), handleNewOwner(true)); + ponder.on(pluginNamespace("Registry:NewResolver"), handleNewResolver); + ponder.on(pluginNamespace("Registry:NewTTL"), handleNewTTL); + ponder.on(pluginNamespace("Registry:Transfer"), handleTransfer); } diff --git a/src/plugins/base.eth/handlers/Resolver.ts b/src/plugins/base.eth/handlers/Resolver.ts index 2fdba189..e1a30e0c 100644 --- a/src/plugins/base.eth/handlers/Resolver.ts +++ b/src/plugins/base.eth/handlers/Resolver.ts @@ -13,7 +13,7 @@ import { handleTextChanged, handleVersionChanged, } from "../../../handlers/Resolver"; -import { ponderNamespace as ns } from "../ponder.config"; +import { pluginNamespace as ns } from "../ponder.config"; export default function () { // New registry handlers diff --git a/src/plugins/base.eth/ponder.config.ts b/src/plugins/base.eth/ponder.config.ts index 5d5d9638..d74947c5 100644 --- a/src/plugins/base.eth/ponder.config.ts +++ b/src/plugins/base.eth/ponder.config.ts @@ -2,16 +2,16 @@ import { createConfig, factory } from "ponder"; import { http, getAbiItem } from "viem"; import { base } from "viem/chains"; -import { createPonderNamespace } from "../../lib/ponder-plugin-utils"; +import { createPluginNamespace } from "../../lib/ponder-plugin-utils"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -export const managedSubname = "base.eth" as const; +export const indexedSubname = "base.eth" as const; -export const ponderNamespace = createPonderNamespace(managedSubname); +export const pluginNamespace = createPluginNamespace(indexedSubname); export const config = createConfig({ networks: { @@ -21,13 +21,13 @@ export const config = createConfig({ }, }, contracts: { - [ponderNamespace("Registry")]: { + [pluginNamespace("Registry")]: { network: "base", abi: Registry, address: "0xb94704422c2a1e396835a571837aa5ae53285a95", startBlock: 17571480, }, - [ponderNamespace("Resolver")]: { + [pluginNamespace("Resolver")]: { network: "base", abi: L2Resolver, address: factory({ @@ -37,19 +37,19 @@ export const config = createConfig({ }), startBlock: 17575714, }, - [ponderNamespace("BaseRegistrar")]: { + [pluginNamespace("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, address: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a", startBlock: 17571486, }, - [ponderNamespace("EARegistrarController")]: { + [pluginNamespace("EARegistrarController")]: { network: "base", abi: EarlyAccessRegistrarController, address: "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda", startBlock: 17575699, }, - [ponderNamespace("RegistrarController")]: { + [pluginNamespace("RegistrarController")]: { network: "base", abi: RegistrarController, address: "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5", diff --git a/src/plugins/eth/handlers/EthRegistrar.ts b/src/plugins/eth/handlers/EthRegistrar.ts index 9ccd8342..596ce069 100644 --- a/src/plugins/eth/handlers/EthRegistrar.ts +++ b/src/plugins/eth/handlers/EthRegistrar.ts @@ -1,6 +1,6 @@ import { ponder } from "ponder:registry"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { managedSubname, ponderNamespace } from "../ponder.config"; +import { indexedSubname, pluginNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -8,32 +8,32 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(managedSubname); +} = makeRegistryHandlers(indexedSubname); export default function () { - ponder.on(ponderNamespace("BaseRegistrar:NameRegistered"), handleNameRegistered); - ponder.on(ponderNamespace("BaseRegistrar:NameRenewed"), handleNameRenewed); + ponder.on(pluginNamespace("BaseRegistrar:NameRegistered"), handleNameRegistered); + ponder.on(pluginNamespace("BaseRegistrar:NameRenewed"), handleNameRenewed); - ponder.on(ponderNamespace("BaseRegistrar:Transfer"), async ({ context, event }) => { + ponder.on(pluginNamespace("BaseRegistrar:Transfer"), async ({ context, event }) => { return await handleNameTransferred({ context, args: event.args }); }); ponder.on( - ponderNamespace("EthRegistrarControllerOld:NameRegistered"), + pluginNamespace("EthRegistrarControllerOld:NameRegistered"), async ({ context, event }) => { // the old registrar controller just had `cost` param return await handleNameRegisteredByController({ context, args: event.args }); }, ); ponder.on( - ponderNamespace("EthRegistrarControllerOld:NameRenewed"), + pluginNamespace("EthRegistrarControllerOld:NameRenewed"), async ({ context, event }) => { return await handleNameRenewedByController({ context, args: event.args }); }, ); ponder.on( - ponderNamespace("EthRegistrarController:NameRegistered"), + pluginNamespace("EthRegistrarController:NameRegistered"), async ({ context, event }) => { // the new registrar controller uses baseCost + premium to compute cost return await handleNameRegisteredByController({ @@ -45,7 +45,7 @@ export default function () { }); }, ); - ponder.on(ponderNamespace("EthRegistrarController:NameRenewed"), async ({ context, event }) => { + ponder.on(pluginNamespace("EthRegistrarController:NameRenewed"), async ({ context, event }) => { return await handleNameRenewedByController({ context, args: event.args }); }); } diff --git a/src/plugins/eth/handlers/Registry.ts b/src/plugins/eth/handlers/Registry.ts index d1fded23..b3e2064e 100644 --- a/src/plugins/eth/handlers/Registry.ts +++ b/src/plugins/eth/handlers/Registry.ts @@ -9,7 +9,7 @@ import { setupRootNode, } from "../../../handlers/Registry"; import { makeSubnodeNamehash } from "../../../lib/subname-helpers"; -import { ponderNamespace } from "../ponder.config"; +import { pluginNamespace } from "../ponder.config"; // a domain is migrated iff it exists and isMigrated is set to true, otherwise it is not async function isDomainMigrated(context: Context, node: Hex) { @@ -18,18 +18,18 @@ async function isDomainMigrated(context: Context, node: Hex) { } export default function () { - ponder.on(ponderNamespace("RegistryOld:setup"), setupRootNode); + ponder.on(pluginNamespace("RegistryOld:setup"), setupRootNode); // old registry functions are proxied to the current handlers // iff the domain has not yet been migrated - ponder.on(ponderNamespace("RegistryOld:NewOwner"), async ({ context, event }) => { + ponder.on(pluginNamespace("RegistryOld:NewOwner"), async ({ context, event }) => { const node = makeSubnodeNamehash(event.args.node, event.args.label); const isMigrated = await isDomainMigrated(context, node); if (isMigrated) return; return handleNewOwner(false)({ context, event }); }); - ponder.on(ponderNamespace("RegistryOld:NewResolver"), async ({ context, event }) => { + ponder.on(pluginNamespace("RegistryOld:NewResolver"), async ({ context, event }) => { // NOTE: the subgraph makes an exception for the root node here // but i don't know that that's necessary, as in ponder our root node starts out // unmigrated and once the NewOwner event is emitted by the new registry, @@ -42,20 +42,20 @@ export default function () { return handleNewResolver({ context, event }); }); - ponder.on(ponderNamespace("RegistryOld:NewTTL"), async ({ context, event }) => { + ponder.on(pluginNamespace("RegistryOld:NewTTL"), async ({ context, event }) => { const isMigrated = await isDomainMigrated(context, event.args.node); if (isMigrated) return; return handleNewTTL({ context, event }); }); - ponder.on(ponderNamespace("RegistryOld:Transfer"), async ({ context, event }) => { + ponder.on(pluginNamespace("RegistryOld:Transfer"), async ({ context, event }) => { const isMigrated = await isDomainMigrated(context, event.args.node); if (isMigrated) return; return handleTransfer({ context, event }); }); - ponder.on(ponderNamespace("Registry:NewOwner"), handleNewOwner(true)); - ponder.on(ponderNamespace("Registry:NewResolver"), handleNewResolver); - ponder.on(ponderNamespace("Registry:NewTTL"), handleNewTTL); - ponder.on(ponderNamespace("Registry:Transfer"), handleTransfer); + ponder.on(pluginNamespace("Registry:NewOwner"), handleNewOwner(true)); + ponder.on(pluginNamespace("Registry:NewResolver"), handleNewResolver); + ponder.on(pluginNamespace("Registry:NewTTL"), handleNewTTL); + ponder.on(pluginNamespace("Registry:Transfer"), handleTransfer); } diff --git a/src/plugins/eth/handlers/Resolver.ts b/src/plugins/eth/handlers/Resolver.ts index 122eb7d0..e7e0046a 100644 --- a/src/plugins/eth/handlers/Resolver.ts +++ b/src/plugins/eth/handlers/Resolver.ts @@ -14,61 +14,61 @@ import { handleTextChanged, handleVersionChanged, } from "../../../handlers/Resolver"; -import { ponderNamespace } from "../ponder.config"; +import { pluginNamespace } from "../ponder.config"; export default function () { // Old registry handlers - ponder.on(ponderNamespace("OldRegistryResolvers:AddrChanged"), handleAddrChanged); - ponder.on(ponderNamespace("OldRegistryResolvers:AddressChanged"), handleAddressChanged); - ponder.on(ponderNamespace("OldRegistryResolvers:NameChanged"), handleNameChanged); - ponder.on(ponderNamespace("OldRegistryResolvers:ABIChanged"), handleABIChanged); - ponder.on(ponderNamespace("OldRegistryResolvers:PubkeyChanged"), handlePubkeyChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:AddrChanged"), handleAddrChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:AddressChanged"), handleAddressChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:NameChanged"), handleNameChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:ABIChanged"), handleABIChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:PubkeyChanged"), handlePubkeyChanged); ponder.on( - ponderNamespace( + pluginNamespace( "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", ), handleTextChanged, ); ponder.on( - ponderNamespace( + pluginNamespace( "OldRegistryResolvers:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", ), handleTextChanged, ); - ponder.on(ponderNamespace("OldRegistryResolvers:ContenthashChanged"), handleContenthashChanged); - ponder.on(ponderNamespace("OldRegistryResolvers:InterfaceChanged"), handleInterfaceChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:ContenthashChanged"), handleContenthashChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:InterfaceChanged"), handleInterfaceChanged); ponder.on( - ponderNamespace("OldRegistryResolvers:AuthorisationChanged"), + pluginNamespace("OldRegistryResolvers:AuthorisationChanged"), handleAuthorisationChanged, ); - ponder.on(ponderNamespace("OldRegistryResolvers:VersionChanged"), handleVersionChanged); - ponder.on(ponderNamespace("OldRegistryResolvers:DNSRecordChanged"), handleDNSRecordChanged); - ponder.on(ponderNamespace("OldRegistryResolvers:DNSRecordDeleted"), handleDNSRecordDeleted); - ponder.on(ponderNamespace("OldRegistryResolvers:DNSZonehashChanged"), handleDNSZonehashChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:VersionChanged"), handleVersionChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:DNSRecordChanged"), handleDNSRecordChanged); + ponder.on(pluginNamespace("OldRegistryResolvers:DNSRecordDeleted"), handleDNSRecordDeleted); + ponder.on(pluginNamespace("OldRegistryResolvers:DNSZonehashChanged"), handleDNSZonehashChanged); // New registry handlers - ponder.on(ponderNamespace("Resolver:AddrChanged"), handleAddrChanged); - ponder.on(ponderNamespace("Resolver:AddressChanged"), handleAddressChanged); - ponder.on(ponderNamespace("Resolver:NameChanged"), handleNameChanged); - ponder.on(ponderNamespace("Resolver:ABIChanged"), handleABIChanged); - ponder.on(ponderNamespace("Resolver:PubkeyChanged"), handlePubkeyChanged); + ponder.on(pluginNamespace("Resolver:AddrChanged"), handleAddrChanged); + ponder.on(pluginNamespace("Resolver:AddressChanged"), handleAddressChanged); + ponder.on(pluginNamespace("Resolver:NameChanged"), handleNameChanged); + ponder.on(pluginNamespace("Resolver:ABIChanged"), handleABIChanged); + ponder.on(pluginNamespace("Resolver:PubkeyChanged"), handlePubkeyChanged); ponder.on( - ponderNamespace( + pluginNamespace( "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key)", ), handleTextChanged, ); ponder.on( - ponderNamespace( + pluginNamespace( "Resolver:TextChanged(bytes32 indexed node, string indexed indexedKey, string key, string value)", ), handleTextChanged, ); - ponder.on(ponderNamespace("Resolver:ContenthashChanged"), handleContenthashChanged); - ponder.on(ponderNamespace("Resolver:InterfaceChanged"), handleInterfaceChanged); - ponder.on(ponderNamespace("Resolver:AuthorisationChanged"), handleAuthorisationChanged); - ponder.on(ponderNamespace("Resolver:VersionChanged"), handleVersionChanged); - ponder.on(ponderNamespace("Resolver:DNSRecordChanged"), handleDNSRecordChanged); - ponder.on(ponderNamespace("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); - ponder.on(ponderNamespace("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); + ponder.on(pluginNamespace("Resolver:ContenthashChanged"), handleContenthashChanged); + ponder.on(pluginNamespace("Resolver:InterfaceChanged"), handleInterfaceChanged); + ponder.on(pluginNamespace("Resolver:AuthorisationChanged"), handleAuthorisationChanged); + ponder.on(pluginNamespace("Resolver:VersionChanged"), handleVersionChanged); + ponder.on(pluginNamespace("Resolver:DNSRecordChanged"), handleDNSRecordChanged); + ponder.on(pluginNamespace("Resolver:DNSRecordDeleted"), handleDNSRecordDeleted); + ponder.on(pluginNamespace("Resolver:DNSZonehashChanged"), handleDNSZonehashChanged); } diff --git a/src/plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts index 5d8a2f94..5e674103 100644 --- a/src/plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -2,7 +2,7 @@ import { createConfig, factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; import { mainnet } from "viem/chains"; -import { createPonderNamespace } from "../../lib/ponder-plugin-utils"; +import { createPluginNamespace } from "../../lib/ponder-plugin-utils"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; @@ -13,9 +13,9 @@ import { Resolver } from "./abis/Resolver"; const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); -export const managedSubname = "eth"; +export const indexedSubname = "eth"; -export const ponderNamespace = createPonderNamespace(managedSubname); +export const pluginNamespace = createPluginNamespace(indexedSubname); export const config = createConfig({ networks: { @@ -25,19 +25,19 @@ export const config = createConfig({ }, }, contracts: { - [ponderNamespace("RegistryOld")]: { + [pluginNamespace("RegistryOld")]: { network: "mainnet", abi: Registry, address: "0x314159265dd8dbb310642f98f50c066173c1259b", startBlock: 3327417, }, - [ponderNamespace("Registry")]: { + [pluginNamespace("Registry")]: { network: "mainnet", abi: Registry, address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", startBlock: 9380380, }, - [ponderNamespace("OldRegistryResolvers")]: { + [pluginNamespace("OldRegistryResolvers")]: { network: "mainnet", abi: RESOLVER_ABI, address: factory({ @@ -47,7 +47,7 @@ export const config = createConfig({ }), startBlock: 9380380, }, - [ponderNamespace("Resolver")]: { + [pluginNamespace("Resolver")]: { network: "mainnet", abi: RESOLVER_ABI, address: factory({ @@ -57,25 +57,25 @@ export const config = createConfig({ }), startBlock: 9380380, }, - [ponderNamespace("BaseRegistrar")]: { + [pluginNamespace("BaseRegistrar")]: { network: "mainnet", abi: BaseRegistrar, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", startBlock: 9380410, }, - [ponderNamespace("EthRegistrarControllerOld")]: { + [pluginNamespace("EthRegistrarControllerOld")]: { network: "mainnet", abi: EthRegistrarControllerOld, address: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", startBlock: 9380471, }, - [ponderNamespace("EthRegistrarController")]: { + [pluginNamespace("EthRegistrarController")]: { network: "mainnet", abi: EthRegistrarController, address: "0x253553366Da8546fC250F225fe3d25d0C782303b", startBlock: 16925618, }, - [ponderNamespace("NameWrapper")]: { + [pluginNamespace("NameWrapper")]: { network: "mainnet", abi: NameWrapper, address: "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401", From 7c4bc1e3476ba5d78375e0b3b9d60292d0e0cbfb Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 16:01:37 +0100 Subject: [PATCH 26/30] refactor(plugins): rename plugin helpers file --- src/lib/{ponder-plugin-utils.ts => plugin-helpers.ts} | 0 src/plugins/base.eth/ponder.config.ts | 2 +- src/plugins/eth/ponder.config.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/lib/{ponder-plugin-utils.ts => plugin-helpers.ts} (100%) diff --git a/src/lib/ponder-plugin-utils.ts b/src/lib/plugin-helpers.ts similarity index 100% rename from src/lib/ponder-plugin-utils.ts rename to src/lib/plugin-helpers.ts diff --git a/src/plugins/base.eth/ponder.config.ts b/src/plugins/base.eth/ponder.config.ts index d74947c5..304ae4dc 100644 --- a/src/plugins/base.eth/ponder.config.ts +++ b/src/plugins/base.eth/ponder.config.ts @@ -2,7 +2,7 @@ import { createConfig, factory } from "ponder"; import { http, getAbiItem } from "viem"; import { base } from "viem/chains"; -import { createPluginNamespace } from "../../lib/ponder-plugin-utils"; +import { createPluginNamespace } from "../../lib/plugin-helpers"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EarlyAccessRegistrarController } from "./abis/EARegistrarController"; import { L2Resolver } from "./abis/L2Resolver"; diff --git a/src/plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts index 5e674103..fa1b0402 100644 --- a/src/plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -2,7 +2,7 @@ import { createConfig, factory, mergeAbis } from "ponder"; import { http, getAbiItem } from "viem"; import { mainnet } from "viem/chains"; -import { createPluginNamespace } from "../../lib/ponder-plugin-utils"; +import { createPluginNamespace } from "../../lib/plugin-helpers"; import { BaseRegistrar } from "./abis/BaseRegistrar"; import { EthRegistrarController } from "./abis/EthRegistrarController"; import { EthRegistrarControllerOld } from "./abis/EthRegistrarControllerOld"; From 9891ed0906bbf5ac4f44a85a416ad1a89cb86cf0 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 16:11:48 +0100 Subject: [PATCH 27/30] refactor(plugins): rename ponder plugins to just plugins --- src/lib/plugin-helpers.ts | 72 ++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/lib/plugin-helpers.ts b/src/lib/plugin-helpers.ts index 4d4e3dc2..ac242fcc 100644 --- a/src/lib/plugin-helpers.ts +++ b/src/lib/plugin-helpers.ts @@ -1,64 +1,80 @@ /** - * A factory function that returns a function to create a name-spaced contract name for Ponder indexing handlers. + * A factory function that returns a function to create a namespaced contract + * name for Ponder indexing handlers. * - * Ponder config requires a flat dictionary of contract config entires, where each entry has its unique name and set of EVM event names derived from the contract's ABI. - * Ponder will use contract names and their respective event names to create names for indexing handlers. - * For example, a contract named `Registry` includes events: `NewResolver` and `NewTTL`. Ponder will create indexing handlers named `Registry:NewResolver` and `Registry:NewTTL`. + * Ponder config requires a flat dictionary of contract config entires, where + * each entry has its unique name and set of EVM event names derived from + * the contract's ABI. Ponder will use contract names and their respective + * event names to create names for indexing handlers. For example, a contract + * named `Registry` includes events: `NewResolver` and `NewTTL`. Ponder will + * create indexing handlers named `Registry:NewResolver` and `Registry:NewTTL`. * - * However, in some cases, we may want to create a name-spaced contract name to distinguish between contracts having the same name, but handling different implementations. + * However, in some cases, we may want to create a namespaced contract name to + * distinguish between contracts having the same name, but handling different + * implementations. * - * Let's say we have two contracts named `Registry`. One handles the `eth` name, and the other handles the `base.eth` subname. We need to create a name-spaced contract name to avoid conflicts. - * We could use the actual name/subname as a prefix, like `eth/Registry` and `base.eth/Registry`. We cannot do that, though, as Ponder does not support dots and colons in its indexing handler names. + * Let's say we have two contracts named `Registry`. One handles the `eth` name + * and the other handles the `base.eth` subname. We need to create a namespaced + * contract name to avoid conflicts. + * We could use the actual name/subname as a prefix, like `eth/Registry` and + * `base.eth/Registry`. We cannot do that, though, as Ponder does not support + * dots and colons in its indexing handler names. * - * We need to use a different separator, in this case, a forward slash in a path-like format. + * We need to use a different separator, in this case, a forward slash within + * a path-like format. * * @param subname * * @example * ```ts + * const boxNs = createPluginNamespace("box"); * const ethNs = createPluginNamespace("base.eth"); * const baseEthNs = createPluginNamespace("base.eth"); * + * boxNs("Registry"); // returns "/box/Registry" * ethNs("Registry"); // returns "/eth/Registry" * baseEthNs("Registry"); // returns "/base/eth/Registry" * ``` */ -export function createPluginNamespace(subname: Subname) { - const path = transformDomain(subname) satisfies PonderNsPath; +export function createPluginNamespace(subname: Subname) { + const namespacePath = nameIntoPath(subname) satisfies PluginNamespacePath; - /** Creates a name-spaced contract name */ + /** Creates a namespaced contract name */ return function pluginNamespace( contractName: ContractName, - ): PonderNamespaceReturnType { - return `${path}/${contractName}`; + ): PluginNamespaceReturnType { + return `${namespacePath}/${contractName}`; }; } -type TransformDomain = T extends `${infer Sub}.${infer Rest}` - ? `/${TransformDomain}/${Sub}` - : `/${T}`; +type TransformNameIntoPath = Name extends `${infer Sub}.${infer Rest}` + ? `/${TransformNameIntoPath}/${Sub}` + : `/${Name}`; /** - * Transforms a domain name into a path-like format + * Transforms a name into a path-like format, by reversing the name parts and + * joining them with a forward slash. The name parts are separated by a dot. * - * @param domain + * @param name is made of dot-separated labels * @returns path-like format of the reversed domain * * @example * ```ts - * transformDomain("base.eth"); // returns "/eth/base" + * nameIntoPath("base.eth"); // returns "/eth/base" + * nameIntoPath("my.box"); // returns "/box/my" **/ -function transformDomain(domain: T): TransformDomain { - const parts = domain.split(".").reverse(); - return `/${parts.join("/")}` as TransformDomain; +function nameIntoPath(name: Name): TransformNameIntoPath { + // TODO: validate the name + return `/${name.split(".").reverse().join("/")}` as TransformNameIntoPath; } /** The return type of the `pluginNamespace` function */ -export type PonderNamespaceReturnType< +type PluginNamespaceReturnType< ContractName extends string, - NsPath extends PonderNsPath, -> = `${NsPath}/${ContractName}`; + NamespacePath extends PluginNamespacePath, +> = `${NamespacePath}/${ContractName}`; -type PonderNsPath = `` | `/${string}` | `/${string}${T}`; - -type EthSubname = `${string}eth`; +type PluginNamespacePath = + | `` + | `/${string}` + | `/${string}${T}`; From 7b811616e373e176e2e34a13a155092dc727913c Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 16:26:59 +0100 Subject: [PATCH 28/30] refactor(plugins): drop the ACTIVE_PLUGIN env var parser Parser is not required, as we do the validation in the ponder.config.ts and throw an error if the provided value was not expected. --- ponder.config.ts | 2 +- src/lib/plugin-helpers.ts | 7 +++++-- src/lib/subname-helpers.ts | 16 ---------------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/ponder.config.ts b/ponder.config.ts index eae080af..8f25e9f2 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,4 +1,4 @@ -import { ACTIVE_PLUGIN } from "./src/lib/subname-helpers"; +import { ACTIVE_PLUGIN } from "./src/lib/plugin-helpers"; import { activate as activateEthBase, config as ethBaseConfig, diff --git a/src/lib/plugin-helpers.ts b/src/lib/plugin-helpers.ts index ac242fcc..9d0b0ce8 100644 --- a/src/lib/plugin-helpers.ts +++ b/src/lib/plugin-helpers.ts @@ -13,8 +13,8 @@ * distinguish between contracts having the same name, but handling different * implementations. * - * Let's say we have two contracts named `Registry`. One handles the `eth` name - * and the other handles the `base.eth` subname. We need to create a namespaced + * Let's say we have two contracts named `Registry`. One handles `eth` subnames + * and the other handles `base.eth` subnames. We need to create a namespaced * contract name to avoid conflicts. * We could use the actual name/subname as a prefix, like `eth/Registry` and * `base.eth/Registry`. We cannot do that, though, as Ponder does not support @@ -78,3 +78,6 @@ type PluginNamespacePath = | `` | `/${string}` | `/${string}${T}`; + +/** @var the requested active plugin name (see `src/plugins` for available plugins) */ +export const ACTIVE_PLUGIN = process.env.ACTIVE_PLUGIN; diff --git a/src/lib/subname-helpers.ts b/src/lib/subname-helpers.ts index 4128c81c..a00b6edd 100644 --- a/src/lib/subname-helpers.ts +++ b/src/lib/subname-helpers.ts @@ -1,21 +1,5 @@ import { type Hex, concat, keccak256, namehash, toHex } from "viem"; -class IndexedSubname { - private constructor(private readonly name: `${string}eth`) {} - - static parse(name: string | undefined = "") { - if (!name.endsWith("eth")) throw new Error(`IndexedSubname should end with 'eth'`); - - return new IndexedSubname(name as `${string}eth`); - } - - toString() { - return this.name; - } -} - -export const ACTIVE_PLUGIN = IndexedSubname.parse(process.env.ACTIVE_PLUGIN).toString(); - // TODO: pull from ens utils lib or something export const ROOT_NODE = namehash(""); From 1dbe2a912af34d1792a5059f58badaf06c08bbcd Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 17:22:56 +0100 Subject: [PATCH 29/30] refactor(plugins): use `ownedSubname` name This allows to define a name managed by the given plugin. --- ponder.config.ts | 8 ++++---- src/handlers/Registrar.ts | 18 +++++++++--------- src/plugins/base.eth/handlers/Registrar.ts | 8 ++++---- src/plugins/base.eth/ponder.config.ts | 4 ++-- src/plugins/eth/handlers/EthRegistrar.ts | 4 ++-- src/plugins/eth/ponder.config.ts | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/ponder.config.ts b/ponder.config.ts index 8f25e9f2..6328544a 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -2,12 +2,12 @@ import { ACTIVE_PLUGIN } from "./src/lib/plugin-helpers"; import { activate as activateEthBase, config as ethBaseConfig, - indexedSubname as ethBaseIndexedSubname, + ownedSubname as ethBaseOwnedSubname, } from "./src/plugins/base.eth/ponder.config"; import { activate as activateEth, config as ethConfig, - indexedSubname as ethIndexedSubname, + ownedSubname as ethOwnedSubname, } from "./src/plugins/eth/ponder.config"; type AllConfigs = typeof ethConfig & typeof ethBaseConfig; @@ -17,10 +17,10 @@ type AllConfigs = typeof ethConfig & typeof ethBaseConfig; // config is run at runtime export default ((): AllConfigs => { switch (ACTIVE_PLUGIN) { - case ethIndexedSubname: + case ethOwnedSubname: activateEth(); return ethConfig as AllConfigs; - case ethBaseIndexedSubname: + case ethBaseOwnedSubname: activateEthBase(); return ethBaseConfig as AllConfigs; default: diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index b181d461..66994013 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -10,28 +10,28 @@ const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds /** * A factory function that returns Ponder indexing handlers for a specified subname. */ -export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { - const indexedSubnameNode = namehash(indexedSubname); +export const makeRegistryHandlers = (ownedSubname: `${string}eth`) => { + const ownedSubnameNode = namehash(ownedSubname); async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(ownedSubnameNode, label); const domain = await context.db.find(domains, { id: node }); if (!domain) throw new Error("domain expected"); if (domain.labelName !== name) { await context.db .update(domains, { id: node }) - .set({ labelName: name, name: `${name}${indexedSubname}` }); + .set({ labelName: name, name: `${name}${ownedSubname}` }); } await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); } return { - get indexedSubnameNode() { - return indexedSubnameNode; + get ownedSubnameNode() { + return ownedSubnameNode; }, async handleNameRegistered({ @@ -49,7 +49,7 @@ export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { await upsertAccount(context, owner); const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(ownedSubnameNode, label); // TODO: materialze labelName via rainbow tables ala Registry.ts const labelName = undefined; @@ -104,7 +104,7 @@ export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { const { id, expires } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(ownedSubnameNode, label); await context.db.update(registrations, { id: label }).set({ expiryDate: expires }); @@ -129,7 +129,7 @@ export const makeRegistryHandlers = (indexedSubname: `${string}eth`) => { await upsertAccount(context, to); const label = tokenIdToLabel(tokenId); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(ownedSubnameNode, label); const registration = await context.db.find(registrations, { id: label }); if (!registration) return; diff --git a/src/plugins/base.eth/handlers/Registrar.ts b/src/plugins/base.eth/handlers/Registrar.ts index 26a58e07..81d23675 100644 --- a/src/plugins/base.eth/handlers/Registrar.ts +++ b/src/plugins/base.eth/handlers/Registrar.ts @@ -3,7 +3,7 @@ import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/subname-helpers"; import { upsertAccount } from "../../../lib/upserts"; -import { indexedSubname, pluginNamespace } from "../ponder.config"; +import { ownedSubname, pluginNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -11,8 +11,8 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, - indexedSubnameNode, -} = makeRegistryHandlers(indexedSubname); + ownedSubnameNode, +} = makeRegistryHandlers(ownedSubname); export default function () { // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers @@ -28,7 +28,7 @@ export default function () { // allowing the base indexer to progress. const { id, owner } = event.args; const label = tokenIdToLabel(id); - const node = makeSubnodeNamehash(indexedSubnameNode, label); + const node = makeSubnodeNamehash(ownedSubnameNode, label); await upsertAccount(context, owner); await context.db .insert(domains) diff --git a/src/plugins/base.eth/ponder.config.ts b/src/plugins/base.eth/ponder.config.ts index 304ae4dc..75ecf540 100644 --- a/src/plugins/base.eth/ponder.config.ts +++ b/src/plugins/base.eth/ponder.config.ts @@ -9,9 +9,9 @@ import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -export const indexedSubname = "base.eth" as const; +export const ownedSubname = "base.eth" as const; -export const pluginNamespace = createPluginNamespace(indexedSubname); +export const pluginNamespace = createPluginNamespace(ownedSubname); export const config = createConfig({ networks: { diff --git a/src/plugins/eth/handlers/EthRegistrar.ts b/src/plugins/eth/handlers/EthRegistrar.ts index 596ce069..46efea25 100644 --- a/src/plugins/eth/handlers/EthRegistrar.ts +++ b/src/plugins/eth/handlers/EthRegistrar.ts @@ -1,6 +1,6 @@ import { ponder } from "ponder:registry"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { indexedSubname, pluginNamespace } from "../ponder.config"; +import { ownedSubname, pluginNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -8,7 +8,7 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(indexedSubname); +} = makeRegistryHandlers(ownedSubname); export default function () { ponder.on(pluginNamespace("BaseRegistrar:NameRegistered"), handleNameRegistered); diff --git a/src/plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts index fa1b0402..86e0b62a 100644 --- a/src/plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -13,9 +13,9 @@ import { Resolver } from "./abis/Resolver"; const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); -export const indexedSubname = "eth"; +export const ownedSubname = "eth"; -export const pluginNamespace = createPluginNamespace(indexedSubname); +export const pluginNamespace = createPluginNamespace(ownedSubname); export const config = createConfig({ networks: { From cc6f2c371da7da5cbaa027bf819bfb1676df8ac9 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Wed, 8 Jan 2025 17:27:20 +0100 Subject: [PATCH 30/30] refactor(plugins): rename `ownedSubname` to `ownedName` --- package.json | 3 +++ pnpm-lock.yaml | 21 ++++++++++++--------- ponder.config.ts | 8 ++++---- src/handlers/Registrar.ts | 6 +++--- src/plugins/base.eth/handlers/Registrar.ts | 4 ++-- src/plugins/base.eth/ponder.config.ts | 4 ++-- src/plugins/eth/handlers/EthRegistrar.ts | 4 ++-- src/plugins/eth/ponder.config.ts | 4 ++-- 8 files changed, 30 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index ffa63380..d052dd6a 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,8 @@ }, "engines": { "node": ">=18.14" + }, + "resolutions": { + "vite": "5.1.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88e44b13..9daa9d03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + vite: 5.1.8 + importers: .: @@ -1698,13 +1701,13 @@ packages: vite-tsconfig-paths@4.3.1: resolution: {integrity: sha512-cfgJwcGOsIxXOLU/nELPny2/LUD/lcf1IbfyeKTv2bsupVbTH/xpFtdQlBmIP1GEK2CjjLxYhFfB+QODFAx5aw==} peerDependencies: - vite: '*' + vite: 5.1.8 peerDependenciesMeta: vite: optional: true - vite@5.0.7: - resolution: {integrity: sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==} + vite@5.1.8: + resolution: {integrity: sha512-mB8ToUuSmzODSpENgvpFk2fTiU/YQ1tmcVJJ4WZbq4fPdGJkFNVcmVL5k7iDug6xzWjjuGDKAuSievIsD6H7Xw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2967,9 +2970,9 @@ snapshots: react: 18.3.1 stacktrace-parser: 0.1.10 viem: 2.21.57(typescript@5.7.2) - vite: 5.0.7(@types/node@20.17.10) + vite: 5.1.8(@types/node@20.17.10) vite-node: 1.0.2(@types/node@20.17.10) - vite-tsconfig-paths: 4.3.1(typescript@5.7.2)(vite@5.0.7(@types/node@20.17.10)) + vite-tsconfig-paths: 4.3.1(typescript@5.7.2)(vite@5.1.8(@types/node@20.17.10)) optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: @@ -3296,7 +3299,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.0.7(@types/node@20.17.10) + vite: 5.1.8(@types/node@20.17.10) transitivePeerDependencies: - '@types/node' - less @@ -3307,18 +3310,18 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.1(typescript@5.7.2)(vite@5.0.7(@types/node@20.17.10)): + vite-tsconfig-paths@4.3.1(typescript@5.7.2)(vite@5.1.8(@types/node@20.17.10)): dependencies: debug: 4.4.0(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.4(typescript@5.7.2) optionalDependencies: - vite: 5.0.7(@types/node@20.17.10) + vite: 5.1.8(@types/node@20.17.10) transitivePeerDependencies: - supports-color - typescript - vite@5.0.7(@types/node@20.17.10): + vite@5.1.8(@types/node@20.17.10): dependencies: esbuild: 0.19.12 postcss: 8.4.49 diff --git a/ponder.config.ts b/ponder.config.ts index 6328544a..771964df 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -2,12 +2,12 @@ import { ACTIVE_PLUGIN } from "./src/lib/plugin-helpers"; import { activate as activateEthBase, config as ethBaseConfig, - ownedSubname as ethBaseOwnedSubname, + ownedName as ethBaseOwnedName, } from "./src/plugins/base.eth/ponder.config"; import { activate as activateEth, config as ethConfig, - ownedSubname as ethOwnedSubname, + ownedName as ethOwnedName, } from "./src/plugins/eth/ponder.config"; type AllConfigs = typeof ethConfig & typeof ethBaseConfig; @@ -17,10 +17,10 @@ type AllConfigs = typeof ethConfig & typeof ethBaseConfig; // config is run at runtime export default ((): AllConfigs => { switch (ACTIVE_PLUGIN) { - case ethOwnedSubname: + case ethOwnedName: activateEth(); return ethConfig as AllConfigs; - case ethBaseOwnedSubname: + case ethBaseOwnedName: activateEthBase(); return ethBaseConfig as AllConfigs; default: diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index 66994013..2031c0cc 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -10,8 +10,8 @@ const GRACE_PERIOD_SECONDS = 7776000n; // 90 days in seconds /** * A factory function that returns Ponder indexing handlers for a specified subname. */ -export const makeRegistryHandlers = (ownedSubname: `${string}eth`) => { - const ownedSubnameNode = namehash(ownedSubname); +export const makeRegistryHandlers = (ownedName: `${string}eth`) => { + const ownedSubnameNode = namehash(ownedName); async function setNamePreimage(context: Context, name: string, label: Hex, cost: bigint) { if (!isLabelValid(name)) return; @@ -23,7 +23,7 @@ export const makeRegistryHandlers = (ownedSubname: `${string}eth`) => { if (domain.labelName !== name) { await context.db .update(domains, { id: node }) - .set({ labelName: name, name: `${name}${ownedSubname}` }); + .set({ labelName: name, name: `${name}${ownedName}` }); } await context.db.update(registrations, { id: label }).set({ labelName: name, cost }); diff --git a/src/plugins/base.eth/handlers/Registrar.ts b/src/plugins/base.eth/handlers/Registrar.ts index 81d23675..7ab0a0e5 100644 --- a/src/plugins/base.eth/handlers/Registrar.ts +++ b/src/plugins/base.eth/handlers/Registrar.ts @@ -3,7 +3,7 @@ import { domains } from "ponder:schema"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; import { makeSubnodeNamehash, tokenIdToLabel } from "../../../lib/subname-helpers"; import { upsertAccount } from "../../../lib/upserts"; -import { ownedSubname, pluginNamespace } from "../ponder.config"; +import { ownedName, pluginNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -12,7 +12,7 @@ const { handleNameRenewed, handleNameTransferred, ownedSubnameNode, -} = makeRegistryHandlers(ownedSubname); +} = makeRegistryHandlers(ownedName); export default function () { // support NameRegisteredWithRecord for BaseRegistrar as it used by Base's RegistrarControllers diff --git a/src/plugins/base.eth/ponder.config.ts b/src/plugins/base.eth/ponder.config.ts index 75ecf540..6340a2c8 100644 --- a/src/plugins/base.eth/ponder.config.ts +++ b/src/plugins/base.eth/ponder.config.ts @@ -9,9 +9,9 @@ import { L2Resolver } from "./abis/L2Resolver"; import { RegistrarController } from "./abis/RegistrarController"; import { Registry } from "./abis/Registry"; -export const ownedSubname = "base.eth" as const; +export const ownedName = "base.eth" as const; -export const pluginNamespace = createPluginNamespace(ownedSubname); +export const pluginNamespace = createPluginNamespace(ownedName); export const config = createConfig({ networks: { diff --git a/src/plugins/eth/handlers/EthRegistrar.ts b/src/plugins/eth/handlers/EthRegistrar.ts index 46efea25..b4c1249d 100644 --- a/src/plugins/eth/handlers/EthRegistrar.ts +++ b/src/plugins/eth/handlers/EthRegistrar.ts @@ -1,6 +1,6 @@ import { ponder } from "ponder:registry"; import { makeRegistryHandlers } from "../../../handlers/Registrar"; -import { ownedSubname, pluginNamespace } from "../ponder.config"; +import { ownedName, pluginNamespace } from "../ponder.config"; const { handleNameRegistered, @@ -8,7 +8,7 @@ const { handleNameRenewedByController, handleNameRenewed, handleNameTransferred, -} = makeRegistryHandlers(ownedSubname); +} = makeRegistryHandlers(ownedName); export default function () { ponder.on(pluginNamespace("BaseRegistrar:NameRegistered"), handleNameRegistered); diff --git a/src/plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts index 86e0b62a..5b0e960b 100644 --- a/src/plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -13,9 +13,9 @@ import { Resolver } from "./abis/Resolver"; const RESOLVER_ABI = mergeAbis([LegacyPublicResolver, Resolver]); -export const ownedSubname = "eth"; +export const ownedName = "eth"; -export const pluginNamespace = createPluginNamespace(ownedSubname); +export const pluginNamespace = createPluginNamespace(ownedName); export const config = createConfig({ networks: {