Skip to content

Conversation

@tk-o
Copy link
Contributor

@tk-o tk-o commented Jun 4, 2025

This PR introduces an initial ENSNode plugin demo for indexing the Ethereum Follow Protocol.

Suggested review order:

  1. packages/datasources/src/* & packages/ensnode-sdk/src/utils/types.ts
  2. packages/ensnode-schema/src/efp.schema.ts & apps/ensindexer/src/lib/api-documentation.ts
  3. apps/ensindexer/src/plugins/efp/efp.plugin.ts
  4. apps/ensindexer/src/plugins/efp/lib/utils.ts
  5. apps/ensindexer/src/plugins/efp/lib/chains.ts
  6. apps/ensindexer/src/plugins/efp/lib/lsl.ts
  7. apps/ensindexer/src/plugins/efp/lib/api.ts
  8. apps/ensindexer/src/plugins/efp/handlers/EFPListRegistry.ts

Most important features:

  • indexing the List Registry contract, including
    • Transfer event (handled event variants: mint, ownership change, burn)
    • UpdateListStorageLocation event
      • when encoded LSL data can be decoded, we insert its decoded representation to efp_listStorageLocation table
      • otherwise, we store its encoded representation in efp_unrecognizedListStorageLocation table
  • GET /efp/list/:listTokenId route returns basic information about a requested List Token (including its encoded LSL, if available)

Most important changes:

  • New EFPBase datasource added to the mainnet ENS Namespace object and to the sepolia ENS Namespace object;
    • covers the EFPListRegistry contract on, respectively, Base and Base Sepolia chains.
  • A new efp plugin created;
    • uses the EFPRoot datasource only (as the EFPListRegistry contract has only been present on Base chain)
    • defines a EPF plugin specific ponder schema (see packages/ensnode-schema/src/efp.schema.ts file),
    • stores information about a new List being minted (checking if event.args.from is the zeroAddress),
    • if possible, indexes List Storage Location data when handling the UpdateListStorageLocation event,
      • uses schema-based parsing method to ensure 100% data safety before indexing,
      • sometimes the encoded listStorageLocation value cannot be parsed successfully — in this case, we store it in its original encoded form on the relevant List Token entity
    • always sets the related List Token with the current token ID for a List Storage Location entity
    • GQL schema documentation was updated to cover the new schema fields
    • API router allows defining custom routes under /efp path

@vercel
Copy link

vercel bot commented Jun 4, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
admin.ensnode.io ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 24, 2025 7:22pm
ensnode.io ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 24, 2025 7:22pm
ensrainbow.io ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 24, 2025 7:22pm

@changeset-bot
Copy link

changeset-bot bot commented Jun 4, 2025

🦋 Changeset detected

Latest commit: a35833c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@ensnode/datasources Minor
@ensnode/ensnode-schema Minor
@ensnode/ensnode-sdk Minor
ensindexer Minor
ensadmin Minor
ensrainbow Minor
@ensnode/ensrainbow-sdk Minor
@ensnode/ponder-metadata Minor
@ensnode/ponder-subgraph Minor
@ensnode/shared-configs Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

This is a plugin demonstrating ENSNode's capability to index the Ethereum Follow Protocol data.
Copy link
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tk-o Awesome to see this! Reviewed and shared a few suggestions 👍

@djstrong
Copy link
Contributor

djstrong commented Jun 4, 2025

BugBot run

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Type Mismatch Between Functions

The getDatasources function's return type was changed from Datasource[] to Record<DatasourceName, Datasource>, but the getIndexedChainIds function still expects Datasource[] as input. This type mismatch causes a runtime error when getIndexedChainIds(getDatasources(...)) is called, as getIndexedChainIds attempts to call .map() on the object returned by getDatasources.

apps/ensindexer/src/lib/plugin-helpers.ts#L116-L143

*/
export function getDatasources(
config: Pick<ENSIndexerConfig, "ensDeploymentChain" | "plugins">,
): Record<DatasourceName, Datasource> {
const requiredDatasourceNames = getRequiredDatasourceNames(config.plugins);
const ensDeployment = getENSDeployment(config.ensDeploymentChain);
const ensDeploymentDatasources = Object.entries(ensDeployment) as Array<
[DatasourceName, Datasource]
>;
const datasources = {} as Record<DatasourceName, Datasource>;
for (let [datasourceName, datasource] of ensDeploymentDatasources) {
if (requiredDatasourceNames.includes(datasourceName)) {
datasources[datasourceName] = datasource;
}
}
return datasources;
}
/**
* Get a list of unique indexed chain IDs for selected plugin names.
*/
export function getIndexedChainIds(datasources: Datasource[]): number[] {
const indexedChainIds = datasources.map((datasource) => datasource.chain.id);
return uniq(indexedChainIds);
}

Fix in Cursor


BugBot free trial expires on June 9, 2025
You have used $0.00 of your $5.00 spend limit so far. Manage your spend limit in the Cursor dashboard.

Was this report helpful? Give feedback by reacting with 👍 or 👎

Copy link
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tk-o Hey super updates! Reviewed and shared some additional suggestions 👍

@lightwalker-eth lightwalker-eth changed the title feat(ensindexer): introduce the efp plugin feat(ensindexer): introduce initial efp plugin demo Jun 4, 2025
Copy link
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tk-o Good to see the improvements here 👍 Thanks!

Reviewed and shared feedback.

Lineanames: "lineanames",
ThreeDNSOptimism: "threedns-optimism",
ThreeDNSBase: "threedns-base",
EFPRoot: "efp-root",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EFPRoot: "efp-root",
EFPRoot: "efproot",

Goal: enhance alignment with the ENSRoot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should change the value for ENSRoot to be ens-root so we enhance alignment across all DatasourceNames entries?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tk-o sure I'm good with that 👍

Copy link
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tk-o Thanks for your updates 👍 Reviewed and shared feedback.

"EVM chain ID of the chain where the EFP list records are stored, an `EFPDeploymentChainId` value.",
listRecordsAddress:
"Contract address on chainId where the EFP list records are stored (always lowercase).",
slot: "A unique identifier within the List Storage Location, distinguishes between multiple EFP lists stored in the same `EFPListRecords` smart contract by serving as the key in mappings for list operations and metadata.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
slot: "A unique identifier within the List Storage Location, distinguishes between multiple EFP lists stored in the same `EFPListRecords` smart contract by serving as the key in mappings for list operations and metadata.",
slot: "A unique identifier distinguishing between multiple EFP lists stored in the same `EFPListRecords` smart contract.",

Is that fair?

lslId:
"Value of `EncodedLsl` type (optional, lowercase if present). Stores the ID of the List Storage Location. If the List Storage Location was never created or not in a recognized format, this field value will be `null`.",
}),
...generateTypeDocSetWithTypeName("efp_listStorageLocation", "EFP List Storage Location", {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
...generateTypeDocSetWithTypeName("efp_listStorageLocation", "EFP List Storage Location", {
...generateTypeDocSetWithTypeName("efp_listStorageLocation", "EFP List Storage Locations with recognized formatting", {

Is that fair?

/**
* The following is documentation for packages/ensnode-schema/src/efp.schema.ts
*/
...generateTypeDocSetWithTypeName("efp_listToken", "EFP List Token", {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
...generateTypeDocSetWithTypeName("efp_listToken", "EFP List Token", {
...generateTypeDocSetWithTypeName("efp_listToken", "EFP List Tokens", {

* The following is documentation for packages/ensnode-schema/src/efp.schema.ts
*/
...generateTypeDocSetWithTypeName("efp_listToken", "EFP List Token", {
id: "Unique token ID for an EFP List Token",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
id: "Unique token ID for an EFP List Token",
id: "Unique token ID for the ERC-721A NFT representing the EFP List Token",

slot: "The 32-byte value that specifies the storage slot of the EFP list records within the listRecordsAddress contract. This disambiguates multiple lists stored within the same contract and de-couples it from the EFP List NFT token id which is stored on the EFP deployment root chain and inaccessible on other chains.",
listTokenId: "Unique identifier for this EFP list token",
listToken: "A reference to the related ListToken entity",
id: "ListStorageLocation ID, an `EncodedLsl` value (always lowercase).",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
id: "ListStorageLocation ID, an `EncodedLsl` value (always lowercase).",
recognizedLsl: "ListStorageLocation with a recognized valid `EncodedLsl` value (always lowercase).",

What do you think?

I find it nice to avoid introducing additional terminology for "lsl id". Instead, in my current understanding, we have lsl, and a subset of lsl we index will be "recognized". All lsl we index will be found in the efp_listToken table, while only the recognized ones will be found in the efp_listStorageLocation table.

@@ -0,0 +1,56 @@
/**
* This Hono application enables the ENSIndexer API to handle requests related to the Ethereum Follow Protocol.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This Hono application enables the ENSIndexer API to handle requests related to the Ethereum Follow Protocol.
* This Hono application demonstrates a draft proof-of-concept for how custom API handlers can be defined to operate on indexed Ethereum Follow Protocol data.

import { type ListTokenId, parseListTokenId } from "./utils";

interface HonoEFP {
db: ReadonlyDrizzle<Record<string, unknown>>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so familiar with Drizzle. Are we supposed to pass in the overall schema we define in another file here to help give Drizzle full type system knowledge?

Appreciate your advice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Use Hono EFP application for handling EFP-related requests
// Activate the draft proof-of-concept Hono app for handling EFP-related API requests

listRecordsAddress: lslContract.listRecordsAddress,
slot: lslContract.slot,
})
.onConflictDoNothing();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my other comments about suggested data model changes.

I think we want to remove this onConflictDoNothing and change this from an insert operation to an upsert operation.

})
.onConflictDoNothing();
} catch {
// The `encodedLsl` value could not be decoded.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my other comments about suggested data model changes.

Based on those suggestions, I believe here we want to perform a delete operation in the efp_listStorageLocation table to handle the case that the previous LSL for the List Token was recognized but the new value isn't.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants