/moonbase-moonlotto-workshop

Building APIs on Moonbase with The Graph

Primary LanguageSolidity

The Graph - MoonLotto Subgraph Workshop

Learn how to build a GraphQL API to query data for a Lottery contract on Moonbase Alpha.

See also Using The Graph on Moonbeam

Subgraph Workshop

Prerequisites

To be successful in this workshop, you should have Node.js installed on your machine.

Getting started

Creating the Graph project in the Graph console

To get started, open the Graph Console and either sign in or create a new account.

Next, go to the dashboard and click on Add Subgraph to create a new subgraph.

Here, set your graph up with the following properties:

  • Subgraph Name - Moonbeamsubgraph (or your preferred name)
  • Subtitle - A subgraph for querying data from Moonbase
  • Optional - Fill the description and GITHUB URL properties

Once the subgraph is created, we will initialize the subgraph locally.

Clone the example project

Next, we'll be using the example Moonlotto subgraph

Clone the repo locally, then change into the new directory and install the dependencies:

git clone git@github.com:PureStake/moonlotto-subgraph.git

cd moonlotto-subgraph

Install the node dependencies using yarn:

yarn

Create the TS types for The Graph (requires the artifacts folder created by contract compilation). Files will be created in the src/types directory:

yarn codegen

In src/mappings you will see code including imports for the types that were generated by the CLI:

import {
  PlayerJoined,
  LotteryResult,
} from "./types/MoonLotto/MoonLotto";
import { Round, Player, Ticket } from "./types/schema";

Graph Node finds smart contract events for your subgraph and runs the mapping handlers you provided. The mapping is a WASM module that creates or updates the data entities that Graph Node stores in response to Ethereum events.

These events are defined in the Subgraph.yml file along with the smart contract address, start block, entities that you would like to index, and other configurations:

specVersion: 0.0.2
description: Moonbeam lottery subgraph tutorial
repository: https://github.com/PureStake/moonlotto-subgraph
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: MoonLotto
    network: mbase
    source:
      address: '0x0bb1ac004BacefFb1fb1a4aA3f74835372A5deD0'
      abi: MoonLotto
      startBlock: 33206
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.4
      language: wasm/assemblyscript
      file: ./src/mapping.ts
      entities:
        - Player
        - Round
        - Ticket
      abis:
        - name: MoonLotto
          file: ./artifacts/contracts/MoonLotto.sol/MoonLotto.json
      eventHandlers:
        - event: PlayerJoined(uint256,address,uint256,bool,uint256)
          handler: handlePlayerJoined
        - event: LotteryResult(uint256,address,uint256,bool,uint256,uint256)
          handler: handleLotteryResult

The GraphQL Schema located at schema.graphql defines the entities to be indexed as well as the fields for each entity / type:

type Round @entity {
  id: ID!
  index: BigInt!
  prize: BigInt! 
  timestampInit: BigInt!
  timestampEnd: BigInt
  tickets: [Ticket!] @derivedFrom(field: "round")
}

type Player @entity {
  id: ID!
  address: Bytes!
  tickets: [Ticket!] @derivedFrom(field: "player")
}

type Ticket @entity {
  id: ID!
  isGifted: Boolean!
  player: Player!
  round: Round!
  isWinner: Boolean!
}

The eventHandlers in subgraph.yaml define the events that will be handled in the mappings and map the event to a function specified in src/mappings.ts:

// src/mapping.ts
import {
  PlayerJoined,
  LotteryResult,
} from "./types/MoonLotto/MoonLotto";
import { Round, Player, Ticket } from "./types/schema";

export function handlePlayerJoined(event: PlayerJoined): void {
  // ID for the round
  let roundId = event.params.round.toString();
  // try to load Round from a previous player
  let round = Round.load(roundId);
  // if round doesn't exists, it's the first player in the round -> create round
  if (round == null) {
    round = new Round(roundId);
    round.timestampInit = event.block.timestamp;
  }
  round.index = event.params.round;
  round.prize = event.params.prizeAmount;

  round.save();

  // ID for the player:
  // issuer address
  let playerId = event.params.player.toHex();
  // try to load Player from previous rounds
  let player = Player.load(playerId);
  // if player doesn't exists, create it
  if (player == null) {
    player = new Player(playerId);
  }
  player.address = event.params.player;

  player.save();

  // ID for the ticket (round - player_address - ticket_index_round):
  // "round_number" + "-" + "player_address" + "-" + "ticket_index_per_round"
  let nextTicketIndex = event.params.ticketIndex.toString();
  let ticketId = roundId + "-" + playerId + "-" + nextTicketIndex;

  let ticket = new Ticket(ticketId);
  ticket.round = roundId;
  ticket.player = playerId;
  ticket.isGifted = event.params.isGifted;
  ticket.isWinner = false;

  ticket.save();  
}

export function handleLotteryResult(event: LotteryResult): void {
  let roundId = event.params.round.toString();    
  // ID for the round:
  // round number
  let round = Round.load(roundId);  
  round.prize = event.params.prizeAmount;
  round.timestampEnd = event.block.timestamp;
  
  round.save();
  
  let ticketIndex = event.params.ticketIndex.toString();
  let playerId = event.params.winner.toHex();  
  let ticketId = roundId + "-" + playerId + "-" + ticketIndex;
  // ID for the ticket:
  // "round_number" + "-" + "player_address" + "-" + "ticket_index"
  let ticket = Ticket.load(ticketId);  
  ticket.isWinner = true;
  
  ticket.save();
}

Updating the deploy script

Next, open package.json and update the deploy script to include the project name from the Graph explorer.

Replace the subgraph name. It should look something like this: username/subgraphname:

"deploy": "npx graph deploy username/subgraphname --ipfs https://api.thegraph.com/ipfs/ --node https://api.thegraph.com/deploy/",

Deploying the subgraph

You should now be able to deploy the subgraph:

yarn deploy

Once the subgraph has been deployed, you should be able to open the Graph Explorer and query data from the subgraph.

Try out the following queries to see what selection sets you are able to query from your API:

# rounds
{
  rounds {
    id
    index
    prize
    timestampInit
  }
}

# rounds with order direction
{
  rounds(
    orderDirection: desc
    orderBy: id
  ) {
    id
    index
    prize
    timestampInit
  }
}

# tickets
{
  tickets {
    id
    isGifted
    isWinner
    round {
      id
      index
      timestampEnd
    }
    player {
      id
      id
    }
  }
}

# players
{
  players {
    id
    address
    tickets {
      id
      isGifted
      isWinner
      round {
        id
        prize
        index
      }
    }
  }
}