Developers Docs
Search…
Implement Handlers

Code generation

In last chapter we created schema and manifest, but how do we use entities, events and smart contracts defined there?
Graph CLI can generate AssemblyScript types so we can easily use it in our mapping code. Most often there's no need to run graph command directly, but use the preconfigured one in package.json:
1
npm run codegen
Copied!
If there are no errors in subgraph definition, outcome of this command will be new folder generated containing AssemblyScript classes for events, calls and entities. We can then easily import those into mapping code.
Every time GraphQL schema or contract ABIs are modified code generation has to be re-executed to reflect changes.

Mappings

Now we're getting to the core of our subgraph - mapping code transforms events and function calls we subscribed to to the entities defined in schema. Code is written in AssemblyScript. (If you are first time AssemblyScript user prepare your nerves
πŸ™‚
. It is very strict language so it has lot of limitations compared to traditional programming languages. Good overview can be find in AssemblyScript book.)

Event Handlers

Let's start with implementing event handler for Deposit event. We import Deposit class and create the function - it has to match specifications from subgraph manifest:
1
import {
2
Deposit,
3
} from "../../generated/MasterChef/MasterChef";
4
​
5
export function handleDeposit(event: Deposit): void {}
Copied!
If we look at the contract code, we can see that couple of steps take place:
  • update farm reward variables
  • calculate amount of Sushi entitled to user and send it to him as reward
  • send LP tokens from user to MasterChef
  • update user's position
We need to handle those steps in our mapping code in order to keep correct state of market and user's position. First, let's extract the data that was emitted by event. Event params are stored in event.params object:
1
let masterChef = MasterChefEntity.load(event.address.toHexString()) as MasterChefEntity;
2
let farmId = masterChef.id + "-" + event.params.pid.toString();
3
let sushiFarm = getOrCreateSushiFarm(masterChef, null, event, farmId);
4
let user = getOrCreateAccount(event.params.user);
5
let amount = event.params.amount;
Copied!
In this subgraph Market represents a single SushiSwap farm. Usually ID of the Market is simply the address of the smart contract implementing the main functionality of the protocol. However in case of Sushi farms, one MasterChef contract implements all the Sushi farms. So in order to make it unique, we append farm's PID to the MasterChef address and use that as Market ID. E.g. farm with PID 100 will have ID "0xc2edad668740f1aa35e4d8f227fb8e17dca888cd-100"
Next we update farm reward variables. We won't go into implementation details of updateFarm, but it closely reflects the implementation of updatePool function of the contract.
1
// update farm/pool reward variables
2
updateFarm(sushiFarm, event.block);
Copied!
What's the amount of Sushi rewards user received as part of this transaction? Unfortunately, this information is not emitted so we'll need to do the calculation ourselves. Total amount of Sushi user is entitled to is equal to amount of LP tokens user deposited multiplied by the accSushiPerShare variable which we just updated in previous step. And we need to subtract amount of Sushi already paid out to user to get amount of Sushi received by user in this TX.
1
// calculate harvested Sushi amount
2
let userInfo = getOrCreateUserInfo(user.id, sushiFarm.id);
3
let harvestedSushi = userInfo.amount
4
.times(sushiFarm.accSushiPerShare)
5
.div(ACC_SUSHI_PRECISION)
6
.minus(userInfo.rewardDebt);
Copied!
State of the farm also needs update - both protocol specific entity SushiFarm and generic entity Market. We're updating new total supply of LP tokens deposited into the farm. As you can see in snippet below, we're using function updateMarket from SimpleFi's common library to update Market entity.
1
// update sushifarm
2
sushiFarm.totalSupply = sushiFarm.totalSupply.plus(amount);
3
sushiFarm.save();
4
​
5
// update market LP supply
6
let market = Market.load(sushiFarm.id) as Market;
7
updateMarket(
8
event,
9
market,
10
[new TokenBalance(sushiFarm.lpToken, masterChef.id, sushiFarm.totalSupply)],
11
sushiFarm.totalSupply
12
);
Copied!
Now market state is updated, let's do the same for user's position. Good news is that also here we can just call the appropriate function from SimpleFi's common library:
1
export function investInMarket(
2
event: ethereum.Event,
3
account: Account,
4
market: Market,
5
outputTokenAmount: BigInt,
6
inputTokenAmounts: TokenBalance[],
7
rewardTokenAmounts: TokenBalance[],
8
outputTokenBalance: BigInt,
9
inputTokenBalances: TokenBalance[],
10
rewardTokenBalances: TokenBalance[],
11
transferredFrom: string | null
12
)
Copied!
This function will internally create market and position snapshot, create transaction entity and finally update user's position. Now the tricky part is to collect and pass correct values to the function. We already have event, account and market references. Let's find output, input and reward token amounts - those amounts are changes in user's balance as a result of Deposit transaction he made.
1
// sushi farms don't have output token, but keep track of provided LP token amounts
2
let outputTokenAmount = amount;
3
​
4
// user deposited `amount` LP tokens
5
let inputTokenAmounts: TokenBalance[] = [new TokenBalance(sushiFarm.lpToken, user.id, amount)];
6
​
7
// number of Sushi tokens user received in this transaction
8
let rewardTokenAmounts: TokenBalance[] = [];
9
let rewardTokens = market.rewardTokens as string[];
10
rewardTokenAmounts.push(new TokenBalance(rewardTokens[0], user.id, harvestedSushi));
Copied!
Then we do the same for output, input and reward token balances - those balances are new total's of user's holdings, as a results of Deposit transaction.
1
// keep track of provided LP token amounts
2
let outputTokenBalance = userInfo.amount;
3
​
4
// inputTokenBalance -> number of LP tokens that can be redeemed by user
5
let inputTokenBalances: TokenBalance[] = [];
6
inputTokenBalances.push(new TokenBalance(sushiFarm.lpToken, user.id, userInfo.amount));
7
​
8
// Sushi amount claimable by user - at this point it is 0 as all the pending reward Sushi has just
9
// been transferred to user
10
let rewardTokenBalances: TokenBalance[] = [
11
new TokenBalance(rewardTokens[0], user.id, BigInt.fromI32(0)),
12
];
Copied!
Finally we have everything to actually call the investInMarket function and finish the deposit handler.
1
investInMarket(
2
event,
3
user,
4
market,
5
outputTokenAmount,
6
inputTokenAmounts,
7
rewardTokenAmounts,
8
outputTokenBalance,
9
inputTokenBalances,
10
rewardTokenBalances,
11
null
12
);
Copied!
In similar fashion we implement handlers for withdraw and emergencyWithdraw events.

Call Handlers

Not let's do a showcase for a call handler. Remember, due to smart contract implementation details we don't have events defined and emitted for everything we're interested in. One such thing is farm creation. We need to know when a new farm is added so we can create a Market entity and start tracking all the investments made in and out of the farm. We will use callHandler to pick up this piece of information.
CallHandler is triggered once a call to contract's function (which we subscribed to in manifest) is made. We can extract values of arguments passed to a function.
Let's import AddCall and define the handler:
1
import { AddCall } from "../../generated/MasterChef/MasterChef";
2
​
3
export function handleAdd(call: AddCall): void {}
Copied!
We want to use this handler to create SushiFarm and Market entities. But as a prerequisite, if this is the first farm ever created, we need to create MasterChef entity as well:
1
let masterChef = MasterChefEntity.load(call.to.toHexString());
2
​
3
// create MasterChef entity and store Sushi token address
4
if (masterChef == null) {
5
masterChef = new MasterChefEntity(call.to.toHexString());
6
masterChef.version = BigInt.fromI32(1);
7
​
8
// get sushi address, store it and start indexer if needed
9
let masterChefContract = MasterChef.bind(call.to);
10
let sushi = masterChefContract.sushi();
11
​
12
// initialize other params
13
let sushiToken = getOrCreateERC20Token(event, sushi);
14
masterChef.sushi = sushiToken.id;
15
masterChef.numberOfFarms = BigInt.fromI32(0);
16
masterChef.totalAllocPoint = BigInt.fromI32(0);
17
masterChef.sushiPerBlock = masterChefContract.sushiPerBlock();
18
masterChef.bonusEndBlock = masterChefContract.bonusEndBlock();
19
masterChef.bonusMultiplier = masterChefContract.BONUS_MULTIPLIER();
20
masterChef.save();
21
}
Copied!
As you can see, some information we can fetch from the call object, but for some we need to use contract calls. In general it is best practice to minimize number of contract calls because they negatively impact indexing efficiency. Additional reason is that contract calls report back state of the contract variables at the end of the block - and not immediately after transaction has been included in block. So we lose on precision and granularity of data.
Once MasterChef is created we can create new farm entities:
1
// create SushiFarm entity
2
let farmId = masterChef.id + "-" + masterChef.numberOfFarms.toString();
3
let sushiFarm = getOrCreateSushiFarm(masterChef as MasterChefEntity, call, event, farmId);
Copied!
Let's look at implementation of getOrCreateSushiFarm function. First we create SushiFarm:
1
// create new SushiFarm entity
2
sushiFarm = new SushiFarm(farmId);
3
sushiFarm.farmPid = masterChef.numberOfFarms;
4
sushiFarm.masterChef = masterChef.id;
5
sushiFarm.created = event.block.timestamp;
6
sushiFarm.createdAtBlock = event.block.number;
7
sushiFarm.createdAtTransaction = event.transaction.hash;
8
sushiFarm.totalSupply = BigInt.fromI32(0);
9
sushiFarm.lastRewardBlock = event.block.number;
10
sushiFarm.accSushiPerShare = BigInt.fromI32(0);
11
12
inputToken = getOrCreateERC20Token(event, call.inputs._lpToken);
13
sushiFarm.lpToken = inputToken.id;
14
sushiFarm.allocPoint = call.inputs._allocPoint;
15
sushiFarm.save();
Copied!
And finally we create the market using getOrCreateMarketWithId from common library:
1
// create market representing the farm
2
let marketId = sushiFarm.id;
3
let marketAddress = Address.fromString(sushiFarm.masterChef);
4
let protocolName = ProtocolName.SUSHISWAP_FARM;
5
let protocolType = ProtocolType.TOKEN_MANAGEMENT;
6
let inputTokens: Token[] = [inputToken];
7
let rewardTokens: Token[] = [getOrCreateERC20Token(event, Address.fromString(masterChef.sushi))];
8
​
9
getOrCreateMarketWithId(
10
event,
11
marketId,
12
marketAddress,
13
protocolName,
14
protocolType,
15
inputTokens,
16
null,
17
rewardTokens
18
);
Copied!
Cool, we have successfully created SushiFarm and Market entities! Looking at the MasterChef contract, we can see same needs to be done for other event-less functions which are updating farm's rewards variables - updatePool, set, migrate and massUpdatePools. We won't go into details in this guide, but you can find full subgraph implementation here.

Remarks on using handlers

Keep in mind, using both event and call handlers can be tricky. Sometimes you need to process transaction which triggered both type of handlers. But you can not assume your handlers will be executed in the same order like the code which triggered the handlers. From Graph docs:
The triggers for a data source within a block are ordered using the following process:
  1. 1.
    Event and call triggers are first ordered by transaction index within the block.
  2. 2.
    Event and call triggers with in the same transaction are ordered using a convention: event triggers first then call triggers, each type respecting the order they are defined in the manifest.
  3. 3.
    Block triggers are run after event and call triggers, in the order they are defined in the manifest.
Event handlers are always executed before the call handlers. Sometimes that means we need to create temporary entities in order to process complicated transactions which move a lot assets and execute multiple function calls.

Deploy

That's it, after we implement all the event and call handlers which cover all the smart contract functions which can modify market state, we are ready to deploy the subgraph and start indexing data:
1
$ graph deploy --node https://api.thegraph.com/deploy/ --ipfs https://api.thegraph.com/ipfs/ <your-id>/<subgraph-name>
Copied!
As an exercise you can try to develop handlers for MasterChef V2 - which brings better event coverage, but also it has additional features like multi-token rewards which makes subgraph developer's life more challenging
πŸ™‚
. Happy coding!