Skip to main content
Graph Node supports indexing data from multiple blockchain networks simultaneously. This guide covers configuration and best practices for multi-network deployments.

Overview

Multi-network support allows you to:
  • Index the same contract deployed across different networks
  • Aggregate data from multiple chains in a single subgraph
  • Run one Graph Node instance serving multiple networks
  • Scale across networks with different sharding strategies

Configuration

Basic Multi-Network Setup

Configure multiple chains in your Graph Node configuration file:
[store]
[store.primary]
connection = "postgresql://graph:password@localhost:5432/graph-node"
pool_size = 10

[chains]
ingestor = "block_ingestor_node"

[chains.mainnet]
shard = "primary"
protocol = "ethereum"
provider = [
  { label = "mainnet-1", url = "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY", features = ["archive", "traces"] },
  { label = "mainnet-2", url = "https://mainnet.infura.io/v3/YOUR_KEY", features = ["archive"] }
]

[chains.polygon]
shard = "primary"
protocol = "ethereum"
provider = [
  { label = "polygon-1", url = "https://polygon-mainnet.g.alchemy.com/v2/YOUR_KEY", features = ["archive", "traces"] }
]

[chains.arbitrum]
shard = "primary"
protocol = "ethereum"
provider = [
  { label = "arbitrum-1", url = "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY", features = ["archive", "traces"] }
]

[chains.optimism]
shard = "primary"
protocol = "ethereum"
provider = [
  { label = "optimism-1", url = "https://opt-mainnet.g.alchemy.com/v2/YOUR_KEY", features = ["archive"] }
]

[deployment]
[[deployment.rule]]
# Deploy to primary shard and index nodes
indexers = ["index_node_1", "index_node_2"]

Chain Configuration Options

shard
String
required
Specifies which database shard stores this chain’s data. Must reference a shard defined in [store].
protocol
String
default:"ethereum"
The protocol type being indexed. Options: ethereum, near, cosmos, arweave, starknet.
polling_interval
Number
default:"500"
The polling interval for the block ingestor in milliseconds.
amp
String
The network name used by AMP for this chain. Set this when AMP uses a different name than graph-node.Example: amp = "ethereum-mainnet" on a chain named mainnet.
provider
Provider[]
required
A list of providers for that chain. See Provider Configuration.

Provider Configuration

label
String
required
The name of the provider, which will appear in logs.
url
String
required
The URL for the provider (RPC, WebSocket, or IPC endpoint).
features
String[]
default:"[]"
Array of features that the provider supports:
  • archive: Provider is an archive node with full historical state
  • traces: Provider supports debug_traceBlockByNumber for call tracing
  • no_eip1898: Provider doesn’t support EIP-1898
  • no_eip2718: Provider doesn’t return the type field in transaction receipts
  • compression/gzip, compression/brotli, compression/deflate: Provider supports request compression
headers
Object
HTTP headers to be added on every request.Example: headers = { Authorization = "Bearer token" }
limit
Number
The maximum number of subgraphs that can use this provider. Defaults to unlimited.
transport
String
default:"rpc"
Transport type. Options: rpc, ws, ipc.

Subgraph Manifest for Multiple Networks

Single Network Deployment

specVersion: 0.0.8
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: Token
    network: mainnet
    source:
      address: "0x1234567890123456789012345678901234567890"
      abi: ERC20
      startBlock: 12345678
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Transfer
      abis:
        - name: ERC20
          file: ./abis/ERC20.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mapping.ts

Multi-Network Deployment

You can create separate manifests for each network:
subgraph-mainnet.yaml
specVersion: 0.0.8
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: Token
    network: mainnet
    source:
      address: "0x1234567890123456789012345678901234567890"
      abi: ERC20
      startBlock: 12345678
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Transfer
      abis:
        - name: ERC20
          file: ./abis/ERC20.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mapping.ts

Using Mustache Templates

Use templating to manage network-specific values:
specVersion: 0.0.8
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: Token
    network: {{network}}
    source:
      address: "{{address}}"
      abi: ERC20
      startBlock: {{startBlock}}
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Transfer
      abis:
        - name: ERC20
          file: ./abis/ERC20.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mapping.ts

Database Sharding for Multiple Networks

For large-scale deployments, distribute networks across multiple database shards:
[store]
# Primary shard for Ethereum mainnet
[store.primary]
connection = "postgresql://graph:password@mainnet-db:5432/graph-node"
pool_size = 20

# Shard for Layer 2 networks
[store.layer2]
connection = "postgresql://graph:password@layer2-db:5432/graph-node"
pool_size = 15

# Shard for other networks
[store.other]
connection = "postgresql://graph:password@other-db:5432/graph-node"
pool_size = 10

[chains]
ingestor = "block_ingestor_node"

# Ethereum mainnet on primary shard
[chains.mainnet]
shard = "primary"
protocol = "ethereum"
provider = [
  { label = "mainnet-1", url = "https://eth-mainnet.g.alchemy.com/v2/KEY", features = ["archive", "traces"] }
]

# Layer 2 networks on layer2 shard
[chains.polygon]
shard = "layer2"
protocol = "ethereum"
provider = [
  { label = "polygon-1", url = "https://polygon-mainnet.g.alchemy.com/v2/KEY", features = ["archive"] }
]

[chains.arbitrum]
shard = "layer2"
protocol = "ethereum"
provider = [
  { label = "arbitrum-1", url = "https://arb-mainnet.g.alchemy.com/v2/KEY", features = ["archive"] }
]

[chains.optimism]
shard = "layer2"
protocol = "ethereum"
provider = [
  { label = "optimism-1", url = "https://opt-mainnet.g.alchemy.com/v2/KEY", features = ["archive"] }
]

# Other networks on other shard
[chains.bsc]
shard = "other"
protocol = "ethereum"
provider = [
  { label = "bsc-1", url = "https://bsc-dataseed.binance.org", features = [] }
]

[chains.avalanche]
shard = "other"
protocol = "ethereum"
provider = [
  { label = "avalanche-1", url = "https://api.avax.network/ext/bc/C/rpc", features = [] }
]

[deployment]
# Network-specific deployment rules
[[deployment.rule]]
match = { network = "mainnet" }
shard = "primary"
indexers = ["index_node_mainnet_1", "index_node_mainnet_2"]

[[deployment.rule]]
match = { network = ["polygon", "arbitrum", "optimism"] }
shard = "layer2"
indexers = ["index_node_layer2_1", "index_node_layer2_2"]

[[deployment.rule]]
match = { network = ["bsc", "avalanche"] }
shard = "other"
indexers = ["index_node_other_1"]

# Default rule
[[deployment.rule]]
indexers = ["index_node_default"]

Running Graph Node with Configuration

# Using Docker
docker run -it \
  -v $(pwd)/config.toml:/config.toml \
  -p 8000:8000 \
  -p 8020:8020 \
  graphprotocol/graph-node:latest \
  --config /config.toml

# From source
cargo run -p graph-node --release -- --config config.toml
When using a configuration file, you cannot use the command-line options --postgres-url, --postgres-secondary-hosts, or --postgres-host-weights.

Provider Management

Multiple Providers per Network

Configure redundant providers for reliability:
[chains.mainnet]
shard = "primary"
provider = [
  # Primary provider with all features
  { 
    label = "alchemy-mainnet",
    url = "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
    features = ["archive", "traces"],
    headers = { "X-Custom-Header" = "value" }
  },
  # Backup provider
  { 
    label = "infura-mainnet",
    url = "https://mainnet.infura.io/v3/YOUR_KEY",
    features = ["archive"]
  },
  # Local node
  { 
    label = "local-mainnet",
    url = "http://localhost:8545",
    features = ["archive", "traces"],
    limit = 5  # Limit to 5 subgraphs
  }
]

Provider Limits

Control how many subgraphs can use each provider:
[chains.mainnet]
shard = "primary"
provider = [
  # Unlimited (default)
  { label = "primary", url = "https://primary.com/rpc", features = ["archive", "traces"] },
  
  # Limited by node name
  { 
    label = "secondary",
    url = "https://secondary.com/rpc",
    features = ["archive"],
    match = [
      { name = "index_node_1", limit = 10 },
      { name = "index_node_2", limit = 5 }
    ]
  }
]

Network-Specific Mappings

Handle network-specific logic in your mappings:
import { dataSource } from '@graphprotocol/graph-ts'

export function handleTransfer(event: Transfer): void {
  let network = dataSource.network()
  
  // Network-specific handling
  if (network == 'mainnet') {
    // Ethereum mainnet logic
    handleMainnetTransfer(event)
  } else if (network == 'polygon') {
    // Polygon-specific logic
    handlePolygonTransfer(event)
  } else if (network == 'arbitrum-one') {
    // Arbitrum-specific logic
    handleArbitrumTransfer(event)
  }
}

function handleMainnetTransfer(event: Transfer): void {
  // Higher gas costs, different transaction patterns
  let entity = new Transfer(event.transaction.hash.toHex())
  entity.gasPrice = event.transaction.gasPrice
  entity.save()
}

function handlePolygonTransfer(event: Transfer): void {
  // Lower gas costs, higher throughput
  let entity = new Transfer(event.transaction.hash.toHex())
  entity.network = 'polygon'
  entity.save()
}

Monitoring Multiple Networks

Query indexing status across all networks:
query {
  indexingStatuses {
    subgraph
    synced
    health
    chains {
      network
      chainHeadBlock {
        number
      }
      latestBlock {
        number
      }
      earliestBlock {
        number
      }
    }
  }
}

Best Practices

  1. Separate shards by network load - Put high-volume networks on dedicated shards
  2. Use multiple providers - Configure redundant RPC endpoints for each network
  3. Set appropriate pool sizes - Allocate connection pools based on expected load
  4. Use deployment rules - Route subgraphs to appropriate nodes and shards
  5. Name chains consistently - Use standard network names that match graph-cli

Environment Variables for Multi-Network

Key environment variables when running multiple networks:
ETHEREUM_REORG_THRESHOLD
Number
default:"250"
Maximum expected reorg size. May need to be set differently per network.
ETHEREUM_POLLING_INTERVAL
Number
default:"500"
Polling interval in milliseconds. Consider network block times.
GRAPH_ETHEREUM_MAX_BLOCK_RANGE_SIZE
Number
default:"1000"
Maximum blocks to scan per request. Adjust based on network throughput.
GRAPH_ETHEREUM_TARGET_TRIGGERS_PER_BLOCK_RANGE
Number
default:"100"
Target triggers per batch. Optimize based on event density.

Troubleshooting

Problem: Error says network is not configuredSolutions:
  • Verify network name in subgraph.yaml matches config.toml
  • Check that the chain section exists: [chains.network-name]
  • Ensure Graph Node was restarted after config changes
  • Validate config file: graph-node --config config.toml config check

Next Steps

Build docs developers (and LLMs) love