Dynamic Data Sources
Dynamic data sources (also called the factory pattern) let you start indexing a new contract address at runtime — typically when a factory contract deploys a new instance.
The classic example is Uniswap V2: when the factory emits PairCreated, you create a new data source for the pair contract so its Swap events get indexed.
Setup
Declare a template in graphite.toml:
[[contracts]]
name = "Factory"
abi = "abis/Factory.json"
address = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
start_block = 10000835
[[contracts.event_handlers]]
event = "PairCreated(address,address,address,uint256)"
handler = "handle_pair_created"
[[templates]]
name = "Pair"
abi = "abis/Pair.json"
[[templates.event_handlers]]
event = "Swap(address,uint256,uint256,uint256,uint256,address)"
handler = "handle_swap"
Factory Handler
In the factory handler, call data_source::create_contract to start indexing the new address:
#![allow(unused)] fn main() { use graphite::data_source; #[handler] pub fn handle_pair_created(event: &FactoryPairCreatedEvent, _ctx: &graphite::EventContext) { let pool_id = addr_hex(&event.pair); Pool::new(&pool_id) .set_token0(event.token0.to_vec()) .set_token1(event.token1.to_vec()) .save(); // Start indexing events from this pair address using the "Pair" template data_source::create_contract("Pair", event.pair); } }
Template Handler
Template handlers use data_source::address_current() to find out which instance they're running for:
#![allow(unused)] fn main() { #[handler] pub fn handle_swap(event: &PairSwapEvent, ctx: &graphite::EventContext) { let pair_addr = data_source::address_current(); let pool_id = addr_hex(&pair_addr); let swap_id = format!("{}-{}", hex(&event.tx_hash), hex(&event.log_index)); Swap::new(&swap_id) .set_pool(pool_id.clone()) .set_amount0_in(event.amount0_in.clone()) .set_amount1_out(event.amount1_out.clone()) .set_block_number(ctx.block_number.clone()) .save(); } }
Data Source API
#![allow(unused)] fn main() { use graphite::data_source; // Create a new contract data source instance data_source::create_contract("TemplateName", address_bytes); // Create with context data attached data_source::create_contract_with_context("TemplateName", address_bytes, context_map); // Introspect the current data source (inside a template handler) let addr: [u8; 20] = data_source::address_current(); let net: String = data_source::network_current(); let id: String = data_source::id_current(); let ctx: TypedMap = data_source::context_current(); }
Testing Dynamic Data Sources
#![allow(unused)] fn main() { #[test] fn pair_created_makes_data_source() { mock::reset(); handle_pair_created_impl(&event, &graphite::EventContext::default()); mock::assert_contract_data_source_created("Pair", pair_address); assert!(mock::has_entity("Pool", &addr_hex(&pair_address))); } #[test] fn swap_increments_count() { mock::reset(); // Create the pool first handle_pair_created_impl(&factory_event, &graphite::EventContext::default()); // Set the data source address for the template handler mock::set_current_address(pair_address); handle_swap_impl(&swap_event, &graphite::EventContext::default()); assert_eq!(mock::entity_count("Swap"), 1); } }
See the Uniswap V2 example for a complete factory + template subgraph.