Developers Docs
Search
⌃K

Start Subgraph Development

There are 3 main components of a subgraph:
  • GraphQL schema
  • subgraph manifest
  • mappings

GraphQL schema

In schema we will define our data model - set of entities which give a shape to the data we want to collect. As a first thing, we'll add entities from SimpleFi's common subgraph schema. Common schema enables us to use abstraction model which can be applied to any DeFi protocol and thus makes it easier to compare protocols, calculate user's ROIs and do other analytics. However it is not trivial to map features of each protocol to a common model. To make it easier, we most often define additional protocol specific entities which enable us to capture all the data we're interested in and then transform it into common shape. So, in case of MasterChef we will first add representation of a farm:
type SushiFarm @entity {
" masterchef-farmPid "
id: ID!
" pid of the farm/pool assigned by the masterChef "
farmPid: BigInt!
" masterchef contract address "
masterChef: MasterChef!
" sushi LP token which is farm's input token "
lpToken: Token!
" contract which tracks and distributes extra reward tokens "
rewarder: Rewarder
" amount of sushi to distribute per block. "
allocPoint: BigInt!
" total amount of LP tokens provided to farm "
totalSupply: BigInt!
" last block number where sushi distribution occurs. "
lastRewardBlock: BigInt!
" accumulated amount of sushi per share"
accSushiPerShare: BigInt!
" creation timestamp "
created: BigInt!
" creation block number "
createdAtBlock: BigInt!
" creation transaction "
createdAtTransaction: Bytes!
}
As you can see we store all the relevant info - LP token of the farm and its supply, reward related variables and info related to creation of farm. User's position in particular farm needs to be represented as well:
type UserInfo @entity {
" usersAddress-farmPID"
id: ID!
" user taking positions in farm "
user: Account!
" farm to which user position belongs to "
farm: SushiFarm!
" LP token amount the user has provided "
amount: BigInt!
" amount of SUSHI entitled to the user "
rewardDebt: BigInt!
}
Both farm and userInfo are based on the structs used within the smart contract. That way it is straight-forward to keep track of all the contract interactions. There is one more important entity - MasterChef itself. Currently there are 2 versions of MasterChef deployed in production, and in future there might be more. This is the entity:
type MasterChef @entity {
" contract address "
id: ID!
" masterchef contract version "
version: BigInt!
" address of sushi token"
sushi: Token!
" total number of farms/pools this masterChef is tracking "
numberOfFarms: BigInt!
" sum of all allocation points in all farms "
totalAllocPoint: BigInt!
" sushi tokens created per block "
sushiPerBlock: BigInt!
" block number when bonus Sushi period ends "
bonusEndBlock: BigInt
" bonus muliplier for early sushi makers "
bonusMultiplier: BigInt
}
Now we have basic entities defined (there are some more in the actual subgraph implementation, mostly related to MasterChef V2 which we don't cover here). Let's continue with next step.

Subgraph manifest

Subgraph manifest is yaml file where we specify which smart contracts we're interested in, what are the events we subscribe to and where are the mappings which handle the events. Since MasterChef is implemented as a single smart contract, manifest will be short and concise. We define a dataSource:
dataSources:
# Controls pools (farms) and SUSHI issuance - V1 version
- name: MasterChef
kind: ethereum/contract
network: mainnet
source:
abi: MasterChef
address: "0xc2edad668740f1aa35e4d8f227fb8e17dca888cd"
startBlock: 10736242
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
file: ./src/mappings/masterChef.ts
abis:
- name: MasterChef
file: ./abis/MasterChef.json
- name: IERC20
file: ./abis/IERC20.json
entities:
- MasterChef
- Block
- Account
- Token
- Market
- Transaction
- Transfer
- Position
- PositionSnapshot
eventHandlers:
- event: Deposit(indexed address,indexed uint256,uint256)
handler: handleDeposit
- event: Withdraw(indexed address,indexed uint256,uint256)
handler: handleWithdraw
- event: EmergencyWithdraw(indexed address,indexed uint256,uint256)
handler: handleEmergencyWithdraw
callHandlers:
- function: add(uint256,address,bool)
handler: handleAdd
- function: updatePool(uint256)
handler: handleUpdatePool
- function: set(uint256,uint256,bool)
handler: handleSet
- function: migrate(uint256)
handler: handleMigrate
- function: massUpdatePools()
handler: handleMassUpdatePools
In addition to event handlers we have call handlers defined as well - remember, not every state transition is covered by event, that's why we need to subscribe to event-less function calls. Defining the startBlock will save indexing time, because not all the Ethereum block will have to be digested, only the ones where our contract is live. You can find start block by looking at contract creation info in Etherscan. ABI files can be downloaded from there and stored into appropriate directory. In general, you will spend big chunk of time going through Etherscan while developing and troubleshooting subgraphs so make sure to become familiar with it. Now that we have schema and manifest in place, let's get onto the real stuff - implementing the mapping code!