Start Subgraph Development
Let's start by creating a subgraph with command
graph init --product hosted-service <GITHUB_USERNAME>/<SUBGRAPH_NAME>
. This will ask for smart contract address to be used to initialize the subgraph.For this example we want to develop subgraph for
UniswapV2Pair.sol
so we find an instance of this contract on Etherscan and use that as input to above command. We found 0x00040a7ebfc9f6fbce4d23bd66b79a603ba1c323
and will use it as input.This command will also ask for other things like network, name of the contract. Please give a suitable value and wait for it to finish.
Once this command finishes you will have a working subgraph ready in <SUBGRAPH_NAME> directory. This subgraph does not index anything as it's schema and mapping code are empty. it's just a simple template that helps us with subgraph manifest file and ABI for above contract.
If we wanted to index only one SushiSwap pair then we could start filling in
schema.graphql
and mapping.ts
but we need to track all the pairs of SushiSwap exchange. Let's figure out if we can automate the process of creating a subgraph of every pair or we need to add a source
entry for every pair manually in the manifest file subgraph.yaml
.Looking at smart contract of SushiSwap we find that there is
UniswapV2Facotry.sol
this contract is used to deploy a new exchange pair. This contract also emits an event PairCreated(address indexed token0, address indexed token1, address pair, uint)
that we can subscribe to create dynamic subgraph sources for every pair deployed.Please note that there are protocols which don't have a factory contract. For such protocols we need to add a source entry for every market manually in manifest file like we did for curve exchange subgraph
Let's add following to the
subgraph.yaml
just below dataSources
key.We assume knowledge of subgraph manifest, please read subgraph documentation at https://thegraph.com/docs/developer/create-subgraph-hosted#the-subgraph-manifest for more information on manifest file.
- kind: ethereum/contract
name: UniswapV2Factory
network: mainnet
source:
address: "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac"
abi: UniswapV2Factory
startBlock: 10794228
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
entities:
- Block
- Account
- Token
- Market
- Pair
abis:
- name: ERC20
file: ./abis/IERC20.json
- name: UniswapV2Factory
file: ./abis/IUniswapV2Factory.json
eventHandlers:
- event: PairCreated(indexed address,indexed address,address,uint256)
handler: handlePairCreated
file: ./src/uniswapV2Factory.ts
The
address
here is the address of SushiSwap factory contract and the startBlock
is the block number of the block in which this factory contract was deployed.This entry tells subgraph indexer node to listen to
PairCreated
event on factory contract deployed at address 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac
and trigger execution of handler function handlePairCreated
in mapping file uniswapV2Factory.ts
. We will also need to add ABI json files for both IERC20
token and IUniswapV2Factory.json
contracts. We can get these ABIs from Etherscan or by compiling smart contract in SushiSwap github repository.Please make sure that you use correct ABI json files because if you use an ABI generated from different version/fork of contract then it may have different function and event signatures which will cause trouble in mapping code. To avoid any such issues we recommend downloading ABI json from etherscan from contract page itself because the ABI at contract page is always in sync with the current version of deployed contract.
Let's create a file named
uniswapV2Factory.ts
in src
directory of subgraph project and start implementing the handler function with following lineexport function handlePairCreated(event: PairCreated): void {}
We will notice that this
PairCreated
is showing an error because our IDE (we use vs code for subgraph development) can not find definition of this class. Good news is we don't need to create classes for every event of contract. Graph CLI tool has a command codegen
that can generate all required class definitions for us from the given ABI. Before we run this
codegen
command we also need to copy entities from common subgraph schema to schema.graphql
so that codegen
can also generate class definitions for these entities. So let' go ahead an copy code from Common Subgraph Schema to graphql file. Now run following command to generate sources for ABIs and graphql entitiesnpm run codegen
It will create a directory named
generated
in subgraph directory and this will have one file for each ABI and one file for schema. All these files are webassembly code files and are defining the class definitions of smart contract functions, events, input and output arguments along with graphql entities. We will be using these classes directly in our mapping code to keep it type safe and avoid runtime errors.Yes yes it's a lot of work before we event start with the real indexing code development but it only one time setup, once done we just need to keep repeating following steps -
- 1.Update schem and mapping code
- 2.Deploy subgraph
- 3.Verify correctness of subgraph data
- 4.Debug subgraph in case of error or incorrect data
- 5.Update schem and mapping code ...
First let's think what we want to do in the handler of the event
PairCreated
. We want to create an entry of Market
entity and create a Pair
template source to listen to UniswapV2Pair.sol
contract events. We will write following code in the handler function to create Market
and Token
entities that the Market
entity links to - // Create a tokens and market entity
let token0 = getOrCreateERC20Token(event, event.params.token0)
let token1 = getOrCreateERC20Token(event, event.params.token1)
let lpToken = getOrCreateERC20Token(event, event.params.pair)
let market = getOrCreateMarket(
event,
event.params.pair,
ProtocolName.SUSHISWAP,
ProtocolType.EXCHANGE,
[token0, token1],
lpToken,
[]
)
lpToken.mintedByMarket = market.id
lpToken.save()
As you can see from code above it's really easy to create entities from SimpleFi common subgraph schema using function imported from SimpleFi mapping code library. Let's explain the code line by line.
Line number 2, 3 and 4 are simply calling
getOrCreateERC20
function of library code which will internally create a record of Token
entity and populate it's attribute like symbol
, name
and decimals
by making contract calls. It will take care of not creating the record twice if it already exists.Statement starting at line number 6 calls
getOrCreateMarket
function of library code which will internally create a Market
entity and initialize it's attributes to zero values so that further event handlers can update these values without worrying about these attributes being null. You can check code for both function in common.ts
.We also need to copy both
common.ts
and constants.ts
from SimpleFi library code in src
directory. Then addProtocolName.SUSHISWAP
enum value in both schema.graphql
and constants.ts
. We will also need to import required functions and entities from common.ts
and constants.ts
with following code -import {
PairCreated
} from "../generated/UniswapV2Factory/UniswapV2Factory"
import {
getOrCreateAccount,
getOrCreateERC20Token,
getOrCreateMarket
} from "./common"
import { ProtocolName, ProtocolType } from "./constants"
You will notice that we are using suffix
Entity
to the imported entity classes, it's just a convention that we follow to avoid confusion between schema entity classes and smart contract classes which often have same names like PairEntity
.Now if you have been following this guide line by line then you should have two
kind
entries in dataSources
key in subgraph.yaml
. One was created automatically when we initialized the subgraph with graph init ...
command and one we added above. In this step we need to remove the one that was created automatically because we are converting that to a template. After removing this kind
entry we add following to the subgraph.yaml
at the end without any tabs (templates
is a top level key in manifest just like dataSources
) -templates:
- kind: ethereum/contract
name: UniswapV2Pair
network: mainnet
source:
abi: UniswapV2Pair
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
file: ./src/uniswapV2Pair.ts
entities:
- Block
- Account
- Token
- Market
- Transaction
- Transfer
- Position
- PositionSnapshot
- Pair
- PairSnapshot
abis:
- name: ERC20
file: ./abis/IERC20.json
- name: UniswapV2Factory
file: ./abis/IUniswapV2Factory.json
- name: UniswapV2Pair
file: ./abis/IUniswapV2Pair.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
- event: Sync(uint112,uint112)
handler: handleSync
- event: Mint(indexed address,uint256,uint256)
handler: handleMint
- event: Burn(indexed address,uint256,uint256,indexed address)
handler: handleBurn
You can clearly notice that there is no
address
and startBlock
keys in this template kind
entry. That's because subgraph does not start the source from this template on starting the subgraph. We need to create the source from this template in our mapping code.Before we do that we need to run the
codegen
command again to generate classes for this newly added template.We also need to remove the
generated
directory before running codegen
command again because we have reorganized the source entries in our manifest file. Otherwise there will be two files with same name and same sources some ABIs and we may import the wrong one by mistake. It's not going to cause trouble till only one developer is working on the subgraph but when someone else clones the repository and runs codegen
then they won't get the older (before reorganization) files and their mapping code will not compile. We don't commit generated
files in the git repository.We just need to add a single like of code in our handler function for
PairCreated
event in uniswapV2Factory.ts
- // Start listening for market events
UniswapV2Pair.create(event.params.pair)
As always if only writing code was so simple, we also need to import the template class with following statement -
import { UniswapV2Pair } from "../generated/templates"
y0 guys we have successfully created a subgraph that we can deploy and check if it's working as expected. Was not too tough was it? Wait till you get to the next article. Just kidding
😉
Some of you may have noticed that we did not talk the code that creates the Pair entity. We will talk about it in our next article after we explain the reason for creating this entity.
Last modified 1yr ago