Skip to main content

triton_preconfs_client/
lib.rs

1//! Client for the Triton Preconfs streams.
2//!
3//! Preconfs are preconfirmed Solana transactions: the Harmonic and BAM feeds
4//! deliver them while the slot is still being built, before the transaction
5//! lands on chain. This crate connects to a Triton Preconfs server, builds
6//! validated filters and turns each stream into typed [`Event`]s, resubscribing
7//! when a connection drops.
8//!
9//! ```no_run
10//! use solana_pubkey::Pubkey;
11//! use triton_preconfs_client::{Connector, Event, Feed, Filter, Filters, Region, parse};
12//!
13//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
14//! let client = Connector::new("https://preconfs.rpcpool.com")
15//!     .x_token(Some("my-token"))
16//!     .connect()
17//!     .await?;
18//!
19//! let token_program: Pubkey = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".parse()?;
20//! let region = Region::parse(Feed::Harmonic, "ams")?;
21//! let filters = Filters::single(Filter::new().accounts([token_program]));
22//!
23//! let mut stream = client.subscribe_harmonic(region, filters).await?;
24//! while let Some(event) = stream.next().await {
25//!     match event? {
26//!         Event::Transaction(matched) => {
27//!             let signature = parse::parse_signature(&matched.transaction.transaction)?;
28//!             println!("slot {} {signature}", matched.transaction.slot);
29//!         }
30//!         Event::SlotEnd { slot } => println!("slot {slot} complete"),
31//!         Event::Reconnected { attempts } => println!("reconnected after {attempts} attempts"),
32//!         _ => {}
33//!     }
34//! }
35//! # Ok(()) }
36//! ```
37//!
38//! # Streams
39//!
40//! A stream serves one feed in one region and yields [`Event`]s. On the
41//! Harmonic feed every transaction sits between its slot's `SlotStart` and
42//! `SlotEnd`; after `SlotEnd` the program holds everything its filters
43//! matched for that slot. BAM has no framing, each transaction names its
44//! slot. Pings are consumed by the stream. Transactions withheld because an
45//! account is over its coverage share are announced with [`Event::Clip`],
46//! never silent.
47//!
48//! # Reconnect
49//!
50//! Points of presence restart on every deploy, so a long lived stream will
51//! drop. By default the stream resubscribes with a backoff and yields
52//! [`Event::Reconnected`] so the program knows it missed the data produced
53//! in between (preconfs from the gap cannot be replayed). Errors that
54//! retrying cannot fix end the stream: a bad token, a refused filter, a
55//! region the server does not serve. Tune it with [`Connector::reconnect`]
56//! or turn it off with [`Connector::no_reconnect`].
57//!
58//! # Errors
59//!
60//! One error type per step: [`ConnectError`] from connecting,
61//! [`SubscribeError`] from opening a stream, [`StreamError`] from a stream
62//! that ended. Wrap them with `anyhow` or `Box<dyn Error>` for one top
63//! level type.
64
65#![warn(missing_docs)]
66
67pub mod connect;
68pub mod error;
69pub mod feed;
70pub mod filter;
71pub mod parse;
72pub mod reconnect;
73pub mod stream;
74
75pub use {
76    connect::{Client, Connector},
77    error::{ConnectError, StreamError, SubscribeError},
78    feed::{Feed, Region, RegionError},
79    filter::{Filter, FilterError, Filters, InstructionFilter, Memcmp},
80    reconnect::Reconnect,
81    stream::{BamEvent, BamStream, Event, HarmonicEvent, HarmonicStream, Matched},
82    triton_preconfs_proto as proto,
83};