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.
Updated 18 days ago