How to use The Graph to query Event data

alinobrasil

ali kim

Posted on November 2, 2023

How to use The Graph to query Event data

As a data guy, I find The Graph super awesome for developing any dapp. But I had a slightly hard time getting my subgraph to work. So here's a little guide for anyone who may find the same struggles.

Contents:

Why The Graph

How to use the Graph

  1. Getting set up
  2. Build your subgraph
  3. Define event handlers
  4. Finishing Touches

Extras

  • a more complex example
  • how to deal with using proxy contracts
  • how to deal with slow Studio performance

Why The Graph

The Graph is GREAT for developing the frontend of a decentralized app (dapp). Here's my take on why it's so awesome and how you can quickly get up and running with it.

When your dapp needs to display some information on the state or history of something going on in your smart contract, using events instead of state variables will help save gas and overall performance. Things like tracking transaction history of an ERC20 token, borrowing/lending activity or actions like voting. You only need to read this info.

So if you design your frontend to read from logs, you can potentially let your smart contract use less storage variables. Your frontend can query from some external source with the data all prepared in a clean format.

The problem is that you need some infrastructure to collect all the relevant events, clean them up a bit and then host them somewhere for you. The Graph solves these problems in one go. Your project of indexed events is referred to as a Subgraph.

How to use The Graph

At a high level you'll need 3 things:

  1. Set up a subgraph using their web interface and cli tool
  2. Design a schema for how to store your data
  3. Set up the logic to fit the event data into your schema

Part 1: Getting set up

  1. Go to thegraph's studio: thegraph.com/studio. You'll need to connect your metamask wallet to log in.
  2. Click the button "Create a Subgraph" Image description
  3. Set things up The next screen shows you the commands you need to get set up

Setting up subgraph

You'll be prompted to enter a bunch of info. Here's an example from thegraph's site:

setting things up

When you enter a contract address, it will be able to fetch the ABI from etherscan if the contract has been verified. So if it's your own smart contract, take the steps to verify/publish your contract. Then this graph init step will obtain the abi automatically.

It's easiest to just go with the default config by pressing enter all the way.

Part 2: Design a schema

Go into your subgraph folder. In my case, it'd be test123. Open your code editor in this folder.

The default schema generated by graph init is called schema.graphql. It creates an entity (data table) for each smart contract event but most often that will not be the schema you want to use.

How do you go about this?

Let's say your smart contract emits 2 events:

event GravatarAccepted(
  address indexed owner,
  bytes32 indexed id, 
  string displayName,
  string imageUrl
);


event GravatarDeclined(
  address indexed owner,
  bytes32 indexed id,
  string displayName,
  string imageUrl  
);
Enter fullscreen mode Exit fullscreen mode

Instead of creating an entity for each event, you should try to think of the simplest way you can store this info in a way that makes sense to your front-end app.

We shouldn't need duplicate info for owner, id, displayName and imageUrl. You'd likely need just one additional field indicating whether a Gravatar was accepted or declined. So you can end up with this:
Schema example - gravatar

This is an over-simplified example. Later I'll show you a slightly more complex example that got me an award from The Graph (1st place in Best Use of Subgraph at ETH Global New York 2023).

Event handlers: Mapping events to your schema

This is the bulk of the work.

You'll see a ts file in the src folder named after your contract. It auto-generates a bunch of files to be able to import contract & event related event objects.

You just need to focus on the handler functions that specify what to do with each event.

In the Gravatar example above, I had 2 events:

  • GravatarAccepted
  • GravatarDeclined

Then my ts file would have handler functions called

  • handleGravatarAccepted
  • handleGravatarDeclined

The default ones only show you how to create a new record in your entity.

But that's not what we want to do for GravatarAccepted and GravatarDeclined.

For these events, we should only need to indicate whether the gravatar was accepted or not. Consider the schema defined earlier:

Image description

We should only need to update the "accepted" field.

Let's assume the contract has an event 'GravatarCreated' indicating the details (owner, displayName, imageUrl). This can simply use the default function where it creates for the our entity every time this event is created.

Then our handler functions for GravatarAccepted and GravatarDeclined should only need to locate the existing record by id and update the value for the field 'accepted'.

export function handleGravatarAccepted(event: GravatarAccepted): void {

  const gravatarId = event.params.id.toString()

  const gravatar = Gravatar.load(gravatarId) //locate entry with matching Id

  if (gravatar) {
    gravatar.accepted = true // set to true for GravatarAccepted event
    gravatar.save()
  }

}
Enter fullscreen mode Exit fullscreen mode

For GravatarDeclined, the handler function would be identical except I'd just change the value to false.

That's all!

Finishing touches

One last thing before attempting to deploy your subgraph: make sure your subgraph.yaml file specifies the correct handler functions. The initially auto-generated settings are likely wrong.

Under datasources you'll see eventHandlers. We should keep only the ones we need: GravatarAccepted & GravatarDeclined. Based on our assumption that we have another event for initially creating the records, we may have GravatarCreated. So in the end it should look like this:

yaml file - event handlers

Alright. Time to deploy. Just follow the cli instructions you see in The Graph's studio page and you should be good to go:

instructions from Studio

Upon successful deployment, you'll see a query url that you can use from a frontend app:

Image description

You can use the playground to put together a query you want:

Image description

Then from the frontend, you just need to make a request to the url using a graphql query.

const graphqlQuery = 
{
gravatar(last: 5) {
id
owner
displayName
imageUrl
accepted
}
}
;

const graphQLRequest = {
method: 'post',
url: 'https://api.studio.thegraph.com/query/12345/test123/0.0.26',
data: {
query: graphqlQuery,
},
};

Enter fullscreen mode Exit fullscreen mode




Extras:

A more complex example

In this post I've been using the Gravatar example seen on The Graph's documentation. Here's a slightly more complex example from my hackathon project at ETH Global New York. It won #1 Best Use of Subgraph.

It indexes some key info from Blend, which is Blur's lending contract for NFT-backed loans. (Blur is the largest NFT trading dex)

The events from Blend's smart contract are rather messy because they did some hacky things to optimize for gas. The result is some very unintuitive events. I simplified them into 2 entities that reflect the true context of NFT-backed lending.

Image description

My schema has a 1-to-many relationship between Liens and Loans. Each lien contains a collateralized NFT and can have its associated loan renewed/updated many times with new terms. This schema makes it much easier to work with for building an app that analyzes historical data.

I have many different events that map to these 2 entities. One of the events get re-used in many different situations so I have some complex logic for that event:

Event handler code

What if your smart contract is a proxy contract, and the implementation code is in another contract?

When setting up your project with graph init, it'll get the abi of the contract. If it's a proxy contract, it'll get the abi of the proxy, NOT the implementation.

To deal with this, you should specify the address of the implementation contract containing the actual logic of the smart contract, including the events you're trying to index.

For my hackathon project, this was the target contract: 0x29469395eaf6f95920e59f858042f0e28d98a20b. On etherscan, I obtained the address of the implementation contract and used it when initializing my subgraph project (graph init).

Image description

But when deploying the subgraph, you still need to point to the proxy contract, so just before deploying, you should specify the proxy contract in subgraph.yaml.

Image description

Slow indexing on Studio

While The Graph's Studio is mostly great, it is possible for you to run into performance issues. In my case, it took countless hours to index my target smart contract.

To deal with this, I specified a startBlock that's very recent, just so I could obtain some data to test with during the hackathon, though incomplete.

Some time later, the slow Studio issue was resolved. But in the heat of the moment that's what I did.

💖 💪 🙅 🚩
alinobrasil
ali kim

Posted on November 2, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related