Skip to main content

The Graph

Overview

The Graph is a powerful indexing protocol that simplifies how you access blockchain data. Using GraphQL, it enables seamless querying of smart-contract data through APIs called subgraphs.

In this tutorial, we'll create an indexer for a smart contract called DemoNFT, using the contract deployed in the Deploying an NFT with Hardhat tutorial. Don't worry if you haven't deployed it yourself, you can use this pre-deployed DemoNFT contract.

Why Learn The Graph?

The Graph makes blockchain data accessible and usable for developers, turning raw data into meaningful, queryable formats. Whether you're building a dApp or exploring blockchain analytics, mastering this tool is a game-changer.

Prerequisites

Before diving in, ensure you've got the following:


Example Code Repository

You can find the example code we used for this tutorial here.

Step 1: Creating a Subgraph Project

  1. Kickstart Your Subgraph
    • Visit Subgraph Studio and connect your wallet.
    • Click "Create a Subgraph" and choose a descriptive name (it's permanent).
  1. Set Up Your Environment

    • Install The Graph CLI:
      npm install -g @graphprotocol/graph-cli@latest
    • Initialize your subgraph using the slug displayed in Subgraph Studio:
      graph init <SUBGRAPH_SLUG>
      cd [subgraph_name]

    The CLI will guide you through setting up the project. It can even fetch your contract's ABI automatically! If you haven't deployed your own DemoNFT contract, enter the address of the pre-deployed contract instead.


Step 2: Building the Indexer

Our goal: Create a schema called DemoNFT to track NFT ownership via the Transfer event.

Designing the Schema

Replace the contents of the schema.graphql file in the root directory of your subgraph project with the following:

schema.graphql
type DemoNFT @entity {
id: String! # tokenId of the NFT
owner: Bytes! # Owner's wallet address
}

Writing the Query Logic

We are going to index the Transfer event for this DemoNFT query as _transfer, _mint, and _burn functions will emit the Transfer event. Let's update demo-nft.ts in src folder.

src/demo-nft.ts
import { Transfer as TransferEvent } from "../generated/DemoNFT/DemoNFT";
import { DemoNFT } from "../generated/schema";

export function handleTransferEvent(event: TransferEvent): void {
// find from DemoNFT entity if it exists
let entity = DemoNFT.load(event.params.tokenId.toString());

if (entity) {
// update the owner field
entity.owner = event.params.to;

// save the entity
entity.save();

return;
}

// create a new entity
entity = new DemoNFT(event.params.tokenId.toString());

// set the owner field
entity.owner = event.params.to;

// save the entity
entity.save();
}

Update the subgraph.yaml

Update the eventHandlers with the following content.

subgraph.yaml
specVersion: 1.0.0
indexerHints:
prune: auto
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: DemoNFT
network: soneium-testnet
source:
address: "0x509020Ac6410142F3146f0CdFF25701010073b7f"
abi: DemoNFT
startBlock: 2810104
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Approval
- ApprovalForAll
- OwnershipTransferred
- Transfer
abis:
- name: DemoNFT
file: ./abis/DemoNFT.json
eventHandlers:
- event: Transfer(indexed address,indexed address,indexed uint256)
handler: handleTransferEvent
file: ./src/demo-nft.ts

Step 3: Build and Test Your Subgraph

Build the Subgraph

Before testing, you need to build the subgraph to ensure everything is correctly set up. Run the following commands:

$ graph codegen
$ graph build
tip

Run graph codegen whenever you update the schema and graph build when the mapping logic changes.

Writing Tests

  1. Update the tests/demo-nft-utils.ts file by adding createTransferEvent function.
  2. Update the tests/demo-nft.test.ts file
tests/demo-nft-utils.ts
import { Address, ethereum, BigInt } from "@graphprotocol/graph-ts";
import { newMockEvent } from "matchstick-as";
import { Transfer } from "../generated/DemoNFT/DemoNFT";

export function createTransferEvent(
from: Address,
to: Address,
tokenId: BigInt
): Transfer {
const transferEvent = changetype<Transfer>(newMockEvent());

transferEvent.parameters = new Array();

transferEvent.parameters.push(
new ethereum.EventParam("from", ethereum.Value.fromAddress(from))
);
transferEvent.parameters.push(
new ethereum.EventParam("to", ethereum.Value.fromAddress(to))
);
transferEvent.parameters.push(
new ethereum.EventParam(
"tokenId",
ethereum.Value.fromUnsignedBigInt(tokenId)
)
);

return transferEvent;
}

Running Tests

Run the tests with:

$ yarn test
tip

There is a unknown bug in the matchstick that the CLI won't display the test result on your terminal. In that case, you can add a GitHub action in your repo and see the result in the PR.

GitHub action source code

Step 4: Deploying the Subgraph

Authenticate and deploy your subgraph using these commands. You can copy these commands directly from your subgraph's page in Studio to include your specific deploy key and subgraph slug. graph auth is required for the first time only.

$ graph auth <DEPLOY_KEY>
$ graph deploy <SUBGRAPH_SLUG>

Step 5: Querying the Data

Test with Playground

You can test your subgraph by making a sample query in the playground section. The Details tab will show you an API endpoint. You can use that endpoint to test from your dapp.

{
demoNFT(id: 1) {
id
owner
}
}

Query by API

Leverage the development API endpoint to integrate data queries into your application.

For example:

curl -X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ demoNFT(id: 5) { id owner } }", "operationName": "Subgraphs", "variables": {}}' \
https://api.studio.thegraph.com/query/93757/demo-nft/version/latest

> {"data":{"demoNFT":{"id":"5","owner":"0xb0b24d750ad75d581654f54b6b0eca5d553fee41"}}}