diff --git a/README.md b/README.md new file mode 100644 index 0000000..af36cb8 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# Chaindexing + +[github](https://github.com/jurshsmith/chaindexing-rs) +[crates.io](https://crates.io/crates/chaindexing) +[diesel-streamer build](https://github.com/jurshsmith/chaindexing-rs/actions?query=branch%3Amain) + +A Chain Reorg-Proof indexing engine that helps aggregate states for EVM contracts in RDBMS' as accurately as possible. + +Example: + +Indexing states of NFTs (`NftState`) for Bored Ape Yatch Club and Doodle's contracts in a Postgres DB. + +1. Setup state by specifying its RDBMS's(Postgres) table name and migration: + +```rust +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 + )", + ] + } +} +``` + +2. Setup Event Handlers: + +For our example, we simply need a handler for `Transfer` events. + +```rust + +... + +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; + } + } +} +``` + +3. Start the indexing background process: + +```rust +... +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( + // Choose your database and provider its corresponding url + PostgresRepo::new("postgres://postgres:postgres@localhost/example-db"), + 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(); +} +```