quicknode-cascade 0.2.3

Stream blockchain data at scale. Plugin-based framework powered by QuickNode Cascade — start with Solana, more chains coming.
Documentation
//! Solana plugin trait and PluginFuture type alias.
//!
//! Implement the [`Plugin`] trait to observe Solana block and transaction events.
//! Register plugins with [`CascadeRunner::with_plugin()`](crate::CascadeRunner::with_plugin).
//!
//! Two approaches:
//! - **Custom extraction**: implement [`on_raw`](Plugin::on_raw) and parse the raw JSON yourself.
//!   Use [`extract_block()`](super::extract_block) as a utility if you want structured data too.
//! - **Built-in extraction**: implement [`on_block`](Plugin::on_block),
//!   [`on_transaction`](Plugin::on_transaction), etc. and let the framework extract for you.
//!
//! Both can be combined — `on_raw` fires first, then the structured hooks.

use std::future::Future;
use std::pin::Pin;

use super::types::{
    AccountActivityData, BlockData, TokenTransferData, TransactionData,
};

/// The return type for all plugin hooks. A boxed async future that returns
/// `Result<(), Box<dyn Error>>`. Use `Box::pin(async move { ... })` to create one.
pub type PluginFuture<'a> =
    Pin<Box<dyn Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'a>>;

/// Observe Solana block and transaction events.
///
/// Two approaches to consuming data:
///
/// **1. Custom extraction** — implement [`on_raw`](Self::on_raw) and parse the raw
/// JSON-RPC response yourself. Use [`extract_block()`](super::extract_block)
/// as an optional utility.
///
/// **2. Built-in extraction** — implement [`on_block`](Self::on_block),
/// [`on_transaction`](Self::on_transaction), etc. The framework extracts structured data
/// and calls your hooks.
///
/// Both can be combined: `on_raw` fires first with the raw JSON, then the framework
/// extracts and calls the structured hooks.
///
/// # Example: Custom extraction
///
/// ```rust,no_run
/// use quicknode_cascade::solana::{Plugin, PluginFuture};
///
/// struct RawProcessor;
///
/// impl Plugin for RawProcessor {
///     fn name(&self) -> &'static str { "raw" }
///
///     fn on_raw<'a>(&'a self, slot: u64, raw: &'a serde_json::Value) -> PluginFuture<'a> {
///         Box::pin(async move {
///             let txs = raw.get("transactions").and_then(|v| v.as_array());
///             println!("slot {} has {} txs", slot, txs.map_or(0, |t| t.len()));
///             Ok(())
///         })
///     }
/// }
/// ```
///
/// # Example: Built-in extraction
///
/// ```rust,no_run
/// use quicknode_cascade::solana::{Plugin, PluginFuture, BlockData, TransactionData};
///
/// struct MyIndexer;
///
/// impl Plugin for MyIndexer {
///     fn name(&self) -> &'static str { "my-indexer" }
///
///     fn on_block<'a>(&'a self, block: &'a BlockData) -> PluginFuture<'a> {
///         Box::pin(async move {
///             println!("slot {} — {} txs", block.slot, block.transaction_count);
///             Ok(())
///         })
///     }
///
///     fn on_transaction<'a>(&'a self, tx: &'a TransactionData) -> PluginFuture<'a> {
///         Box::pin(async move {
///             if !tx.is_vote {
///                 println!("  tx {}", tx.signature);
///             }
///             Ok(())
///         })
///     }
/// }
/// ```
pub trait Plugin: Send + Sync + 'static {
    /// Human-friendly name for this plugin (used in logs).
    fn name(&self) -> &'static str;

    /// Called once when the runner starts, before any data is fetched.
    /// Use this to create tables, open connections, or validate config.
    fn on_load<'a>(&'a self) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }

    /// Called for every non-skipped slot with the full raw JSON-RPC response.
    ///
    /// This fires BEFORE the built-in extraction hooks (`on_block`, `on_transaction`, etc.).
    /// Use this for custom extraction — parse the JSON however you need.
    ///
    /// The name `on_raw` is consistent across all chain modules so multi-chain
    /// code follows the same pattern everywhere.
    ///
    /// If you also want the framework's structured data from within your custom
    /// extraction, call [`extract_block()`](super::extract_block) on a clone of the JSON.
    fn on_raw<'a>(&'a self, _slot: u64, _raw: &'a serde_json::Value) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }

    /// Called for each block. Fired once per slot, before transactions.
    fn on_block<'a>(&'a self, _block: &'a BlockData) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }

    /// Called for each transaction within a block.
    /// Fired in order of `tx_index` within the slot.
    fn on_transaction<'a>(&'a self, _tx: &'a TransactionData) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }

    /// Called for each token balance change within a transaction.
    /// Only fired when the balance actually changed (pre != post).
    fn on_token_transfer<'a>(&'a self, _transfer: &'a TokenTransferData) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }

    /// Called for each account touched by a transaction (SOL balance changes).
    fn on_account_activity<'a>(&'a self, _activity: &'a AccountActivityData) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }

    /// Called when a slot was skipped by the Solana leader (no block produced).
    fn on_skipped_slot<'a>(&'a self, _slot: u64) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }

    /// Called once when the runner is shutting down. Flush buffers, close connections.
    fn on_exit<'a>(&'a self) -> PluginFuture<'a> {
        Box::pin(async { Ok(()) })
    }
}