Subgraphs are data-querying APIs in the Graph ecosystem. A subgraph includes various types of information such as the data source, which blocks for indexing, which events to notice, how the data will be retrieved, or the schema that data should be stored. Indexing nodes only need to follow instructions in subgraphs to function.

Setup contract

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0;
contract RandomAssignment{
    mapping(address => uint) public valueOf;
    event Assign(address indexed addr, uint indexed value);
    function assignValueFor(address addr) public {
        uint val = block.timestamp%15 + 1;
        valueOf[addr] = val;
        emit Assign(addr, val);
    }
}

In this contract, we can set any address to a random value from 1 to 1. We can also change the value of the assigned address. An event is emitted each time the function assignValueFor is called. (Note: This is not the recommended way to generate a random value in solidity)

Now in our DApp, we want to get a set of addresses that are assigned value more than two times in this smart contract. More complicated, we want to get a set of addresses assigned with current values from 5 to 10, counting from the second address from block 10228285 forward sorted in ascending order of value. We also want to get data on how many addresses each value is assigned.

We can do it by Web3 normally, but here we will use The Graph Protocol to create open APIs allowing us to query all information much easier and faster in real-time.

For simplicity, we deploy this contract on the Rinkeby test net. You can use MetaMask as your wallet and get some ETH on Rinkeby through Rinkeby Faucet. You can use Remix to deploy this contract for simplicity. Please switch to injected Web3 to connect with your MetaMask in the Rinkeby test net.

Here I deployed it at the address: 0x261eceB52b36D931f3Ec3742a6b66Deb720d8Fe4 and then I assigned some random values to some addresses using this contract (You can use this address for testing).

Create decentralized subgraph by Subgraph Studio

First, go to My Subgraphs | Subgraph Studio (thegraph.com). Then, connect your wallet -> Sign message to Log in to Studio -> Create Subgraph:

Source: from thegraph.com

-> Set subgraph’s name -> Hit “Continue”

If you don’t have NodeJS installed, you need to install it first.

-> Open folder project: npm install -g @graphprotocol/graph-cli to install graph-cli

-> “graph init –studio <subgraph’s name>” to init project. 

Note that the subgraph’s name must be the name in Subgraph Studio in lower case. In my case, it must be:

graph init --studio subgraphforrandomassignment

Image on terminal

-> Then we set some information such as network Rinkeby, contract address specific above,...

Note that if you haven’t published your code on Etherscan, you must provide the ABI file. Here I copy ABI when compiling the contract on Remix and paste it to the file abi.json

The graph-cli will create a sample subgraph project for your contract. To create a subgraph, you need to modify 3 files: subgraph.yaml, schema.graphql and src/mapping.ts

-> cd subgraphforrandomassignment to forward to subgraph folder

-> npm install to install package graph-ts already defined in our package.json file

We need this library to the write Typescript file

-> In the file schema.graphql, we define each entity(schema) of data that we want to get in our DApp:

type AddressValue @entity {
  id: ID!
  count: Int!
}
type Address @entity {
  id: ID!
  timesChange: Int!
  blockNumber: BigInt!
  timestamp: BigInt!
  currentValue: BigInt!
}

We need to define 2 entities: Address and AddressValue. We want to get how many addresses each value is assigned, so it is the field “count” in “AddressValue”. In “Address”, we want to count how many times the address has been assigned a value(timeChanged), currentValue, blockNumber, and timestamp. The field “id” with the type “ID!” is unique and required in every entity.

-> In file subgraph.yaml, we write configuration for the subgraph like the entities, startBlock, and event to catch:

    source:
      address: "0x261eceB52b36D931f3Ec3742a6b66Deb720d8Fe4"
      abi: RandomAssignment
      startBlock: 10263362

Here we define the startBlock as the number of the block that the data source starts indexing from. In most cases, we suggest using the block in which the contract was created. If not, then the indexer will start from the genesis block.

entities:
        - AddressValue
        - Address


These two entities are those we defined in the file schema.graphql before

   eventHandlers:
        - event: Assign(indexed address,indexed uint256)
          handler: handleAssign

It means when the event “Assign” is emitted from the smart contract, it will update data by calling the function “handleAssign” that we will define in mapping.ts

Numerous contracts avoid emitting events so that gas costs can be reduced. In these instances, events are not an effective way to collect relevant changes to the state of a contract. The Graph also uses callHandler and blockHandler to catch new blocks, and all function calls or interact with IPFS.

-> graph codegen

To make smart contracts, events, and entities easy and type-safe, the Graph CLI generates AssemblyScript types from a combination of the subgraph's GraphQL schema and the contract ABIs included in the data sources. This command creates a new folder generated containing AssemblyScript.

-> The main part is the mapping.ts file which we determine how the data is obtained from the blockchain:

import { Assign as AssignEvent } from "../generated/RandomAssignment/RandomAssignment"
import { AddressValue, Address } from "../generated/schema";

We need to import all entities defined in schema.graphql to get data for them in this file. The Graph will run from the startBlock to the newest block, catch-all events chronologically and handle it via the corresponding handler function in mapping.ts

export function handleAssign(event: AssignEvent): void {
  let val = AddressValue.load(event.params.value.toString());
  if(!val){
    val = new AddressValue(event.params.value.toString());
    val.count = 0;
  }
  let address = Address.load(event.params.addr.toHexString());
  if(!address){
    val.count++;
    address = new Address(event.params.addr.toHexString());
    address.timesChange = 0;
  }
  address.blockNumber = event.block.number;
  address.timesChange++;
  address.timestamp = event.block.timestamp;
  address.currentValue = event.params.value;
  address.save();
  val.save();
}

When event Assign comes, we will get the entity AddressValue via function AddressValue.load(<id of entity>) and check if it doesn’t exist(it is the first time an address is assigned this value), we will initiate this entity. Note that here we assign the id of the AddressValue entity to the value from the event(from 1 to 15). Similarly, with entity Address, we initiate and write code to update each entity. Finally, we save them to the store of Graph Node via function save().

We finished our subgraph, let’s deploy it to The Graph Network

-> “graph auth  --studio <DEPLOY_KEY>” to authenticate within CLI. Note that DEPLOY_KEY is taken from the subgraph website.

Subgraph Studio

-> After deployment, we need to wait for The Graph to sync data on the blockchain, and then we can use it in our DApp. However, other people cannot use your subgraph because it hasn't been published to be indexed by Indexer yet. To become a Curator to signal your subgraph, you need to stake GRT token.

You can get token GRT to your wallet on the Rinkeby test net by joining discord of The Graph at link: https://discord.com/invite/vtvv7FP and get the role Testnet Indexer and then request GRT token to your address in channel 🚰-testnet-faucet by command:

Discord of The Graph

-> Now you can publish and signal your subgraph:

Subgraph Studio

For example, here I stake 10,000 GRT token to signal for this subgraph to The Graph Network. If our project has high potential, the Indexer will start indexing for us soon. We can also become a Delegator to attract Indexers to come for our subgraph. After indexing, everyone can use open APIs to get data from our project easily and fast.

Create a subgraph with Hosted Service

We can create subgraphs in the Hosted Service of The Graph. It is centralized, we don’t need to publish our code and use Metamask. We only need to sign in to our Github account and start creating subgraphs easily. In the new version of The Graph, they built Subgraph Studio allowing anyone to create subgraphs. They will gradually sunset the Hosted Service once they reach feature parity with the decentralized network.

We can easily create a subgraph with commands and code similar to section (3)

Note: there is no Curator or Indexer and you don’t need to own GRT token to create subgraphs in the Hosted Service.

Use API from subgraph in your project

We can query data via GraphQL in the Playground section of The Graph for testing purposes. For example, we query “a set of addresses assigned with current value from 5 to 10 counting from the second address from block 10228285 forward sorted in ascending order of value” by GraphQL:

Query GraphQL online on The Graph Playground

=> The first column is our query, the second column is output, and the third column is some schema that we can use in our GraphQL queries.

Note: GraphQL is a query language built by Facebook, which is very easy to learn. The query syntax is purely basic and simple to use

To use in our project, we need an URL and then query data just like normal HTTP requests. For other people to access the URL, they need to create an API key and stake GRT token in Subgraph Studio. However, in this blog, we will use the Temporary Query URL in the Details tab on our Subgraph Studio for free because we are the creator of the subgraph.

-> Open our subgraph to get the URL. In my case, the URL is: https://api.studio.thegraph.com/query/22198/subgraphforrandomassignment/v0.0.1

Subgraph Studio

This open API works with any HTTP library. For example, I use httpie to query “a set of addresses that are assigned value more than 2 times” in command line:

We get the output right away:

Query output from terminal of NodeJS

For example, using subgraph in your NodeJS Project:

-> npm init -y to init a NodeJS project -> npm install axios -> create file script.js:

Code query GraphQL with Axios in NodeJS

- > node script.js to get “how many addresses each value is assigned”:

Output on terminal when executing script axios above

References

[1] Building Subgraphs with Subgraph Studio, accessed 10th May 2022

[2] Query Ethereum with GraphQL with The Graph, accessed 11th May 2022