DocumentationSubgraphsBasicsHandlers

Event Handlers

Event handlers are your main tool. Event handlers will get triggered when the indexer encounters the specified in the manifest file (link to where we talk about specifying events) events. They are by far the fastest way to collect the needed data.

From within the handler you will have access to data such as, contract address, transaction hash, block data, log topics, log data, receipts, etc.

For example, lets assume we are listening to the ERC721 Transfer event.

This is our handler definition in the subgraph.yaml file

...
eventHandlers:
	- event: Transfer(indexed address,indexed address,indexed uint256)
	  handler: handleTransfer
...

This is how you can access different information within the mappings

...
 
export function handleTransfer(event: Transfer): void {
	// Contract address
	event.address;
 
	// Event params
	event.params.from;
	event.params.to;
	event.params.tokenId;
 
	// Block data
 
	event.block.number;
	event.block.hash;
 
	// Transaction data
	event.transaction.hash;
	event.transaction.index;
	event.transaction.from;
	event.transaction.to;
}
 
...

Note: Sometimes events include indexed params. Indexed params are used for filtering events, and each event can have up to 3 indexed params (except anonymous events, which can have 4), which become event topics and are excluded from the event data. Event topic length is 32 bytes, so values that are less than 32 bytes will get padded with zeros. In the case with indexed parameters that have complex or dynamic types, such as String, Array, Tuple or Struct, those parameters will be keccak256 hashed to a hash with length of 32 bytes. In this case event.params will return the keccak256 hash, which cannot be decoded back to the original value and you’ll have to get that value using other means. Keep this in mind when writing your own contracts and events, and choose your indexed parameters wisely.

Starting from specVersion 0.0.5 and apiVersion 0.0.7, event handlers can have access to the receipt for the transaction which emitted them, this allows you to have access additional transaction information or even other events emitted in the same transaction. You’ll first need to enable the receipts in the subgraph manifest by adding receipt: true to your handler declaration. It’s an optional key and defaults to false.

eventHandlers:
        - event: MyEvent(address, uint256)
          handler: handleMyEvent
					receipt: true

Then in your handler you can access the receipt via the Event receipt field.

export function handleMyEvent(event: MyEvent) {
	...
	let receipt = event.receipt;
	if (receipt) {
		// do something with the receipt
	}
}

If the receipt key is omitted or set to false in the manifest, null value will be returned instead.

Note: Because the receipt filed type is TransactionReceipt | null we always need to perform a nullability check, otherwise the compiler will throw an error.

All available fields can be found here.

Example for using receipts:

You may notice that in our eventHandler definition, the keyword indexed is present in the event signature [The code has been generated by the graph-cli] As you may know every Event has up to 4 topics. The first topic or topic0 is the keccak256 hash of the event signature, but the indexed keyword is ignored when the signature is hashed. The rest of the topics are populated with the parameters marked as indexed. For example the ERC20 and ERC721 Transfer events have the same topic0, both have the following signature Transfer(address,address,uint256) The difference between the two is that ERC20 has 2 indexed events and ERC721 has three. So for example if you want to track any Transfer event and want to know if it’s an ERC20 or ERC721, you can do the following:

  1. Activate receipts by adding receipt: true in the handler definition, like so:

    eventHandlers:
            - event: Transfer(indexed address,indexed address,indexed uint256)
              handler: handleTransfer
    					receipt: true
  2. In the handler do the following

...
 
export function handleTransfer(event: Transfer): void {
	let topic0 = Bytes::fromHexString("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
	...
	let receipt = event.receipt;
	if (receipt) {
		let logs = receipt.logs;
		for (let i = 0, k = logs.length; i < k; ++i) {
	    if (logs[i].topics[0] = topic0) {
					if (logs[i].topics.length == 3) {
						// Is ERC20
						...
					} else if(logs[i].topics.length == 4) {
						// Is ERC721
						...
					}
			}
	  }
}
...

Anonymous events

Anonymous events are declared using the anonymous keyword. Those events don’t have a selector and topic0 is not the keccak256 hash of the event signature. For that reason anonymous events can have up to 4 indexed parameters, contrary to the non-anonymous, which can have 3. To track anonymous events you will need to provide the topic0 , which for anonymous events will be the the first indexed param.

eventHandlers:
  - event: LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)
    topic0: '0x644843f351d3fba4abcd60109eaff9f54bac8fb8ccf0bab941009c21df21cf31'
    handler: handleGive

More info on Solidity events can be found here

Call Handlers

Sometimes it is not possible to collect all needed information with event handlers, because either the contract does not emit any, in order to optimise cost, or not all information the subgraph developer wants to index is emitted via events. A subgraph can subscribe to calls made to the defined datasource addresses.

One important note about call handlers is that they depend on the Parity trace API, which most newer EVM compatible chains do not support. Be sure to check if the EVM chain you want to index supports it.

Full information on Call Handler can be found in TheGraph docs

Block Handlers

Along with event and call handlers, subgraph can subscribe to new blocks. This means that when a new block is appended to the chain, the subgraph will run a function. But running a function on every block is expensive and time consuming, which will make your subgraph sync times very, very slow. Yet, sometimes, we need to do something every n blocks or on specific occasion. TheGraph provides several filters , that can be helpful in some use cases. It is really discouraged to use Block Handlers without filters.

We won’t go in details about Block Handlers here, you can check TheGraph docs to get basic understanding, and we will focus mostly on couple of use cases, where block handlers can be very helpful.

Call filter:

blockHandlers:
  - handler: handleBlockWithCallToContract
    filter:
      kind: call

This block handler will trigger on blocks that contain calls to the dataSource contract address. This filter can be useful for contracts that are rarely called, but we need to do something when that happens. Otherwise it will behave just like a regular block handler and it will make your subgraph slow to sync.

Note: Like the Call Handlers, call filters rely on the Parity trace API. Be sure to check if your network supports it, before using the call filter.

Polling filter

A block handler using the polling filter will trigger every n blocks.

blockHandlers:
  - handler: handleBlock
    filter:
      kind: polling
      every: 10

This type of block handlers are useful in several caaes, for example:

  1. To do some calculations every n blocks
  2. Follow the change of price of a token
  3. Check accumulated rewards

Once filter

A block handler using the once filter (also called initialization handler) will trigger only once when the first block is indexed. Keep in mind that this is still a block handler, and as we mentioned in the manifest section, block handlers are triggered last, after the event and call handlers. To get around that, set the startBlock of the dataSource one block before the contract deployment block.

This type of block handlers are could be used to:

  1. Setup initial entities
  2. Grab on-chain data before indexing, such as configurations or list of allowed addresses
  3. Dynamically initialise dataSources from templates for specific addresses

For more info Initialization and Polling handlers, you can watch this video by Kent from GraphBuildersDAO