An EVM indexing engine that lets you query chain data with SQL.
It is a great alternative to theGraph https://thegraph.com/ if you:
- have a server + relational database setup
- are NOT indexing thousands of contracts
- don't want to deal with an additional external system
- have written your DApp in RUST (Other Languages soon to come!)
Example Usage:
Indexing states of NFTs (NftState
) for Bored Ape Yatch Club and Doodle's contracts in a Postgres DB.
- Setup state by specifying its RDBMS's(Postgres) table name and migration:
use serde::{Deserialize, Serialize};
use chaindexing::{ContractState, ContractStateMigrations};
#[derive(Clone, Debug, Serialize, Deserialize)]
struct NftState {
token_id: i32,
contract_address: String,
owner_address: String,
}
impl ContractState for NftState {
fn table_name() -> &'static str {
"nft_states"
}
}
struct NftStateMigrations;
impl ContractStateMigrations for NftStateMigrations {
fn migrations(&self) -> Vec<&'static str> {
vec![
"CREATE TABLE IF NOT EXISTS nft_states (
token_id INTEGER NOT NULL,
contract_address TEXT NOT NULL,
owner_address TEXT NOT NULL
)",
]
}
}
- Setup Event Handlers:
For our example, we simply need a handler for Transfer
events.
...
use chaindexing::{Contract, EventContext, EventHandler};
struct TransferEventHandler;
#[async_trait::async_trait]
impl EventHandler for TransferEventHandler {
async fn handle_event<'a>(&self, event_context: EventContext<'a>) {
let event = &event_context.event;
// Get event parameters
let event_params = event.get_params();
// Extract each parameter as exactly specified in the ABI:
// "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"
let from = event_params.get("from").unwrap().clone().into_address().unwrap();
let to = event_params.get("to").unwrap().clone().into_address().unwrap();
let token_id = event_params.get("tokenId").unwrap().clone().into_uint().unwrap();
if let Some(nft_state) = NftState::read_one(
[
("token_id".to_owned(), token_id.to_string()),
("owner_address".to_owned(), from.to_string()),
]
.into(),
&event_context,
)
.await
{
let updates = [("owner_address".to_string(), to.to_string())];
nft_state.update(updates.into(), &event_context).await;
} else {
NftState {
token_id: token_id.as_u32() as i32,
contract_address: event.contract_address.clone(),
owner_address: to.to_string(),
}
.create(&event_context)
.await;
}
}
}
- Start the indexing background process:
...
use chaindexing::{Chain, Chaindexing, Chains, Config, Contract, PostgresRepo, Repo};
#[tokio::main]
async fn main() {
// Setup BAYC's contract
let bayc_contract = Contract::new("BoredApeYachtClub")
// add transfer event and it's corresponding handler
.add_event("event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)", TransferEventHandler)
// add migration for the state's DB schema
.add_state_migrations(NftStateMigrations)
// add contract address for BAYC
.add_address(
"0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
&Chain::Mainnet,
17773490,
);
// Setup Doodles' contract
let doodles_contract = Contract::new("Doodles")
.add_event("event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)", TransferEventHandler)
.add_address(
"0x8a90CAb2b38dba80c64b7734e58Ee1dB38B8992e",
&Chain::Mainnet,
17769635,
);
// Setup indexing config
let config = Config::new(
// Database
PostgresRepo::new("postgres://postgres:postgres@localhost/example-db"),
// All possible chains in your Dapp
HashMap::from([(
Chain::Mainnet,
"https://eth-mainnet.g.alchemy.com/v2/some-secret"
)]),
)
// add BAYC's and Doodles' contracts
.add_contract(bayc_contract)
.add_contract(doodles_contract);
// Start Indexing Process
Chaindexing::index_states(&config).await.unwrap();
}
- Query your DB using any approach/ORM:
select * from nft_states