Skip to main content

Multichain Indexing

This page explains how to index from multiple chains in a single indexer.

This means that events from contracts deployed on multiple chains can be used to create and update entities defined in the schema file.

Users are required to populate the network section in config.yaml file for each chain and specify a contract to index from.

Users can then specify event loader/handler for each of the contracts specified in config.yaml file.

Multichain Indexing on Greeter template

Config file

name: Greeter
description: Greeter indexer
#Global contract definitions that must contain all definitions except
#addresses. Now you can share a single handler/abi/event definitions
#for contracts across multiple chains
contracts:
- name: Greeter
abi_file_path: ./abis/greeter-abi.json
handler: ./src/EventHandlers.js
events:
- event: NewGreeting
- event: ClearGreeting
networks:
- id: 137 # Polygon
start_block: 45336336
contracts:
- name: Greeter #A reference to the global contract definition
address: "0x9D02A17dE4E68545d3a58D3a20BbBE0399E05c9c"
- id: 59144 # Linea
start_block: 367801
contracts:
- name: Greeter #A reference to the global contract definition
address: "0xdEe21B97AB77a16B4b236F952e586cf8408CF32A"

The Greeter indexer listens to NewGreeting and ClearGreeting events from Greeter contract (which is defined above networks as a "global contract") to update the Greeting entity.

Notice how the global definition of the "Greeter" contract does not contain any addresses. And in the contracts section of both Polygon and Linea networks, they simply reference the name of the contract ("Greeter") and define the address. Both of these will use the same handler functions and events.

Schema file

type User {
id: ID! # user's account address
greetings: [String!]! # list of greetings made by the user
latestGreeting: String! # most recent greeting
numberOfGreetings: Int! # total number of greetings made
}

Dev note: 📢 When it makes sense, we recommend appending the chain id to the entity id when you are developing multichain indexers. For example if you deploy a contract to two different networks with the same contract address then append -<chainId> to the end of the id to differentiate the contracts between different networks.

Event Handler file

let { GreeterContract } = require("../generated/src/Handlers.bs.js");

GreeterContract.NewGreeting.loader((event, context) => {
context.User.load(event.params.user);
});

GreeterContract.NewGreeting.handler((event, context) => {
let user = event.params.user;
let latestGreeting = event.params.greeting;
let numberOfGreetings = event.params.numberOfGreetings;

let existingUser = context.User.get(event.params.user);

if (existingUser !== undefined) {
context.User.set({
id: user,
latestGreeting: latestGreeting,
numberOfGreetings: existingUser.numberOfGreetings + 1,
greetings: [...existingUser.greetings, latestGreeting],
});
} else {
context.User.set({
id: user,
latestGreeting: latestGreeting,
numberOfGreetings: 1,
greetings: [latestGreeting],
});
}
});

GreeterContract.ClearGreeting.loader((event, context) => {
context.User.load(event.params.user);
});

GreeterContract.ClearGreeting.handler((event, context) => {
let existingUser = context.User.get(event.params.user);
if (existingUser !== undefined) {
context.User.set({
id: user,
latestGreeting: "",
numberOfGreetings: existingUser.numberOfGreetings + 1,
greetings: existingUser.greetings,
});
}
});

Unordered Multichain Mode

To activate "Unordered Multichain Mode", add a field to your config.yaml file like this:

unordered_multichain_mode: true
networks: ...

Or you can set it via environment variable (this would take precedence over config):

UNORDERED_MULTICHAIN_MODE=true

By default the indexer will synchronise the ordering of events across chains, ensuring that indexing is always deterministic and events across networks will be processed in the same order on every indexer run. This deterministic synchronisation is important for some applications, but for others it is not. This is why we have added "unordered multichain mode".

In order for events to be perfectly ordered across multiple networks, the indexer needs to wait for all blocks to increment from each network. This is because the indexer needs to determine which block came first. Hence there is a latency between when an event is emitted and when it is processed by the indexer based on the block interval of the slowest network.

Generally, if operations to update an entity from multiple events across multiple networks are commutative (where the ordering of operations doesnt effect the result) then the ordering of events doesn't need to be ordered and the unordered multichain mode can be set to true. Likewise if there is no overlap between entities from different networks then the unordered multichain mode can be set to true as the ordering of events across the networks is not important.

If unordered multichain mode is set to true, then the indexer will process events as soon as they are emitted, and so the indexer will not wait for all blocks to increment from each network before moving on to the next block. Even though events will remain ordered for a given network, events from different networks can be processed out of order, but it also means that events will be processed as soon as they are emitted.