ENS

The default values for the blockflow init commandIn this tutorial, we will show you how to index the ENS protocol using the Blockflow CLI. Blockflow provides a seamless and efficient solution for building "Serverless Backends" through our Managed Databases, On-chain Instances, and Custom APIs.

The Ethereum Name Service (ENS) is a decentralized protocol on the Ethereum blockchain that converts complex, alphanumeric addresses into human-readable names. This system simplifies cryptocurrency transactions and interactions with decentralized applications (dApps) by making them more user-friendly. ENS also supports linking names to various cryptocurrency addresses, content hashes, and metadata.

Project Overview

The purpose of this project is to teach you how to index ENS by creating subgraphs with the aid of Blockflow and its utilities. A short breakdown for the project is below -

  • Create a project directory and cd into it. Use the command blockflow init through the terminal and provide with the relevant contract address to be tracked.

  • Supply the appropriate schema for depending upon the data emitted by the events in the contract.

  • Write the handler function for each event for tracking.

  • Test the project using the command blockflow test and verify the data stored over the database.

Setting up the project

  • Download the Blockflow CLI using the command:

npm i -g @blockflow-labs/cli

We can remove the -g tag if global installation is not required.

  • Create a project directory and use the cd command over it to make it the working directory.

  • Use the blockflow init command to initiate the project.

  • Below are the fields that need to be entered for the project. You can also let the default values for each field by pressing [ENTER]. The last field requires you to select the instance trigger type with 4 choices. You can also add more than one trigger for a project.

Setting up the schema

The below-mentioned schema is appropriate to track the relevant data from CCTP:

interface Account {
  id: String;
  domains: [String];
  wrappedDomains: [String];
  registrations: [String];
}

interface wrappedTransfer {
  id: String;
  blockNumber: Number;
  transactionID: string;
  owner: string;
}

interface Registration {
  id: String;
  domain: String;
  registrationDate: String;
  expiryDate: Number;
  cost: Number;
  registrant: String;
  labelName: String;
  events: [String];
}

interface RegistrationEvent {
  id: String;
  transactionID: String;
}

interface WrappedDomain {
  id: String;
  expiryDate: Number;
  fuses: Number;
  name: String;
}

interface Domain {
  id: String;
  name: String;
  labelName: String;
  labelhash: String;
  parent: String;
  subdomainCount: Number;
  resolvedAddress: String;
  owner: String;
  resolver: String;
  ttl: Number;
  isMigrated: Boolean;
  createdAt: Number;
  registrant: String;
  wrappedOwner: String;
  expiryDate: Number;
  wrappedDomain: String;
  events: [DomainEvent];
  registration: String;
}

type DomainEvent = {
  domain: String;
  transactionID: String;
  blockNumber: Number;
};

interface Resolver {
  id: String;
  domain: String;
  address: String;
  addr: String;
  contentHash: String;
  texts: [String];
  coinTypes: [String];
  events: [ResolverEvent];
}

type ResolverEvent = {
  resolver: String;
  transactionID: String;
};

interface Transfer {
  id: String;
  domain: String;
  transactionID: String;
  owner: String;
}

interface AddrChanged {
  id: String;
  resolver: String;
  transactionID: String;
  addr: String;
}

interface MulticoinAddrChanged {
  id: String;
  resolver: string;
  transactionID: String;
  coinType: String;
  addr: String;
}

interface TextChanged {
  id: String;
  resolver: String;
  transactionID: String;
  key: String;
}

interface NameChanged {
  id: String;
  resolver: String;
  transactionID: String;
  name: String;
}

interface AbiChanged {
  id: String;
  resolver: String;
  transactionID: String;
  contentType: String;
}

interface PubkeyChanged {
  id: String;
  resolver: String;
  transactionID: String;
  x: String;
  y: String;
}

interface ContenthashChanged {
  id: String;
  resolver: String;
  transactionID: String;
  hash: String;
}

interface InterfaceChanged {
  id: String;
  resolver: String;
  transactionID: String;
  interfaceID: String;
  implementer: String;
}

interface VersionChanged {
  id: String;
  resolver: String;
  transactionID: String;
  version: Number;
}

interface Namewrapperevents {
  id: String;
  blockNumber: Number;
  transactionID: String;
  fuses: Number;
  expiryDate: Number;
  owner: String;
}

interface Nameunwrapperevents {
  id: String;
  blockNumber: Number;
  transactionID: String;
  owner: String;
}

interface Fusesburntevent {
  id: String;
  fuses: Number;
  blockNumber: Number;
  transactionID: String;
}

interface Expiryextendedevent {
  id: String;
  expiryDate: Number;
  blockNumber: Number;
  transactionID: String;
}

On completing the schema, use the command blockflow typegen to generate types.

Writing the handler function

Now, we move onto writing the handler functions for the project, before doing so, we move onto removing unwanted handler and keeping the necessary ones only into the studio.yaml file. As shown below, the studio.yaml will let us track the NameRegistered event:

name: ENS Indexing
description: A top-secret research project to index the Ethereum Name service
startBlock: 9412610
userId: 6cd145d5-b8bc-41a0-a74d-2e9b88ffb675
projectId: da0f8d15-a0df-4a03-ade3-6ceffcd57b2e
network: Ethereum
user: prady
execution: sequential
schema:
  file: ./studio.schema.ts
Resources:
  - Name: baseRegistrar
    Abi: src/abis/BaseRegistrar.json
    Type: contract/event
    Address: "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85"
    Triggers:
      - Event: NameRegistered(uint256 indexed,address indexed,uint256)
        Handler: >-
          src/handlers/BaseRegistrar/NameRegisteredHandler/index.NameRegisteredHandler

Use the command blockflow codegen to generate handler templates in /src/handlers directory.

On going into the handler template file created, we can see 3 events emitted which are id, owner, expires. We import the Registration class from /types/schema and bind the database connections as shown below:

import { Registration } from "../../../types/schema";

const registrationDB: Instance = bind(Registration);

We don't need to create an Id variable as in other tutorials because we already have it emitted in the event data. We still need a string holding a date value during the time of the transction, so we create it as follows:

const isoDate = new Date(Number(block.block_timestamp) * 1000).toISOString();

On being done with all of this, we move onto doing CRUD operations onto the DB. We declare a variable registration and look for a certain ID over the registrationDB .The first if statement gets triggered if the ID is not found in the DB and it creates a new document with the speicified ID. We assign either default or event values to all the fields in the documents accordingly. The else statement block comes into play if a document is found for the ID being searched in the registrationDB . The following values are updated according to the event data - registarant and events. The last line includes the await method to save the data updated in the registration variable.

let registration = await registrationDB.findOne({
    id: id.toString(),
  });
  if (!registration) {
    await registrationDB.create({
      id: id.toString(),
      domain: "",
      registrationDate: isoDate.toString(),
      expiryDate: expires.toString(),
      cost: "",
      registrant: owner,
      labelName: "",
    });
  }
  else{
    registration.registrant = owner;
    registration.events.push({
    id:id.toString(),
    blockNumber:block.block_number,
    transactionID:transaction.transaction_hash,
  });
  await registrationDB.save(registration);
  }

Testing

We use the below command to test if the handler logic is correct and the data gets stored on our specified collection in Mongo.

blockflow instance-test --startBlock <block number> --clean --range 10 --uri <connection string>

The <block number> can be put of any range, but we can put it to latest if no opinion provided. The — uri holds the MongoDB connection URL which is tested locally shall be filled with mongodb://localhost:27017/blockflow_studio.

And, we're good to go, to have a look at the complete indexing and all the necessary handlers for indexing ENS using blockflow CLI, do check the github repository.

Last updated