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}