DEV Community

Ian de Jesus
Ian de Jesus

Posted on

Running a subgraph locally

Introduction

A subgraph extracts data from a blockchain, processing it and storing it so that it can be easily queried via GraphQL. For my use case, I'm currently using a subgraph to store title and markdown descriptions for proposals in a DAO governance application.

This post will provide instructions on how to setup a subgraph locally for development purposes. Most tutorials out there focus on creating a subgraph with testnet and mainnet but its advantageous to create subgraphs locally for easier debugging process.

Required knowledge

  1. Basic blockchain contract development
  2. Docker
  3. Nodejs

Prerequisites

  1. Docker
  2. Node

Contract deployment

  1. Follow the steps here to create a new hardhat project. https://hardhat.org/tutorial/creating-a-new-hardhat-project
  2. Delete Lock.sol
  3. Add Greeting.sol and paste in the code below.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Greeting {
    string public greeting;

    event GreetingChanged(string newGreeting, address changedBy);

    constructor(string memory initialGreeting) {
        greeting = initialGreeting;
    }

    function setGreeting(string memory newGreeting) public {
        greeting = newGreeting;
        emit GreetingChanged(newGreeting, msg.sender);
    }

    function getGreeting() public view returns (string memory) {
        return greeting;
    }
}

Enter fullscreen mode Exit fullscreen mode

The above code creates a basic CRU contract for a greeting text. Whenever the greeting changed, the event GreetingChanged is emitted. We will be capturing the event emitted everytime a new greeting is set and store this in our subgraph.

  1. Replace the code in deploy.ts with the code below
import { ethers } from "hardhat";

async function main() {

  const Greeting= await ethers.getContractFactory("Greeting");
  const greeting = await Greeting.deploy("hello");

  await greeting.deployed();


  console.log(
    `deployed to ${greeting.address} with blockNumber ${greeting.deployTransaction.blockNumber}                                                     `
  );
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

At point of interest hre would be the greeting.deployTransaction.blockNumber which will tell subgraph at which blocknumber will is start indexing data.

  1. Modify hardhat.config.js networks object and add
    localhost: {
      chainId: 1337,
      url:"http://0.0.0.0:8545"
    },
Enter fullscreen mode Exit fullscreen mode
  1. Run npx hardhat node --hostname 0.0.0.0. The 0.0.0.0 is important so that the subgraph that we deploy using docker will be able to connect to our local hardhat node

  2. Run npx hardhat compile

  3. Run npx hardhat run --network localhost scripts/deploy.ts

  4. Copy and save the address of the deployed contract and the blocknumber

Subgraph Deployment

  1. Clone this repo git clone https://github.com/hashgraph/hedera-subgraph-example.git
  2. Replace docker-compose.yaml with the code below
version: '3'
services:
  graph-node:
    image: graphprotocol/graph-node
    ports:
      - '8000:8000'
      - '8001:8001'
      - '8020:8020'
      - '8030:8030'
      - '8040:8040'
    depends_on:
      - ipfs
      - postgres
    extra_hosts:
      - host.docker.internal:host-gateway
    environment:
      postgres_host: postgres
      postgres_user: graph-node
      postgres_pass: let-me-in
      postgres_db: graph-node
      ipfs: 'ipfs:5001'
      ethereum: 'localhost:http://host.docker.internal:8545'
      GRAPH_LOG: info
  ipfs:
    image: ipfs/go-ipfs:latest
    ports:
      - '5001:5001'
    volumes:
      - ./data/ipfs:/data/ipfs
  postgres:
    image: postgres
    ports:
      - '5432:5432'
    command:
      [
        "postgres",
        "-cshared_preload_libraries=pg_stat_statements"
      ]
    environment:
      POSTGRES_USER: graph-node
      POSTGRES_PASSWORD: let-me-in
      POSTGRES_DB: graph-node
      # FIXME: remove this env. var. which we shouldn't need. Introduced by
      # <https://github.com/graphprotocol/graph-node/pull/3511>, maybe as a
      # workaround for https://github.com/docker/for-mac/issues/6270?
      PGDATA: "/var/lib/postgresql/data"
      POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
Enter fullscreen mode Exit fullscreen mode

Worth taking note here are the following:

  • The node connects to localhost:http://host.docker.internal:8545 which is our locally dpeloyed hardhat node
  • We are using the latest ipfs docker image because of some compatibility issues with Mac M1
  • Run docker-compose up to startup your local Graph Node server. Subgraphs deployed in our Grap Node would have its own GraphQL endpoint. You should see multiple calls made by the Graph Node server in your hardhat node logs similar to below

Hardhat Logs

  1. Copy the Greeting ABI from '/artifacts/contracts/Greeting.sol/Greeting.json` in our Hardhat project
  2. Create a new file called Greeting.json inside the abis folder of our subgraph project
  3. Replace the code in mapping.ts with the code in this gist

Dont worry of the import errors for now. We will generate the schema and other related code later on

  1. Replace the code in schema.graphql with the code from this gist

This would mean that we will store Greeting objects in our subgraph.

  1. Update the subgraph.template.yaml with the code from this gist
  • replace source: address and starbBock with the address and blocknumber that we copied earlier after deploying the Greeting contract

This file will tell the Graph Node server the following:

  • Deploy our subgraph in the local node.
  • This subgraph will subscribe to events coming from the stated address in source: address
  • Whenever the event GreetingChanged(string,address) is emitted. Pass the event object to the handler handleGreetingChanged which we created earlier 9.Update testnet.json` with the same address and startblock we defined earlier
{
    "startBlock": 1,
    "Greeting": "0x5FbDB2315678afecb367f032d93F642f64180aa3"
}
Enter fullscreen mode Exit fullscreen mode
  1. Run npm i
  2. Run npm run compile
  3. Run npm run create-local
  4. Run npm run deploy-local. You should see something like this once you have deployed successfully
Build completed: Qmerhoqakv6BuiWyhSjSpQ7a5dVK92QUZVxtgnSFUXsA8s

Deployed to http://localhost:8000/subgraphs/name/Greeter/graphql

Subgraph endpoints:
Queries (HTTP):     http://localhost:8000/subgraphs/name/Greeter
Enter fullscreen mode Exit fullscreen mode

Testing the subgraph

  1. Create a new node project
  2. Run npm i ethers
  3. Create an index.js file and copy paste the code below
const ethers = require("ethers");

const provider = new ethers.JsonRpcProvider(
  "http://127.0.0.1:8545"
);

const signer = new ethers.Wallet("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", provider);

const ABI = [
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "initialGreeting",
          "type": "string"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "string",
          "name": "newGreeting",
          "type": "string"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "changedBy",
          "type": "address"
        }
      ],
      "name": "GreetingChanged",
      "type": "event"
    },
    {
      "inputs": [],
      "name": "getGreeting",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "greeting",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "newGreeting",
          "type": "string"
        }
      ],
      "name": "setGreeting",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ];

const Greeting = new ethers.Contract(
  "0x5FbDB2315678afecb367f032d93F642f64180aa3",
 ABI,
  signer
);

const updateGreeting= async () => {

const tx = await Greeting.setGreeting("hello world")
const receipt = await tx.wait()  
  console.log(receipt.logs)


}

updateGreeting()
Enter fullscreen mode Exit fullscreen mode
  • Replace the wallet private key with one of the private keys from the hardhat node.
  • Replace the Greeting contract address with the deployed address of our Greeting contract
  • Run node index.js
  • Once successful, go to http://localhost:8000/subgraphs/name/Greeter
  • Run this query
query MyQuery {
  greetings {
    id
    changedBy
    newGreeting
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. You should be able to see one greeting object displayed as the query result

GQL Query Result

Congratulations you have now deployed and tested a subgraph locally.

Top comments (1)

Collapse
 
iandjx profile image
Ian de Jesus

dont understand why code blocks aren styled correctly. Will reformat the blog post later today