Skip to main content

Multichain Indexing

Squids can extract data from multiple blockchains into a shared data sink. When data is stored to PostgreSQL, it can be served as a unified multichain GraphQL API.
Multichain indexing enables powerful cross-chain analytics and unified data access across different blockchain networks.

Setup

To index multiple chains, run one processor per source network:

1. Create separate entry points

Make a separate entry point (main.ts or equivalent) for each processor:
├── src
   ├── bsc
   ├── main.ts
   └── processor.ts
   ├── eth
   ├── main.ts
   └── processor.ts
Alternatively, parameterize your processor using environment variables. You can set these on a per-processor basis if you use a deployment manifest.

2. Configure processor commands

Add sqd commands for running each processor to commands.json:
commands.json
{
  "process:eth": {
    "deps": ["build", "migration:apply"],
    "cmd": ["node", "lib/eth/main.js"]
  },
  "process:bsc": {
    "deps": ["build", "migration:apply"],
    "cmd": ["node", "lib/bsc/main.js"]
  }
}
See the full example in the multichain transfers repository.

3. Configure deployment manifest

If you plan to use sqd run for local runs or deploy to SQD Cloud, list your processors in the deploy.processor section of your deployment manifest:
squid.yaml
deploy:
  processor:
    - name: eth-processor
      cmd: ["sqd", "process:prod:eth"]
    - name: bsc-processor
      cmd: ["sqd", "process:prod:bsc"]
Make sure to give each processor a unique name to avoid conflicts.

PostgreSQL Configuration

When using PostgreSQL, ensure the following:

1. Unique state schema for each processor

Each processor needs its own state schema to track sync progress independently:
src/bsc/main.ts
processor.run(
  new TypeormDatabase({
    stateSchema: "bsc_processor",
  }),
  async (ctx) => {
    // BSC processing logic
  }
);
src/eth/main.ts
processor.run(
  new TypeormDatabase({
    stateSchema: "eth_processor",
  }),
  async (ctx) => {
    // Ethereum processing logic
  }
);
State schemas track the sync progress of each processor independently, enabling reliable multichain indexing.

2. Shared schema and API

The schema and GraphQL API should be shared among all processors to provide a unified data model.

Handling concurrency

  • Cross-chain data dependencies are to be avoided. With the default isolation level used by TypeormDatabase, SERIALIZABLE, one of the processors will crash with an error whenever two cross-dependent transactions are submitted to the database simultaneously. It will write the correct data when restarted, but such restarts can impact performance, especially in squids that use many (>5) processors.
  • The alternative isolation level is READ COMMITTED. At this level data dependencies will not cause the processors to crash, but the execution is not guaranteed to be deterministic unless the sets of records that different processors read/write do not overlap.
  • To avoid cross-chain data dependencies, use per-chain records for volatile data. E.g. if you track account balances across multiple chains you can avoid overlaps by storing the balance for each chain in a different table row. When you need to combine the records (e.g. get a total of all balaces across chains) use a custom resolver to do it on the GraphQL server side.
  • It is OK to use cross-chain entities to simplify aggregation. Just don’t store any data in them:
    type Account @entity {
      id: ID! # evm address
      balances: [Balance!]! @derivedFrom("field": "account")
    }
    
    type Balance @entity {
      id: ID! # chainId + evm address
      account: Account!
      value: BigInt!
    }
    

On file-store

Ensure that you use a unique target folder for each processor.

Example

A complete example is available here.