Skip to main content

High-performance EVM blockchain indexer with reorg detection and pluggable storage

Project description

chainindex

Reorg-safe, embeddable blockchain indexing engine.

crates.io docs.rs npm PyPI License: MIT

chainindex is a lightweight library that fetches blocks, detects reorgs, decodes events, calls your handler functions, and saves checkpoints. Think of it as the missing primitive between raw blocks and your database — without the weight of The Graph or Ponder.

Features

Feature Description
Reorg detection 4-scenario reorg detection (short, deep, node switch, RPC inconsistency)
Checkpoint recovery Crash-safe — resumes from last saved checkpoint
Factory tracking Auto-track child contracts from factory events (Uniswap, Compound, etc.)
Entity system Structured storage with typed schemas, CRUD, queries, and reorg rollback
Dead letter queue Failed handlers retry with exponential backoff
Idempotency Deterministic IDs + side-effect guards for safe reorg replay
Call trace indexing Index internal transactions (CALL, DELEGATECALL, CREATE)
Event streaming Cursor-based streaming for downstream consumers
Data export Export to JSONL/CSV for analytics pipelines (DuckDB, BigQuery)
Block handlers Interval handlers (every N blocks), setup handlers (run once)
GraphQL query layer Auto-generated schema from entities, filter/sort/paginate
Parallel backfill Concurrent segment processing for fast historical sync
Multi-chain indexer Single engine coordinating N chains with cross-chain event bus
Solana indexer Slot tracking, program log parsing, Anchor events, account filters
Hot-reload config Update indexer configs at runtime without restart
Multi-chain finality Pre-configured for 12 chains (Ethereum, Polygon, Arbitrum, Solana, etc.)
4 storage backends Memory (dev), SQLite (embedded), PostgreSQL (production), RocksDB (high-throughput)
4 language bindings TypeScript, Python, Go, Java

Install

Rust

[dependencies]
chainindex-core    = "0.1"
chainindex-evm     = "0.1"
chainindex-storage = { version = "0.1", features = ["memory"] }

npm / Node.js

npm install @chainfoundry/chainindex

Python

pip install chainindex

Quick Start (Rust)

use chainindex_core::indexer::IndexerConfig;
use chainindex_core::handler::{DecodedEvent, EventHandler, HandlerRegistry};
use chainindex_core::types::IndexContext;
use chainindex_core::checkpoint::{CheckpointManager, MemoryCheckpointStore};

// 1. Configure
let config = IndexerConfig {
    id: "uniswap-v3".into(),
    chain: "ethereum".into(),
    from_block: 19_000_000,
    confirmation_depth: 12,
    batch_size: 500,
    checkpoint_interval: 100,
    ..Default::default()
};

// 2. Register event handlers
let mut registry = HandlerRegistry::new();
// ... register your EventHandler implementations

// 3. Start indexing (with chainindex-evm)
use chainindex_evm::IndexerBuilder;
let cfg = IndexerBuilder::new()
    .chain("ethereum")
    .from_block(19_000_000)
    .confirmation_depth(12)
    .batch_size(500)
    .build_config();

Quick Start (TypeScript)

import { IndexerConfig, InMemoryStorage, EventFilter } from '@chainfoundry/chainindex';

const config = new IndexerConfig({
  id: 'uniswap-v3',
  chain: 'ethereum',
  fromBlock: 19_000_000,
  confirmationDepth: 12,
  batchSize: 500,
});

const filter = EventFilter.forAddress('0x1F98431c8aD98523631AE4a59f267346ea31F984');

Architecture

chainindex/
├── crates/
│   ├── chainindex-core/        # Core engine (24 modules, 245 tests)
│   │   ├── backfill.rs         # Parallel backfill engine
│   │   ├── block_handler.rs    # Interval + setup handlers
│   │   ├── checkpoint.rs       # Checkpoint persistence + recovery
│   │   ├── cursor.rs           # Block cursor advancement
│   │   ├── dlq.rs              # Dead letter queue
│   │   ├── entity.rs           # Entity/table system
│   │   ├── error.rs            # Error types
│   │   ├── export.rs           # JSONL/CSV export
│   │   ├── factory.rs          # Factory contract tracking
│   │   ├── finality.rs         # 12-chain finality models
│   │   ├── graphql.rs          # GraphQL schema + query executor
│   │   ├── handler.rs          # Event/block/reorg handler traits
│   │   ├── hotreload.rs        # Hot-reload configuration
│   │   ├── idempotency.rs      # Reorg-safe handler replay
│   │   ├── indexer.rs          # Config + state types
│   │   ├── metrics.rs          # Block lag, RPC stats, handler latency
│   │   ├── multichain.rs       # Multi-chain coordinator + event bus
│   │   ├── reorg.rs            # 4-scenario reorg detection
│   │   ├── streaming.rs        # Cursor-based event streaming
│   │   ├── trace.rs            # Call trace indexing
│   │   ├── tracker.rs          # Sliding window block tracker
│   │   └── types.rs            # BlockSummary, EventFilter, IndexContext
│   ├── chainindex-evm/         # EVM-specific indexer (builder, fetcher, loop)
│   ├── chainindex-solana/      # Solana indexer (slots, program logs, Anchor)
│   └── chainindex-storage/     # Memory, SQLite, Postgres, RocksDB backends
├── cli/                        # CLI binary
├── examples/                   # 16 runnable examples
└── bindings/
    ├── node/                   # TypeScript (napi-rs)
    ├── python/                 # Python (PyO3 + maturin)
    ├── go/                     # Go (C FFI)
    └── java/                   # Java (JNI)

Module Reference

Factory Contract Tracking

Track child contracts deployed by factory patterns (Uniswap V3, Compound, etc.):

use chainindex_core::factory::{FactoryConfig, FactoryRegistry};

let registry = FactoryRegistry::new();
registry.register(FactoryConfig {
    factory_address: "0x1f98431c8ad98523631ae4a59f267346ea31f984".into(),
    creation_event_topic0: "0x783cca1c...".into(),
    child_address_field: "pool".into(),
    name: Some("Uniswap V3 Factory".into()),
});

// Feed events through — child addresses auto-tracked
if let Some(child) = registry.process_event(&event) {
    println!("New pool: {}", child.address);
}

// Get all addresses for EventFilter
let all_addrs = registry.get_all_addresses();

Entity System

Structured storage with typed schemas:

use chainindex_core::entity::*;

let schema = EntitySchemaBuilder::new("swap")
    .primary_key("id")
    .field("pool", FieldType::String, true)
    .field("amount0", FieldType::Int64, false)
    .field("amount1", FieldType::Int64, false)
    .build();

let store = MemoryEntityStore::new();
store.register_schema(&schema).await?;

store.upsert(EntityRow {
    id: format!("{}-{}", event.tx_hash, event.log_index),
    entity_type: "swap".into(),
    block_number: event.block_number,
    tx_hash: event.tx_hash.clone(),
    log_index: event.log_index,
    data: /* fields */,
}).await?;

// Reorg rollback — delete entities after fork block
store.delete_after_block("swap", fork_block).await?;

Dead Letter Queue

Failed handlers retry with exponential backoff:

use chainindex_core::dlq::{DeadLetterQueue, DlqConfig};

let dlq = DeadLetterQueue::new(DlqConfig {
    max_retries: 5,
    initial_backoff: Duration::from_secs(1),
    max_backoff: Duration::from_secs(300),
    backoff_multiplier: 2.0,
});

// On handler failure
dlq.push(event, "my_handler", "connection timeout");

// Retry ready entries
let ready = dlq.pop_ready(now);
for entry in ready {
    match handler.handle(&entry.event, &ctx).await {
        Ok(()) => dlq.mark_success(&entry.id),
        Err(e) => dlq.mark_failed(&entry.id, &e.to_string()),
    }
}

GraphQL Query Layer

Auto-generated GraphQL schema from entity definitions:

use chainindex_core::graphql::{GraphqlExecutor, GraphqlSchema};
use chainindex_core::entity::*;

// Generate schema from entities
let mut schema = GraphqlSchema::new();
schema.add_entity(swap_schema);

// Execute queries
let executor = GraphqlExecutor::new(store);
let result = executor.execute(r#"
    { swaps(where: { pool: "0xABC" }, first: 10, orderBy: "amount0", orderDirection: "desc") {
        id pool amount0 sender blockNumber
    }}
"#).await;

// Single entity by ID
let result = executor.execute(r#"{ swap(id: "0x123-0") { id pool amount0 } }"#).await;

// Introspection
let sdl = executor.introspect(); // returns GraphQL SDL string

Parallel Backfill

Concurrent block range processing for fast historical sync:

use chainindex_core::backfill::*;

let config = BackfillConfig {
    from_block: 19_000_000,
    to_block: 19_100_000,
    concurrency: 4,        // 4 parallel workers
    segment_size: 25_000,  // blocks per segment
    batch_size: 500,       // blocks per RPC call
    retry_attempts: 3,
    ..Default::default()
};

let engine = BackfillEngine::new(config, provider, filter, "ethereum");
let result = engine.run().await?;
println!("{} events in {:?}", result.total_events, result.total_duration);

Multi-Chain Indexer

Coordinate multiple chains from a single engine:

use chainindex_core::multichain::*;

let coordinator = MultiChainCoordinator::new(MultiChainConfig {
    chains: vec![eth_config, polygon_config, arbitrum_config],
    max_concurrent_chains: 3,
    restart_on_error: true,
    ..Default::default()
});

// Health monitoring
let health = coordinator.health().await;

// Cross-chain event bus
let bus = CrossChainEventBus::new(10_000);
let mut rx = bus.subscribe();
bus.push("ethereum", event);

// Sync status
let mut sync = ChainSyncStatus::new();
sync.update("ethereum", 19_000_500);
sync.all_caught_up(1000); // within 1000 blocks of tips

Solana Indexer

Slot tracking, program log parsing, and Anchor event decoding:

use chainindex_solana::*;

let builder = SolanaIndexerBuilder::new()
    .from_slot(250_000_000)
    .program("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8") // Raydium
    .exclude_votes(true)
    .confirmation("finalized");

// Parse transaction logs
let logs = ProgramLogParser::parse_transaction_logs(&raw_logs, "SIG123");

// Slot tracking with skip detection
let mut tracker = SlotTracker::new(100);
tracker.push_slot(slot)?;
let skipped = tracker.skipped_slots_in_range(100, 200);

Hot-Reload Configuration

Update configs at runtime without restart:

use chainindex_core::hotreload::*;

let manager = HotReloadManager::new();
manager.register_config("eth-indexer", config).await;

// Subscribe to changes
let mut rx = manager.subscribe("eth-indexer").await.unwrap();

// Update config — validates, diffs, notifies subscribers
let result = manager.update_config("eth-indexer", new_config).await?;
println!("{} fields changed, version {}", result.diffs.len(), result.version);

// Dynamic filter updates
let reloader = FilterReloader::new(filter);
reloader.add_address("0xNewContract").await;
reloader.remove_address("0xOldContract").await;

RocksDB Storage Backend

High-throughput embedded storage:

use chainindex_storage::RocksDbStorage;

let storage = RocksDbStorage::in_memory(); // or RocksDbStorage::open("./data")?

// Events — natural block-order via key encoding
storage.insert_events_batch(&events)?;
let transfers = storage.events_by_schema("Transfer")?;
let range = storage.events_in_block_range(100, 200)?;

// Reorg rollback
storage.rollback_after(block_number)?;

Call Trace Indexing

Index internal transactions from debug_traceBlock or trace_block:

use chainindex_core::trace::*;

// Parse Geth traces
let traces = parse_geth_traces(&geth_json, 12345678)?;

// Parse Parity/OpenEthereum traces
let traces = parse_parity_traces(&parity_json, 12345678)?;

// Filter
let filter = TraceFilter::new()
    .with_address("0xPool")
    .with_selector("0xa9059cbb")
    .exclude_reverted(true);

let matching: Vec<_> = traces.iter().filter(|t| filter.matches(t)).collect();

Event Streaming

Cursor-based streaming for downstream consumers:

use chainindex_core::streaming::{EventStream, StreamCursor};

let mut stream = EventStream::new(10_000);

// Producer pushes events
stream.push(decoded_event);

// Consumer reads batches
let cursor = StreamCursor::initial();
let batch = stream.next_batch(&cursor, 100)?;
// Save batch.cursor for resume after crash

Data Export

use chainindex_core::export::{export_events, ExportConfig, ExportFormat};

let config = ExportConfig {
    format: ExportFormat::Jsonl,
    from_block: Some(19_000_000),
    to_block: Some(19_100_000),
    schema_filter: vec!["Transfer".into()],
    ..Default::default()
};

let mut file = File::create("transfers.jsonl")?;
let stats = export_events(&events, &config, &mut file)?;
println!("Exported {} events ({} bytes)", stats.events_exported, stats.bytes_written);

Finality Models

Pre-configured for 12 chains:

Chain Safe Finalized Block Time Reorg Window
Ethereum 32 64 12s 128
Polygon 128 256 2s 512
Arbitrum 0 1 250ms 64
Optimism 0 1 2s 64
Base 0 1 2s 64
BSC 15 15 3s 64
Avalanche 1 1 2s 32
Solana 1 32 400ms 256
Fantom 1 1 1s 32
Scroll 0 1 3s 64
zkSync 0 1 1s 64
Linea 0 1 12s 64

Examples

16 runnable examples covering every feature:

cargo run -p chainindex-cli --example 01_basic_indexer
cargo run -p chainindex-cli --example 02_reorg_detection
cargo run -p chainindex-cli --example 03_factory_tracking
cargo run -p chainindex-cli --example 04_entity_system
cargo run -p chainindex-cli --example 05_dead_letter_queue
cargo run -p chainindex-cli --example 06_call_traces
cargo run -p chainindex-cli --example 07_streaming
cargo run -p chainindex-cli --example 08_data_export
cargo run -p chainindex-cli --example 09_graphql
cargo run -p chainindex-cli --example 10_parallel_backfill
cargo run -p chainindex-cli --example 11_multichain
cargo run -p chainindex-cli --example 12_solana_indexer
cargo run -p chainindex-cli --example 13_rocksdb_storage
cargo run -p chainindex-cli --example 14_hot_reload
cargo run -p chainindex-cli --example 15_idempotency
cargo run -p chainindex-cli --example 16_block_handlers

Test Coverage

324 tests, 0 failures

chainindex-core:       245 tests (graphql, backfill, multichain, hotreload, factory, entity,
                                  DLQ, idempotency, trace, streaming, export, block_handler,
                                  checkpoint, tracker, reorg, finality, metrics, types, handler)
chainindex-evm:          4 tests (builder, fetcher)
chainindex-solana:      33 tests (slot tracking, program logs, Anchor, filters, builder)
chainindex-storage:     29 tests (memory, RocksDB KV store, checkpoint, events)
doc-tests:              13 tests

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl (265.9 kB view details)

Uploaded CPython 3.11Windows x86-64

chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_11_0_arm64.whl (318.8 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl (327.9 kB view details)

Uploaded CPython 3.11macOS 10.12+ x86-64

chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (352.7 kB view details)

Uploaded CPython 3.8manylinux: glibc 2.17+ x86-64

chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (344.6 kB view details)

Uploaded CPython 3.8manylinux: glibc 2.17+ ARM64

File details

Details for the file chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl.

File metadata

File hashes

Hashes for chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 921b7e2723ee39bcc0062c0f8cb3eb42e66fe39ed21edee06b88940e9da9ad56
MD5 8ce435d9834b1ae9b6515072b6cb0701
BLAKE2b-256 1ca8e0040e77364d30c66d5b9cb26050aebdc63e6a4b799effb81213e7043001

See more details on using hashes here.

Provenance

The following attestation bundles were made for chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl:

Publisher: publish-chainindex.yml on DarshanKumar89/chainkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1c1f1bc8cd94099e66e169aabd0b33f133a5a112eb7c4295d5c0a9dba7e65f1e
MD5 13986f667d6ad0114587079304d10ee3
BLAKE2b-256 584bd8ec40b00295ec537f81242cb35837b93269af3fa0b6ec34cff063c27b27

See more details on using hashes here.

Provenance

The following attestation bundles were made for chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: publish-chainindex.yml on DarshanKumar89/chainkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 79660e3dc6ab9647633838d31b068c2f201824e83bd5d69d5ae6ca40a17a7d19
MD5 cd93f0caca47b346eefbe2c2f2651614
BLAKE2b-256 e08beefe2e24d948b4a89e9d5ce1ede3a56ff95ca22719c5f94f93a076ca3c65

See more details on using hashes here.

Provenance

The following attestation bundles were made for chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl:

Publisher: publish-chainindex.yml on DarshanKumar89/chainkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 85dfbb9b4c4dcd4cb2bf110afdd3281d6d326574a953a11d705c67a6f22b4211
MD5 3b3818cb47ad837359c788624c304d79
BLAKE2b-256 deb259ed772f1ada0624f0ea8419c402637aaf6317f4fa0e65131267bb6919ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish-chainindex.yml on DarshanKumar89/chainkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 7f32ebeaf9e9637452933751b95c9fcb2ab8c00f31a66f88161710fe473e0936
MD5 27a56cd03c4d4693d833a523ecab91b3
BLAKE2b-256 c80398e6873babfb4dd4e03bcaef3174489760d126eaf4c78ad800b3ee1c74b3

See more details on using hashes here.

Provenance

The following attestation bundles were made for chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: publish-chainindex.yml on DarshanKumar89/chainkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page