solana_indexer_sdk/lib.rs
1//! # Solana Indexer SDK
2//!
3//! A batteries-included, high-performance framework for building custom Solana indexers in Rust.
4//!
5//! This SDK provides a complete toolkit to subscribe to on-chain data, decode it into
6//! structured events, and process it with custom handlers, all with built-in concurrency,
7//! error handling, and backfill support.
8//!
9//! ## Features
10//!
11//! - **Multiple Data Sources**: Ingest data via RPC polling, WebSocket subscriptions, Helius Enhanced RPC, or Laserstream (Yellowstone gRPC).
12//! - **Automatic Indexing Modes**: The indexer automatically detects which on-chain data to process (instructions, logs, or account states) based on the decoders you register.
13//! - **Dynamic Backfill**: Automatically detects when the indexer is behind the chain tip and backfills historical data concurrently with live indexing.
14//! - **Simple Handler Pattern**: Implement the `EventHandler` trait to add your custom business logic (e.g., writing to a database).
15//! - **Concurrent & Performant**: Built on `tokio` to process transactions in parallel, with configurable worker limits.
16//!
17//! ## Getting Started
18//!
19//! Here is a complete example of a simple indexer that tracks native SOL transfers.
20//!
21//! ```no_run
22//! use solana_indexer_sdk::{
23//! SolanaIndexer, SolanaIndexerConfigBuilder, InstructionDecoder, EventHandler,
24//! EventDiscriminator, TxMetadata, SolanaIndexerError,
25//! calculate_discriminator,
26//! };
27//! use solana_sdk::pubkey::Pubkey;
28//! use solana_transaction_status::{UiInstruction, UiParsedInstruction};
29//! use async_trait::async_trait;
30//! use sqlx::PgPool;
31//! use borsh::{BorshSerialize, BorshDeserialize};
32//!
33//! // 1. Define the event you want to track.
34//! #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
35//! pub struct SystemTransferEvent {
36//! pub from: Pubkey,
37//! pub to: Pubkey,
38//! pub amount: u64,
39//! }
40//!
41//! // The discriminator helps route the event to the correct handler.
42//! impl EventDiscriminator for SystemTransferEvent {
43//! fn discriminator() -> [u8; 8] {
44//! calculate_discriminator("SystemTransferEvent")
45//! }
46//! }
47//!
48//! // 2. Create a decoder to extract your event from a transaction.
49//! pub struct SystemTransferDecoder;
50//! impl InstructionDecoder<SystemTransferEvent> for SystemTransferDecoder {
51//! fn decode(&self, instruction: &UiInstruction) -> Option<SystemTransferEvent> {
52//! if let UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed)) = instruction {
53//! if parsed.program == "system" && parsed.parsed.get("type")?.as_str()? == "transfer" {
54//! let info = parsed.parsed.get("info")?.as_object()?;
55//! return Some(SystemTransferEvent {
56//! from: info.get("source")?.as_str()?.parse().ok()?,
57//! to: info.get("destination")?.as_str()?.parse().ok()?,
58//! amount: info.get("lamports")?.as_u64()?,
59//! });
60//! }
61//! }
62//! None
63//! }
64//! }
65//!
66//! // 3. Create a handler to process your event.
67//! pub struct TransferHandler;
68//! #[async_trait]
69//! impl EventHandler<SystemTransferEvent> for TransferHandler {
70//! async fn handle(
71//! &self,
72//! event: SystemTransferEvent,
73//! context: &TxMetadata,
74//! db: &PgPool, // This example uses a mock DB; in reality, this is your database pool.
75//! ) -> Result<(), SolanaIndexerError> {
76//! println!(
77//! "✅ Found SOL transfer in tx {}: {} -> {} ({} lamports)",
78//! context.signature, event.from, event.to, event.amount
79//! );
80//! // In a real application, you would insert this into your database.
81//! // Example:
82//! // sqlx::query("INSERT INTO transfers (signature, from, to, amount) VALUES (...)")
83//! // .execute(db).await?;
84//! Ok(())
85//! }
86//! }
87//!
88//! #[tokio::main]
89//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
90//! let rpc_url = "https://api.devnet.solana.com";
91//! let database_url = "postgresql://user:pass@localhost:5432/mydb"; // Your database URL
92//!
93//! // 4. Configure the indexer.
94//! let config = SolanaIndexerConfigBuilder::new()
95//! .with_rpc(rpc_url)
96//! .with_database(database_url)
97//! .program_id("11111111111111111111111111111111") // System Program
98//! .build()?;
99//!
100//! // 5. Create the indexer and register your components.
101//! let mut indexer = SolanaIndexer::new(config).await?;
102//!
103//! // Registering the decoder automatically enables instruction (`inputs`) indexing mode.
104//! indexer.register_decoder("system", SystemTransferDecoder)?;
105//!
106//! // Register the handler for the event type.
107//! indexer.register_handler(TransferHandler)?;
108//!
109//! println!("▶️ Starting indexer... Press Ctrl+C to stop.");
110//!
111//! // 6. Start the indexer.
112//! // This will run indefinitely, processing live data and backfilling if needed.
113//! indexer.start().await?;
114//!
115//! Ok(())
116//! }
117//! ```
118//!
119//! ## Core Concepts
120//!
121//! - **[`SolanaIndexer`]**: The main orchestrator struct that runs the indexing pipeline.
122//!
123//! - **[`SolanaIndexerConfigBuilder`]**: A fluent builder for creating the indexer's configuration. This is where you set the RPC URL, database connection, program IDs to watch, and other parameters.
124//!
125//! - **Decoders**: These are traits you implement to parse on-chain data. The SDK supports three types, and automatically enables the required mode when you register a decoder:
126//! - **[`InstructionDecoder`]**: Parses data from a transaction's instructions.
127//! - **[`LogDecoder`]**: Parses data from program log messages (`sol_log_data`).
128//! - **[`AccountDecoder`]**: Parses the state of an account's data buffer.
129//!
130//! - **[`EventHandler`]**: The heart of your indexer's business logic. You implement this trait for each of your event types. The `handle` method receives the decoded, typed event and the transaction metadata, allowing you to save the data to a database, send a notification, or perform any other action.
131//!
132//! - **Dynamic Backfill**: If the indexer detects it is far behind the current state of the blockchain (based on the `desired_lag_slots` config), it will automatically start a background task to fetch and process historical transactions, all while continuing to process live data.
133
134#![cfg_attr(docsrs, feature(doc_cfg))]
135#![warn(clippy::all)]
136#![allow(clippy::module_name_repetitions)]
137
138// Public API exports
139pub use config::{RetryConfig, SolanaIndexerConfig, SolanaIndexerConfigBuilder};
140pub use core::decoding::Decoder;
141pub use core::decoding::{DecodedTransaction, InstructionInfo};
142pub use core::execution::fetcher::Fetcher;
143pub use core::execution::indexer::SolanaIndexer;
144pub use core::registry::account::AccountDecoderRegistry;
145pub use core::registry::logs::LogDecoderRegistry;
146pub use core::registry::DecoderRegistry;
147pub use storage::{Storage, StorageBackend};
148pub use streams::poller::Poller;
149pub use types::backfill_traits::{
150 BackfillContext, BackfillHandler, BackfillHandlerRegistry, BackfillProgress, BackfillRange,
151 BackfillStrategy, BackfillTrigger, FinalizedBlockTracker, ReorgEvent, ReorgHandler,
152};
153pub use types::events::{
154 calculate_discriminator, DepositEvent, EventDiscriminator, EventType, ParsedEvent,
155 TransferEvent, WithdrawEvent,
156};
157pub use types::metadata::{TokenBalanceInfo, TxMetadata};
158pub use types::traits::{
159 AccountDecoder, DynamicAccountDecoder, DynamicEventHandler, DynamicInstructionDecoder,
160 EventHandler, HandlerRegistry, InstructionDecoder, LogDecoder, SchemaInitializer,
161};
162pub use utils::error::{Result, SolanaIndexerError};
163pub use utils::macros::{
164 generate_event_struct, idl_type_to_rust, Idl, IdlAccount, IdlAccountItem, IdlEvent, IdlField,
165 IdlInstruction, IdlType, IdlTypeDefinition,
166};
167pub use utils::retry::RetryingRpcProvider;
168
169// IDL module is available for documentation purposes
170// Use solana_indexer_idl::generate_sdk_types in build.rs scripts
171
172// Telemetry exports (Phase 1: console)
173#[cfg(feature = "telemetry")]
174pub use telemetry::{init_telemetry, shutdown_telemetry, TelemetryConfig, TelemetryGuard};
175
176// Telemetry exports (Phase 2: OTLP)
177#[cfg(feature = "opentelemetry")]
178pub use telemetry::{init_telemetry_with_otel, OtelConfig, OtlpProtocol};
179
180// Module declarations
181pub mod config;
182pub mod core;
183pub mod idl;
184pub mod storage;
185pub mod streams;
186#[cfg(feature = "telemetry")]
187pub mod telemetry;
188pub mod types;
189pub mod utils;
190
191// Note: Generated types from IDL files should be included in your application code:
192// include!(concat!(env!("OUT_DIR"), "/generated_types.rs"));
193// This is not included here automatically to avoid compilation errors when IDL_PATH is not set.