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 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 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 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/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) { 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, }, }, });