Skip to main content

quicknode_cascade/solana/
plugin.rs

1//! Solana plugin trait and PluginFuture type alias.
2//!
3//! Implement the [`Plugin`] trait to observe Solana block and transaction events.
4//! Register plugins with [`CascadeRunner::with_plugin()`](crate::CascadeRunner::with_plugin).
5
6use std::future::Future;
7use std::pin::Pin;
8
9use super::types::{
10    AccountActivityData, BlockData, TokenTransferData, TransactionData,
11};
12
13/// The return type for all plugin hooks. A boxed async future that returns
14/// `Result<(), Box<dyn Error>>`. Use `Box::pin(async move { ... })` to create one.
15pub type PluginFuture<'a> =
16    Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'a>>;
17
18/// Observe Solana block and transaction events.
19///
20/// Register your plugin with [`CascadeRunner::with_plugin()`](crate::CascadeRunner::with_plugin)
21/// and the framework handles all fetching, retries, parallelism, and cursor management.
22///
23/// All hooks have default no-op implementations. Override only the events you care about.
24///
25/// # Example
26///
27/// ```rust,no_run
28/// use quicknode_cascade::solana::{Plugin, PluginFuture, BlockData, TransactionData};
29///
30/// struct MyIndexer;
31///
32/// impl Plugin for MyIndexer {
33///     fn name(&self) -> &'static str { "my-indexer" }
34///
35///     fn on_block<'a>(&'a self, block: &'a BlockData) -> PluginFuture<'a> {
36///         Box::pin(async move {
37///             println!("slot {} — {} txs", block.slot, block.transaction_count);
38///             Ok(())
39///         })
40///     }
41///
42///     fn on_transaction<'a>(&'a self, tx: &'a TransactionData) -> PluginFuture<'a> {
43///         Box::pin(async move {
44///             if !tx.is_vote {
45///                 println!("  tx {}", tx.signature);
46///             }
47///             Ok(())
48///         })
49///     }
50/// }
51/// ```
52pub trait Plugin: Send + Sync + 'static {
53    /// Human-friendly name for this plugin (used in logs).
54    fn name(&self) -> &'static str;
55
56    /// Called once when the runner starts, before any data is fetched.
57    /// Use this to create tables, open connections, or validate config.
58    fn on_load<'a>(&'a self) -> PluginFuture<'a> {
59        Box::pin(async { Ok(()) })
60    }
61
62    /// Called for each block. Fired once per slot, before transactions.
63    fn on_block<'a>(&'a self, _block: &'a BlockData) -> PluginFuture<'a> {
64        Box::pin(async { Ok(()) })
65    }
66
67    /// Called for each transaction within a block.
68    /// Fired in order of `tx_index` within the slot.
69    fn on_transaction<'a>(&'a self, _tx: &'a TransactionData) -> PluginFuture<'a> {
70        Box::pin(async { Ok(()) })
71    }
72
73    /// Called for each token balance change within a transaction.
74    /// Only fired when the balance actually changed (pre != post).
75    fn on_token_transfer<'a>(&'a self, _transfer: &'a TokenTransferData) -> PluginFuture<'a> {
76        Box::pin(async { Ok(()) })
77    }
78
79    /// Called for each account touched by a transaction (SOL balance changes).
80    fn on_account_activity<'a>(&'a self, _activity: &'a AccountActivityData) -> PluginFuture<'a> {
81        Box::pin(async { Ok(()) })
82    }
83
84    /// Called when a slot was skipped by the Solana leader (no block produced).
85    fn on_skipped_slot<'a>(&'a self, _slot: u64) -> PluginFuture<'a> {
86        Box::pin(async { Ok(()) })
87    }
88
89    /// Called for each slot in raw mode. Receives the full JSON-RPC response.
90    /// Only fired when the runner is in raw encoding mode.
91    fn on_raw_block<'a>(&'a self, _slot: u64, _raw: &'a serde_json::Value) -> PluginFuture<'a> {
92        Box::pin(async { Ok(()) })
93    }
94
95    /// Called once when the runner is shutting down. Flush buffers, close connections.
96    fn on_exit<'a>(&'a self) -> PluginFuture<'a> {
97        Box::pin(async { Ok(()) })
98    }
99}