From d124b34664d1b0129e4f031f5957e06066307cda Mon Sep 17 00:00:00 2001 From: "lightwalker.eth" <126201998+lightwalker-eth@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:50:48 +0400 Subject: [PATCH 1/7] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2364136..e99bc96 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ens-multichain indexer +# ENSNode -> powered by ponder +> a multichain ENS indexer, powered by Ponder estimated backfill time @ 50rps = 24-36 hours on M1 Macbook (~10x speedup) @@ -14,7 +14,7 @@ estimated backfill time @ 50rps = 24-36 hours on M1 Macbook (~10x speedup) - 1:1 equivalency of results for queries via ensjs - 100% ensjs, ens-app-v3 test suites passing - should 'just work', following [this documentation](https://github.com/ensdomains/ensjs/blob/main/docs/basics/custom-subgraph-uris.md) - - ensjs equivalency confirmed via [ens-indexer-transition-tools](https://github.com/namehash/ens-indexer-transition-tools) + - ensjs equivalency confirmed via [ens-subgraph-transition-tools](https://github.com/namehash/ens-subgraph-transition-tools) - v2 — **optimized multichain indexer w/ unified namespace** - true multichain indexing (mainnet, base, linea, etc) - flattened, unified, multichain namespace From 6ca4b4c43a9647592d32b48a895eec7abb8b8196 Mon Sep 17 00:00:00 2001 From: "lightwalker.eth" <126201998+lightwalker-eth@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:31:12 +0400 Subject: [PATCH 2/7] Create CODEOWNERS --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..463c97b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Default owners for everything in the repo + +* @shrugs @tk-o @djstrong @BanaSeba @lightwalker-eth From f7252dd629e9ad4162db59cb6aee718009ddf03e Mon Sep 17 00:00:00 2001 From: "lightwalker.eth" <126201998+lightwalker-eth@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:07:34 +0400 Subject: [PATCH 3/7] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1d3082b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 NameHash + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From 803bcd3e380cf44c258586fcb0d4f61bb9dec00f Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Thu, 16 Jan 2025 15:32:56 +0100 Subject: [PATCH 4/7] feat(partial-index): demonstrate working queries on partial index This partial index allow to focus on particular blocks and show how the GQL queries work. --- ponder.config.ts | 54 +++++++++++++++++++++++++- src/handlers/Registrar.ts | 10 ++++- src/handlers/Registry.ts | 31 ++++++++++++--- src/plugins/base.eth/ponder.config.ts | 22 +++++++---- src/plugins/eth/ponder.config.ts | 29 +++++++++----- src/plugins/linea.eth/ponder.config.ts | 22 +++++++---- 6 files changed, 138 insertions(+), 30 deletions(-) diff --git a/ponder.config.ts b/ponder.config.ts index bd983c9..f262d45 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,4 +1,4 @@ -import { deepMergeRecursive } from "./src/lib/helpers"; +// import { deepMergeRecursive } from "./src/lib/helpers"; import { type IntersectionOf, getActivePlugins } from "./src/lib/plugin-helpers"; import * as baseEthPlugin from "./src/plugins/base.eth/ponder.config"; import * as ethPlugin from "./src/plugins/eth/ponder.config"; @@ -30,3 +30,55 @@ function getActivePluginsConfig(): AllPluginsConfig { // The type of the default export is the intersection of all available plugin // configs so that each plugin can be correctly typechecked export default getActivePluginsConfig(); + +type AnyObject = { [key: string]: any }; + +/** + * Deep merge two objects recursively. + * @param target The target object to merge into. + * @param source The source object to merge from. + * @returns The merged object. + * @see https://stackoverflow.com/a/48218209 + * @example + * const obj1 = { a: 1, b: 2, c: { d: 3 } }; + * const obj2 = { a: 4, c: { e: 5 } }; + * const obj3 = deepMergeRecursive(obj1, obj2); + * // { a: 4, b: 2, c: { d: 3, e: 5 } } + */ +export function deepMergeRecursive( + target: T, + source: U, +): T & U { + const output = { ...target } as T & U; + + function isObject(item: any): item is AnyObject { + return item && typeof item === "object" && !Array.isArray(item); + } + + if (isObject(target) && isObject(source)) { + Object.keys(source).forEach((key) => { + if (isObject(source[key])) { + if (!(key in target)) { + Object.assign(output, { [key]: source[key] }); + } else { + (output as AnyObject)[key] = deepMergeRecursive( + (target as AnyObject)[key], + (source as AnyObject)[key], + ); + } + } else if (Array.isArray(source[key])) { + if (!(key in target)) { + Object.assign(output, { [key]: source[key] }); + } else { + (output as AnyObject)[key] = Array.isArray((target as AnyObject)[key]) + ? [...(target as AnyObject)[key], ...source[key]] + : source[key]; + } + } else { + Object.assign(output, { [key]: source[key] }); + } + }); + } + + return output; +} \ No newline at end of file diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index eb3057b..7fb64be 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -18,7 +18,9 @@ export const makeRegistrarHandlers = (ownedName: `${string}eth`) => { const node = makeSubnodeNamehash(ownedSubnameNode, label); const domain = await context.db.find(schema.domain, { id: node }); - if (!domain) throw new Error("domain expected"); + if (!domain) { + return; + } if (domain.labelName !== name) { await context.db @@ -106,6 +108,12 @@ export const makeRegistrarHandlers = (ownedName: `${string}eth`) => { const label = tokenIdToLabel(id); const node = makeSubnodeNamehash(ownedSubnameNode, label); + const registration = await context.db.find(schema.registration, { id: label }); + + if (!registration) { + return; + } + await context.db.update(schema.registration, { id: label }).set({ expiryDate: expires }); await context.db diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index 3b1a5fe..fb1c2d6 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -56,9 +56,17 @@ function isDomainEmpty(domain: typeof schema.domain.$inferSelect) { // 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(schema.domain, { id: node }); - if (!domain) throw new Error(`Domain not found: ${node}`); + if (!domain) { + return; + } if (isDomainEmpty(domain) && domain.parentId !== null) { + const parent = await context.db.find(schema.domain, { id: domain.parentId }); + + if (!parent) { + return; + } + // decrement parent's subdomain count await context.db .update(schema.domain, { id: domain.parentId }) @@ -132,10 +140,16 @@ export const handleNewOwner = isMigrated, }); - // and increment parent subdomainCount - await context.db - .update(schema.domain, { id: node }) - .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); + const parent = await context.db.find(schema.domain, { id: node }); + + // to support testing a partial-index use case, + // first ensure the parent domain exists before incrementing its subdomainCount + if (parent) { + // and increment parent subdomainCount + await context.db + .update(schema.domain, { id: parent.id }) + .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); + } } // if the domain doesn't yet have a name, construct it here @@ -206,6 +220,13 @@ export async function handleNewResolver({ }) .onConflictDoNothing(); + const domain = await context.db.find(schema.domain, { id: node }); + + if (!domain) { + console.error(`Domain not found: ${node}`); + return; + } + // update the domain to point to it, and denormalize the eth addr await context.db .update(schema.domain, { id: node }) diff --git a/src/plugins/base.eth/ponder.config.ts b/src/plugins/base.eth/ponder.config.ts index fb4eda2..f0aff1e 100644 --- a/src/plugins/base.eth/ponder.config.ts +++ b/src/plugins/base.eth/ponder.config.ts @@ -14,10 +14,13 @@ export const ownedName = "base.eth" as const; export const pluginNamespace = createPluginNamespace(ownedName); +// demo for `0xtko.base.eth` +// minted at block: 24_944_153 + // constrain the ponder indexing between the following start/end blocks // https://ponder.sh/0_6/docs/contracts-and-networks#block-range -const START_BLOCK: ContractConfig["startBlock"] = undefined; -const END_BLOCK: ContractConfig["endBlock"] = undefined; +const START_BLOCK: ContractConfig["startBlock"] = 24_943_153; +const END_BLOCK: ContractConfig["endBlock"] = 24_945_153; export const config = createConfig({ networks: { @@ -32,7 +35,8 @@ export const config = createConfig({ network: "base", abi: Registry, address: "0xb94704422c2a1e396835a571837aa5ae53285a95", - ...blockConfig(START_BLOCK, 17571480, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Resolver")]: { network: "base", @@ -42,25 +46,29 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 17575714, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, address: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a", - ...blockConfig(START_BLOCK, 17571486, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EARegistrarController")]: { network: "base", abi: EarlyAccessRegistrarController, address: "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda", - ...blockConfig(START_BLOCK, 17575699, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("RegistrarController")]: { network: "base", abi: RegistrarController, address: "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5", - ...blockConfig(START_BLOCK, 18619035, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, }, }); diff --git a/src/plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts index de40ae1..83bf9a6 100644 --- a/src/plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -18,9 +18,12 @@ export const ownedName = "eth"; export const pluginNamespace = createPluginNamespace(ownedName); +// demo for `didsendit.eth` +// minted at block: 21_610_362 + // constrain the ponder indexing between the following start/end blocks // https://ponder.sh/0_6/docs/contracts-and-networks#block-range -const START_BLOCK: ContractConfig["startBlock"] = undefined; +const START_BLOCK: ContractConfig["startBlock"] = 21_610_262; const END_BLOCK: ContractConfig["endBlock"] = undefined; export const config = createConfig({ @@ -36,13 +39,15 @@ export const config = createConfig({ network: "mainnet", abi: Registry, address: "0x314159265dd8dbb310642f98f50c066173c1259b", - ...blockConfig(START_BLOCK, 3327417, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Registry")]: { network: "mainnet", abi: Registry, address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", - ...blockConfig(START_BLOCK, 9380380, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("OldRegistryResolvers")]: { network: "mainnet", @@ -52,7 +57,8 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 9380380, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Resolver")]: { network: "mainnet", @@ -62,31 +68,36 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 9380380, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("BaseRegistrar")]: { network: "mainnet", abi: BaseRegistrar, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", - ...blockConfig(START_BLOCK, 9380410, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EthRegistrarControllerOld")]: { network: "mainnet", abi: EthRegistrarControllerOld, address: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", - ...blockConfig(START_BLOCK, 9380471, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EthRegistrarController")]: { network: "mainnet", abi: EthRegistrarController, address: "0x253553366Da8546fC250F225fe3d25d0C782303b", - ...blockConfig(START_BLOCK, 16925618, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("NameWrapper")]: { network: "mainnet", abi: NameWrapper, address: "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401", - ...blockConfig(START_BLOCK, 16925608, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, }, }); diff --git a/src/plugins/linea.eth/ponder.config.ts b/src/plugins/linea.eth/ponder.config.ts index 25626a8..da01735 100644 --- a/src/plugins/linea.eth/ponder.config.ts +++ b/src/plugins/linea.eth/ponder.config.ts @@ -14,10 +14,13 @@ export const ownedName = "linea.eth"; export const pluginNamespace = createPluginNamespace(ownedName); +// demo for `0xtko.linea.eth` +// minted at block: 14_491_235 + // constrain the ponder indexing between the following start/end blocks // https://ponder.sh/0_6/docs/contracts-and-networks#block-range -const START_BLOCK: ContractConfig["startBlock"] = undefined; -const END_BLOCK: ContractConfig["endBlock"] = undefined; +const START_BLOCK: ContractConfig["startBlock"] = 14_490_000; +const END_BLOCK: ContractConfig["endBlock"] = 14_492_000; export const config = createConfig({ networks: { @@ -32,7 +35,8 @@ export const config = createConfig({ network: "linea", abi: Registry, address: "0x50130b669B28C339991d8676FA73CF122a121267", - ...blockConfig(START_BLOCK, 6682888, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Resolver")]: { network: "linea", @@ -42,25 +46,29 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 6682888, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("BaseRegistrar")]: { network: "linea", abi: BaseRegistrar, address: "0x6e84390dCc5195414eC91A8c56A5c91021B95704", - ...blockConfig(START_BLOCK, 6682892, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EthRegistrarController")]: { network: "linea", abi: EthRegistrarController, address: "0xDb75Db974B1F2bD3b5916d503036208064D18295", - ...blockConfig(START_BLOCK, 6682978, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("NameWrapper")]: { network: "linea", abi: NameWrapper, address: "0xA53cca02F98D590819141Aa85C891e2Af713C223", - ...blockConfig(START_BLOCK, 6682956, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, }, }); From 610d318907b284e5116e3f44bff683d140f5ffe8 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Thu, 16 Jan 2025 15:32:56 +0100 Subject: [PATCH 5/7] feat(partial-index): demonstrate working queries on partial index This partial index allow to focus on particular blocks and show how the GQL queries work. --- ponder.config.ts | 54 +++++++++++++++++++++++++- src/handlers/Registrar.ts | 18 ++++++++- src/handlers/Registry.ts | 31 ++++++++++++--- src/plugins/base.eth/ponder.config.ts | 20 +++++++--- src/plugins/eth/ponder.config.ts | 29 +++++++++----- src/plugins/linea.eth/ponder.config.ts | 20 +++++++--- 6 files changed, 143 insertions(+), 29 deletions(-) diff --git a/ponder.config.ts b/ponder.config.ts index bd983c9..f262d45 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,4 +1,4 @@ -import { deepMergeRecursive } from "./src/lib/helpers"; +// import { deepMergeRecursive } from "./src/lib/helpers"; import { type IntersectionOf, getActivePlugins } from "./src/lib/plugin-helpers"; import * as baseEthPlugin from "./src/plugins/base.eth/ponder.config"; import * as ethPlugin from "./src/plugins/eth/ponder.config"; @@ -30,3 +30,55 @@ function getActivePluginsConfig(): AllPluginsConfig { // The type of the default export is the intersection of all available plugin // configs so that each plugin can be correctly typechecked export default getActivePluginsConfig(); + +type AnyObject = { [key: string]: any }; + +/** + * Deep merge two objects recursively. + * @param target The target object to merge into. + * @param source The source object to merge from. + * @returns The merged object. + * @see https://stackoverflow.com/a/48218209 + * @example + * const obj1 = { a: 1, b: 2, c: { d: 3 } }; + * const obj2 = { a: 4, c: { e: 5 } }; + * const obj3 = deepMergeRecursive(obj1, obj2); + * // { a: 4, b: 2, c: { d: 3, e: 5 } } + */ +export function deepMergeRecursive( + target: T, + source: U, +): T & U { + const output = { ...target } as T & U; + + function isObject(item: any): item is AnyObject { + return item && typeof item === "object" && !Array.isArray(item); + } + + if (isObject(target) && isObject(source)) { + Object.keys(source).forEach((key) => { + if (isObject(source[key])) { + if (!(key in target)) { + Object.assign(output, { [key]: source[key] }); + } else { + (output as AnyObject)[key] = deepMergeRecursive( + (target as AnyObject)[key], + (source as AnyObject)[key], + ); + } + } else if (Array.isArray(source[key])) { + if (!(key in target)) { + Object.assign(output, { [key]: source[key] }); + } else { + (output as AnyObject)[key] = Array.isArray((target as AnyObject)[key]) + ? [...(target as AnyObject)[key], ...source[key]] + : source[key]; + } + } else { + Object.assign(output, { [key]: source[key] }); + } + }); + } + + return output; +} \ No newline at end of file diff --git a/src/handlers/Registrar.ts b/src/handlers/Registrar.ts index eb3057b..bb288de 100644 --- a/src/handlers/Registrar.ts +++ b/src/handlers/Registrar.ts @@ -18,7 +18,9 @@ export const makeRegistrarHandlers = (ownedName: `${string}eth`) => { const node = makeSubnodeNamehash(ownedSubnameNode, label); const domain = await context.db.find(schema.domain, { id: node }); - if (!domain) throw new Error("domain expected"); + if (!domain) { + return; + } if (domain.labelName !== name) { await context.db @@ -106,6 +108,12 @@ export const makeRegistrarHandlers = (ownedName: `${string}eth`) => { const label = tokenIdToLabel(id); const node = makeSubnodeNamehash(ownedSubnameNode, label); + const registration = await context.db.find(schema.registration, { id: label }); + + if (!registration) { + return; + } + await context.db.update(schema.registration, { id: label }).set({ expiryDate: expires }); await context.db @@ -136,7 +144,13 @@ export const makeRegistrarHandlers = (ownedName: `${string}eth`) => { await context.db.update(schema.registration, { id: label }).set({ registrantId: to }); - await context.db.update(schema.domain, { id: node }).set({ registrantId: to }); + const domain = await context.db.find(schema.domain, { id: node }); + + if (!domain) { + return; + } + + await context.db.update(schema.domain, { id: domain.id }).set({ registrantId: to }); // TODO: log Event }, diff --git a/src/handlers/Registry.ts b/src/handlers/Registry.ts index 3b1a5fe..fb1c2d6 100644 --- a/src/handlers/Registry.ts +++ b/src/handlers/Registry.ts @@ -56,9 +56,17 @@ function isDomainEmpty(domain: typeof schema.domain.$inferSelect) { // 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(schema.domain, { id: node }); - if (!domain) throw new Error(`Domain not found: ${node}`); + if (!domain) { + return; + } if (isDomainEmpty(domain) && domain.parentId !== null) { + const parent = await context.db.find(schema.domain, { id: domain.parentId }); + + if (!parent) { + return; + } + // decrement parent's subdomain count await context.db .update(schema.domain, { id: domain.parentId }) @@ -132,10 +140,16 @@ export const handleNewOwner = isMigrated, }); - // and increment parent subdomainCount - await context.db - .update(schema.domain, { id: node }) - .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); + const parent = await context.db.find(schema.domain, { id: node }); + + // to support testing a partial-index use case, + // first ensure the parent domain exists before incrementing its subdomainCount + if (parent) { + // and increment parent subdomainCount + await context.db + .update(schema.domain, { id: parent.id }) + .set((row) => ({ subdomainCount: row.subdomainCount + 1 })); + } } // if the domain doesn't yet have a name, construct it here @@ -206,6 +220,13 @@ export async function handleNewResolver({ }) .onConflictDoNothing(); + const domain = await context.db.find(schema.domain, { id: node }); + + if (!domain) { + console.error(`Domain not found: ${node}`); + return; + } + // update the domain to point to it, and denormalize the eth addr await context.db .update(schema.domain, { id: node }) diff --git a/src/plugins/base.eth/ponder.config.ts b/src/plugins/base.eth/ponder.config.ts index fb4eda2..3cddb4d 100644 --- a/src/plugins/base.eth/ponder.config.ts +++ b/src/plugins/base.eth/ponder.config.ts @@ -14,9 +14,12 @@ export const ownedName = "base.eth" as const; export const pluginNamespace = createPluginNamespace(ownedName); +// demo for `0xtko.base.eth` +// minted at block: 24_944_153 + // constrain the ponder indexing between the following start/end blocks // https://ponder.sh/0_6/docs/contracts-and-networks#block-range -const START_BLOCK: ContractConfig["startBlock"] = undefined; +const START_BLOCK: ContractConfig["startBlock"] = 24_943_153; const END_BLOCK: ContractConfig["endBlock"] = undefined; export const config = createConfig({ @@ -32,7 +35,8 @@ export const config = createConfig({ network: "base", abi: Registry, address: "0xb94704422c2a1e396835a571837aa5ae53285a95", - ...blockConfig(START_BLOCK, 17571480, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Resolver")]: { network: "base", @@ -42,25 +46,29 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 17575714, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("BaseRegistrar")]: { network: "base", abi: BaseRegistrar, address: "0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a", - ...blockConfig(START_BLOCK, 17571486, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EARegistrarController")]: { network: "base", abi: EarlyAccessRegistrarController, address: "0xd3e6775ed9b7dc12b205c8e608dc3767b9e5efda", - ...blockConfig(START_BLOCK, 17575699, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("RegistrarController")]: { network: "base", abi: RegistrarController, address: "0x4cCb0BB02FCABA27e82a56646E81d8c5bC4119a5", - ...blockConfig(START_BLOCK, 18619035, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, }, }); diff --git a/src/plugins/eth/ponder.config.ts b/src/plugins/eth/ponder.config.ts index de40ae1..83bf9a6 100644 --- a/src/plugins/eth/ponder.config.ts +++ b/src/plugins/eth/ponder.config.ts @@ -18,9 +18,12 @@ export const ownedName = "eth"; export const pluginNamespace = createPluginNamespace(ownedName); +// demo for `didsendit.eth` +// minted at block: 21_610_362 + // constrain the ponder indexing between the following start/end blocks // https://ponder.sh/0_6/docs/contracts-and-networks#block-range -const START_BLOCK: ContractConfig["startBlock"] = undefined; +const START_BLOCK: ContractConfig["startBlock"] = 21_610_262; const END_BLOCK: ContractConfig["endBlock"] = undefined; export const config = createConfig({ @@ -36,13 +39,15 @@ export const config = createConfig({ network: "mainnet", abi: Registry, address: "0x314159265dd8dbb310642f98f50c066173c1259b", - ...blockConfig(START_BLOCK, 3327417, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Registry")]: { network: "mainnet", abi: Registry, address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", - ...blockConfig(START_BLOCK, 9380380, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("OldRegistryResolvers")]: { network: "mainnet", @@ -52,7 +57,8 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 9380380, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Resolver")]: { network: "mainnet", @@ -62,31 +68,36 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 9380380, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("BaseRegistrar")]: { network: "mainnet", abi: BaseRegistrar, address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85", - ...blockConfig(START_BLOCK, 9380410, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EthRegistrarControllerOld")]: { network: "mainnet", abi: EthRegistrarControllerOld, address: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", - ...blockConfig(START_BLOCK, 9380471, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EthRegistrarController")]: { network: "mainnet", abi: EthRegistrarController, address: "0x253553366Da8546fC250F225fe3d25d0C782303b", - ...blockConfig(START_BLOCK, 16925618, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("NameWrapper")]: { network: "mainnet", abi: NameWrapper, address: "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401", - ...blockConfig(START_BLOCK, 16925608, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, }, }); diff --git a/src/plugins/linea.eth/ponder.config.ts b/src/plugins/linea.eth/ponder.config.ts index 25626a8..327cd83 100644 --- a/src/plugins/linea.eth/ponder.config.ts +++ b/src/plugins/linea.eth/ponder.config.ts @@ -14,9 +14,12 @@ export const ownedName = "linea.eth"; export const pluginNamespace = createPluginNamespace(ownedName); +// demo for `0xtko.linea.eth` +// minted at block: 14_491_235 + // constrain the ponder indexing between the following start/end blocks // https://ponder.sh/0_6/docs/contracts-and-networks#block-range -const START_BLOCK: ContractConfig["startBlock"] = undefined; +const START_BLOCK: ContractConfig["startBlock"] = 14_490_000; const END_BLOCK: ContractConfig["endBlock"] = undefined; export const config = createConfig({ @@ -32,7 +35,8 @@ export const config = createConfig({ network: "linea", abi: Registry, address: "0x50130b669B28C339991d8676FA73CF122a121267", - ...blockConfig(START_BLOCK, 6682888, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("Resolver")]: { network: "linea", @@ -42,25 +46,29 @@ export const config = createConfig({ event: getAbiItem({ abi: Registry, name: "NewResolver" }), parameter: "resolver", }), - ...blockConfig(START_BLOCK, 6682888, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("BaseRegistrar")]: { network: "linea", abi: BaseRegistrar, address: "0x6e84390dCc5195414eC91A8c56A5c91021B95704", - ...blockConfig(START_BLOCK, 6682892, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("EthRegistrarController")]: { network: "linea", abi: EthRegistrarController, address: "0xDb75Db974B1F2bD3b5916d503036208064D18295", - ...blockConfig(START_BLOCK, 6682978, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, [pluginNamespace("NameWrapper")]: { network: "linea", abi: NameWrapper, address: "0xA53cca02F98D590819141Aa85C891e2Af713C223", - ...blockConfig(START_BLOCK, 6682956, END_BLOCK), + startBlock: START_BLOCK, + endBlock: END_BLOCK, }, }, }); From a208c45900778dc691649a9fec7b4233a8160c53 Mon Sep 17 00:00:00 2001 From: Tomek Kopacki Date: Thu, 16 Jan 2025 17:09:20 +0100 Subject: [PATCH 6/7] refactor(ponder.config): use deep merge lib --- ponder.config.ts | 54 +----------------------------------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/ponder.config.ts b/ponder.config.ts index f262d45..bd983c9 100644 --- a/ponder.config.ts +++ b/ponder.config.ts @@ -1,4 +1,4 @@ -// import { deepMergeRecursive } from "./src/lib/helpers"; +import { deepMergeRecursive } from "./src/lib/helpers"; import { type IntersectionOf, getActivePlugins } from "./src/lib/plugin-helpers"; import * as baseEthPlugin from "./src/plugins/base.eth/ponder.config"; import * as ethPlugin from "./src/plugins/eth/ponder.config"; @@ -30,55 +30,3 @@ function getActivePluginsConfig(): AllPluginsConfig { // The type of the default export is the intersection of all available plugin // configs so that each plugin can be correctly typechecked export default getActivePluginsConfig(); - -type AnyObject = { [key: string]: any }; - -/** - * Deep merge two objects recursively. - * @param target The target object to merge into. - * @param source The source object to merge from. - * @returns The merged object. - * @see https://stackoverflow.com/a/48218209 - * @example - * const obj1 = { a: 1, b: 2, c: { d: 3 } }; - * const obj2 = { a: 4, c: { e: 5 } }; - * const obj3 = deepMergeRecursive(obj1, obj2); - * // { a: 4, b: 2, c: { d: 3, e: 5 } } - */ -export function deepMergeRecursive( - target: T, - source: U, -): T & U { - const output = { ...target } as T & U; - - function isObject(item: any): item is AnyObject { - return item && typeof item === "object" && !Array.isArray(item); - } - - if (isObject(target) && isObject(source)) { - Object.keys(source).forEach((key) => { - if (isObject(source[key])) { - if (!(key in target)) { - Object.assign(output, { [key]: source[key] }); - } else { - (output as AnyObject)[key] = deepMergeRecursive( - (target as AnyObject)[key], - (source as AnyObject)[key], - ); - } - } else if (Array.isArray(source[key])) { - if (!(key in target)) { - Object.assign(output, { [key]: source[key] }); - } else { - (output as AnyObject)[key] = Array.isArray((target as AnyObject)[key]) - ? [...(target as AnyObject)[key], ...source[key]] - : source[key]; - } - } else { - Object.assign(output, { [key]: source[key] }); - } - }); - } - - return output; -} \ No newline at end of file From dd538a9747bbd90f3a38320e565ae70492e04a54 Mon Sep 17 00:00:00 2001 From: Tomasz Kopacki Date: Thu, 16 Jan 2025 17:52:31 +0100 Subject: [PATCH 7/7] feat(partial-index): soft error handling --- src/handlers/Resolver.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/handlers/Resolver.ts b/src/handlers/Resolver.ts index d77167a..b69863f 100644 --- a/src/handlers/Resolver.ts +++ b/src/handlers/Resolver.ts @@ -243,7 +243,9 @@ export async function handleVersionChanged({ const { node } = event.args; const id = makeResolverId(node, event.log.address); const domain = await context.db.find(schema.domain, { id: node }); - if (!domain) throw new Error("domain expected"); + if (!domain) { + return; + } // materialize the Domain's resolvedAddress field if (domain.resolverId === id) {