Multi-Source Subgraphs
A single WASM binary can index multiple contracts. This is useful when you want to combine related contracts — for example, an ERC20 token and a liquidity pool — in one deployment.
Setup
Add multiple [[contracts]] sections to graphite.toml:
output_dir = "src/generated"
schema = "schema.graphql"
network = "mainnet"
[[contracts]]
name = "ERC20"
abi = "abis/ERC20.json"
address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
start_block = 6082465
[[contracts.event_handlers]]
event = "Transfer(address,address,uint256)"
handler = "handle_transfer"
[[contracts]]
name = "ERC721"
abi = "abis/ERC721.json"
address = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"
start_block = 12287507
[[contracts.event_handlers]]
event = "Transfer(address,address,uint256)"
handler = "handle_nft_transfer"
Generated Types
graphite codegen generates a separate module for each contract:
src/generated/
├── mod.rs
├── erc20.rs # ERC20TransferEvent
├── erc721.rs # ERC721TransferEvent
└── schema.rs # all entity builders
Note that two contracts can have events with the same name (like Transfer). The generated types are prefixed with the contract name: ERC20TransferEvent, ERC721TransferEvent. They are fully distinct types.
Handler Implementation
#![allow(unused)] fn main() { mod generated; use generated::{ ERC20TransferEvent, ERC721TransferEvent, TokenTransfer, NFTTransfer, }; #[handler] pub fn handle_transfer(event: &ERC20TransferEvent, ctx: &graphite::EventContext) { let id = format!("{}-{}", hex(&ctx.tx_hash), hex(&ctx.log_index)); TokenTransfer::new(&id) .set_from(event.from.to_vec()) .set_to(event.to.to_vec()) .set_value(event.value.clone()) .save(); } #[handler] pub fn handle_nft_transfer(event: &ERC721TransferEvent, ctx: &graphite::EventContext) { let id = format!("{}-{}", hex(&ctx.tx_hash), hex(&ctx.log_index)); NFTTransfer::new(&id) .set_from(event.from.to_vec()) .set_to(event.to.to_vec()) .set_token_id(event.token_id.clone()) .save(); } }
Entity Namespacing
Entity types are global across all contracts in a subgraph — they are defined in schema.graphql, not per contract. Make sure entity type names are unique.
Testing
Tests work identically — mock::entity_count and mock::has_entity count across the whole store:
#![allow(unused)] fn main() { #[test] fn both_handlers_independent() { mock::reset(); handle_transfer_impl(&token_event, &graphite::EventContext::default()); handle_nft_transfer_impl(&nft_event, &graphite::EventContext::default()); assert_eq!(mock::entity_count("TokenTransfer"), 1); assert_eq!(mock::entity_count("NFTTransfer"), 1); } }
See the multi-source example in the repository for a complete working subgraph.