Migration Example

In this page we will demonstrate how to migrate a subgraph repository from The Graph to SendBlocks. We will be using the Bored Ape Yacht Club subgraph as an example.

Step 1: File Structure Overview

The original subgraph repository has the following file structure:

the-graph/
├── abis/
│   └── BoredApeYachtClub.json
├── mappings/
│   └── BoredApeYachtClub.ts
├── schema.graphql
└── subgraph.yaml

The SendBlocks subgraph project directory should have the following file structure:

sendblocks/
├── abis/
│   ├── BoredApeYachtClub.json
│   └── BoredApeYachtClub-hr.json
├── mappings/
│   └── BoredApeYachtClub.ts
├── src/
│   └── subgraph.yaml
├── schema.graphql
└── schema.graphql.ts

Step 2: Configuration File

The subgraph.yaml file in the original repo includes mappings between events and functions holding the indexing logic. In SendBlocks you can define indexing functions for events, addresses, functions and more.

Data sources in the SendBlocks configuration file are defined using the functions key. The original subgraph.yaml contains a datasource for the Bored Ape Yacht Club contract which defines a mapping between each event and a function that handles the event.

dataSources:
  - kind: ethereum
    name: BoredApeYachtClub
    network: mainnet
    source:
      address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"
    ...
    mappings:
      kind: ethereum/events
      ...
      eventHandlers:
        - event: Approval(indexed address,indexed address,indexed uint256)
          handler: handleApproval
        - event: ApprovalForAll(indexed address,indexed address,bool)
          handler: handleApprovalForAll
        - event: OwnershipTransferred(indexed address,indexed address)
          handler: handleOwnershipTransferred
        - event: Transfer(indexed address,indexed address,indexed uint256)
          handler: handleTransfer
      file: ./src/BoredApeYachtClub.ts

In sendblocks we create indexing logic for the entire BAYC contract. We will create a function for each event that is emitted by the contract. The subgraph.yaml file in the SendBlocks subgraph project should look like this:

functions:
    - bayc-approval:
          chain_id: CHAIN_ETH_MAINNET
          code: mappings/approval.ts
          should_send_std_streams: true
          triggers:
              - type: TRIGGER_TYPE_EVENT
                emitter_address: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
                event: "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
    - bayc-approval-for-all:
          chain_id: CHAIN_ETH_MAINNET
          code: mappings/approval-for-all.ts
          should_send_std_streams: true
          triggers:
              - type: TRIGGER_TYPE_EVENT
                emitter_address: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
                event: "0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31"
    - bayc-ownership-transferred:
          chain_id: CHAIN_ETH_MAINNET
          code: mappings/ownership-transferred.ts
          should_send_std_streams: true
          triggers:
              - type: TRIGGER_TYPE_EVENT
                emitter_address: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
                event: "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0"
    - bayc-transfer:
          chain_id: CHAIN_ETH_MAINNET
          code: mappings/transfer.ts
          should_send_std_streams: true
          triggers:
              - type: TRIGGER_TYPE_EVENT
                emitter_address: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
                event: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

Step 3: Schema File

The schema file defines the layout of the data that will be indexed. It is identical to the existing schema.graphql file but with the @entity directive removed.

Copy the existing schema.graphql file to the root of the sendblocks directory and remove the @entity directive from all the types.

Once the schema file is in place, we can generate TypeScript classes for the schema types using the sb-cli subgraph gen command. This will create a schema.graphql.ts file in the base directory of the project.

Add the following to the subgraph.yaml file to deploy the schema:

subgraphs:
    - bayc-subgraph:
          schema: schema.graphql

Step 4: ABI

The BoredApeYachtClub.json file contains the ABI of the contract. Copy this file to the abis directory in the SendBlocks subgraph project.

We need to use this ABI to decode the events emitted by the contract. This is done inside the mapping function and so the ABI file needs to manually defined as a string in the mapping file. To keep the string from being too long, we will use the hrabi package to convert the ABI to a human readable format.

npx hrabi parse sendblocks/abis/BoredApeYachtClub.json sendblocks/abis/BoredApeYachtClub-hr.json

We can then delete any abi definitions for entities that are not events (since we trigger only on log emitter).

This should leave us with the following content which we will copy into the mappings/BoredApeYachtClub.ts file:

[
    "event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)",
    "event ApprovalForAll(address indexed owner, address indexed operator, bool approved)",
    "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)",
    "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
]

Step 5: Mappings

Start by importing ethers and creating an interface using the appropriate ABI for the event you are currently handling. We will take the Approval event as an example:

import { ethers } from "https://cdn.skypack.dev/[email protected]";

const eventIface = new ethers.utils.Interface([
    "event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)",
]);

Next, define the triggerHandler function which will be called for each Approval event that is emitted by the contract. First lets examine the original handleApproval function:

export function handleApproval(event: ApprovalEvent): void {
    let entity = new Approval(event.transaction.hash.concatI32(event.logIndex.toI32()));
    entity.owner = event.params.owner;
    entity.approved = event.params.approved;
    entity.tokenId = event.params.tokenId;

    entity.blockNumber = event.block.number;
    entity.blockTimestamp = event.block.timestamp;
    entity.transactionHash = event.transaction.hash;

    entity.save();
}

This function will be replaced by the triggerHandler function in the SendBlocks subgraph project. The triggerHandler function will take context and data as arguments. The context object contains information about the transaction and block, while the data object contains the raw log data. The triggerHandler function will be responsible for parsing the log data and updating the subgraph's data model.

export async function triggerHandler(context, data) {
    const parsedLog = eventIface.parseLog(data);

    let entity = new Approval({
        id: context.txHash + data.logIndex,
        owner: parsedLog.args.owner,
        approved: parsedLog.args.approved,
        tokenId: parsedLog.args.tokenId,
        blockNumber: context.blockNumber,
        blockTimestamp: new Date(context.blockTimestamp).valueOf(),
        transactionHash: context.txHash,
    });

    entity.save(schemaName);
}

Follow the same process for the other event handlers.

Step 6: Deploy

Once the mappings are complete, we can deploy the subgraph using the sb-cli deploy command. This will deploy both the subgraph itself and the related mapping functions to the SendBlocks network.