chainindex
Reorg-safe, embeddable blockchain indexing engine.
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
Metadata
Release files for chainfoundry-chainindex 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl | CPython 3.11 | CPython 3.11 | Windows x86-64 | Details |
| chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_11_0_arm64.whl | CPython 3.11 | CPython 3.11 | macOS 11.0+ ARM64 | Details |
| chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl | CPython 3.11 | CPython 3.11 | macOS 10.12+ x86-64 | Details |
| chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.8 | CPython 3.8 | Linux glibc 2.17+ x86-64 | Details |
| chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.8 | CPython 3.8 | Linux glibc 2.17+ ARM64 | Details |
Total release size: 1.6 MB
Release files / chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl
| Download URL | chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 265.9 kB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
921b7e2723ee39bcc0062c0f8cb3eb42e66fe39ed21edee06b88940e9da9ad56
|
|
BLAKE2b-256 checksum How to use checksums |
1ca8e0040e77364d30c66d5b9cb26050aebdc63e6a4b799effb81213e7043001
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 8, 2026.
Transparency logRelease files / chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 318.8 kB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
1c1f1bc8cd94099e66e169aabd0b33f133a5a112eb7c4295d5c0a9dba7e65f1e
|
|
BLAKE2b-256 checksum How to use checksums |
584bd8ec40b00295ec537f81242cb35837b93269af3fa0b6ec34cff063c27b27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 8, 2026.
Transparency logRelease files / chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl
| Download URL | chainfoundry_chainindex-0.2.0-cp311-cp311-macosx_10_12_x86_64.whl |
|---|---|
| Size | 327.9 kB |
| Tags | CPython 3.11 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
79660e3dc6ab9647633838d31b068c2f201824e83bd5d69d5ae6ca40a17a7d19
|
|
BLAKE2b-256 checksum How to use checksums |
e08beefe2e24d948b4a89e9d5ce1ede3a56ff95ca22719c5f94f93a076ca3c65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 8, 2026.
Transparency logRelease files / chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 352.7 kB |
| Tags | CPython 3.8 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
85dfbb9b4c4dcd4cb2bf110afdd3281d6d326574a953a11d705c67a6f22b4211
|
|
BLAKE2b-256 checksum How to use checksums |
deb259ed772f1ada0624f0ea8419c402637aaf6317f4fa0e65131267bb6919ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 8, 2026.
Transparency logRelease files / chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | chainfoundry_chainindex-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 344.6 kB |
| Tags | CPython 3.8 Linux glibc 2.17+ ARM64 |
|
SHA-256 checksum How to use checksums |
7f32ebeaf9e9637452933751b95c9fcb2ab8c00f31a66f88161710fe473e0936
|
|
BLAKE2b-256 checksum How to use checksums |
c80398e6873babfb4dd4e03bcaef3174489760d126eaf4c78ad800b3ee1c74b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 8, 2026.
Transparency logSecurity for chainfoundry-chainindex 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 5 files in this release have been uploaded with provenance metadata.
Attestation for chainfoundry_chainindex-0.2.0-cp311-cp311-win_amd64.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.