Skip to main content

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.