Client and protobuf definitions for the Triton Preconfs streams.
A preconfirmation is a transaction announced by the party building the block the moment it is executed or committed into the slot, before any shred exists and before any node reports it. It is the earliest signal that a transaction is in a block, not the cluster's confirmation. Two feeds produce them, Harmonic and BAM, each with its own regions.
Full documentation, including how each feed works and what the stream guarantees, is at docs.triton.one.
| crate | what |
|---|---|
triton-preconfs-proto |
proto/preconfs.proto and the generated messages and gRPC clients (docs) |
triton-preconfs-client |
connection, feeds and regions, filters, transaction parsing (docs) |
examples/rust |
preconfs-subscribe, a CLI that subscribes and logs updates |
use triton_preconfs_client::{Connector, Event, Feed, Filter, Filters, Region};
let client = Connector::new("https://preconfs.rpcpool.com")
.x_token(Some(token))
.connect()
.await?;
let region = Region::parse(Feed::Harmonic, "ams")?;
let filters = Filters::single(Filter::new().accounts([account]));
let mut stream = client.subscribe_harmonic(region, filters).await?;
while let Some(event) = stream.next().await {
match event? {
Event::Transaction(matched) => println!("{:?}", matched.transaction),
Event::SlotEnd { slot } => println!("slot {slot} complete"),
Event::Reconnected { .. } => println!("reconnected, data in between is lost"),
_ => {}
}
}The same shape works for the BAM feed with Feed::Bam and
subscribe_bam. The full program is in examples/rust:
cargo run -p preconfs-example -- --endpoint https://preconfs.rpcpool.com \
--x-token $TOKEN --region harmonic:ams \
--account TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
preconfs.rpcpool.comis anycast: the connection lands on the closest point of presence, one of the servers behind that address.Connector::dialpins one by address.- Every request carries your
x-token, the token issued with your preconfs subscription. The server answers nothing without it, version and health checks included. - A stream serves one feed in one region. Harmonic regions: ams, ewr, fra,
lon, tyo, sgp, slc. BAM regions:
Feed::Bam.regions()lists them.
Filters are named; every matching update echoes the names that matched. A transaction matches a filter when it satisfies every set condition:
account_include: references any of these accountsaccount_required: references all of these accountsaccount_exclude: drops transactions referencing any of these; narrows a selection, cannot stand alonesigner_include: signed by any of these accounts (fee payer included)signer_exclude: drops transactions signed by any of these; narrows a selection, cannot stand aloneinstructions: a top-level instruction invokes the program and its data passes every memcmp (bytes at an offset) and the exact data size, when set; any of the listed instruction filters. CPI instructions are not seen.signatures: is one of these signaturesexecution_results: landed with one of these outcomes (Harmonic only)
Account conditions see the static account keys only; an account a v0 transaction loads through a lookup table is not seen, by include or by exclude.
Limits, checked client side before the request is sent: 64 filters per stream, 10000 accounts per list, 1000 signatures per filter, 64 byte names, 16 instruction filters per stream, 4 memcmps per instruction filter, 128 bytes per memcmp, and a memcmp or data size must fit in 4096 bytes of instruction data. Every filter must select something; full feed subscriptions are refused.
Transactions touching a pool, without a spammer that trades it all day:
Filter::new().accounts([pool]).exclude_accounts([spammer])Transactions a wallet actually signed, as fee payer or cosigner. A wallet that is only referenced, say as the recipient of a transfer, does not match:
Filter::new().signers([wallet])
// only its trades on one program
Filter::new().signers([wallet]).accounts([program])One instruction of a program, from any sender. Anchor programs start the
data with an 8 byte discriminator, the first 8 bytes of
sha256("global:<instruction name>"); Meteora DBC create_config is
c9cff3724b6f2fbd, so new configs arrive without their swaps:
let dbc: Pubkey = "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN".parse()?;
Filter::new().instructions([
InstructionFilter::new(dbc).memcmp(0, [201, 207, 243, 114, 75, 111, 47, 189]),
])Native programs use a one byte tag, and data_size pins the layout. SPL
Token TransferChecked is tag 12 followed by an 8 byte amount and a 1 byte
decimals:
Filter::new().instructions([InstructionFilter::new(token_program).memcmp(0, [12]).data_size(10)])A program invoked by the transaction, not merely mentioned in it:
InstructionFilter::new(program) matches a top-level instruction of that
program, while accounts([program]) also matches transactions that only
pass the program id as an account.
Everything above except your own transactions:
Filter::new().instructions([swap]).exclude_signers([my_wallet])The example CLI takes the same conditions: --exclude, --signer,
--exclude-signer, --instruction PROGRAM or PROGRAM:OFFSET:HEX, and
--data-size:
cargo run -p preconfs-example -- --x-token $TOKEN --region bam:fra \
--instruction dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN:0:c9cff3724b6f2fbd
- Harmonic events are framed per slot:
SlotStart, the transactions,SlotEnd. AfterSlotEndfor a slot you hold everything your filters matched for it. A stream that subscribes while a slot is open joins at the nextSlotStart. BAM has no framing; each transaction names its slot. - The server never drops matching transactions silently. Withheld
transactions are announced with an
Event::Clip(see Coverage below); if you cannot keep up, the stream ends with an explicit error. - Streams reconnect by default. When a point of presence restarts, the
stream resubscribes with a backoff and yields
Event::Reconnected; the data produced in between is gone.Connector::reconnecttunes the schedule,Connector::no_reconnectturns it off. - Transactions carry raw bytes.
parse::parse_static_partsreturns the first signature and the static account keys;parse::parse_signaturereturns the signature alone.
Each account may receive up to a share of a feed's total traffic, measured
over a sliding window. Over that share, matching transactions are withheld
and the count is announced with Event::Clip. Staying over it ends the
stream with ResourceExhausted, and subscribing again is refused for a
cooloff period; with reconnect on, the stream retries by itself. Filters
that select only what you need keep you under the share.
Every release has an entry in CHANGELOG.md. Released clients keep working against newer servers.