# satd-events-client
Async Rust client SDK for the satd **Streaming Consumption API** (`satd.events.v1`).
It wraps the generated [tonic](https://github.com/hyperium/tonic) client with a
typed event model, per-call auth metadata, cursor capture/persistence,
reconnect-with-backoff, `Lagged` auto-resume, replay-truncation detection, and a
privacy-preserving prefix-watch local re-filter — so a consumer writes against
`StreamClient` instead of hand-rolling channels, metadata, and protobuf
unwrapping.
- **Typed events & watches** — `Event` is a flat enum over the proto `oneof`;
`WatchHandle` has a typed helper for every watch kind (scripts with
`min_value` floors, outpoints, txid lifecycle, depth alarms, descriptors,
prefixes).
- **Durable firehose** — `resilient_subscribe` reconnects with backoff and
persists the resume cursor through a `CursorStore`, so a process restart
resumes from the stored height; `Lagged` recovers automatically and a
server-side replay clamp surfaces as a synthetic `Event::ReplayGap`.
- **Native TLS / mTLS** — `tls()` / `tls_ca_pem()` / `tls_client_identity()` /
`tls_domain()` encrypt the transport (ring rustls provider) so a bearer token
is never sent in cleartext.
- **No server-dependency leak** — wire types live in the thin
[`satd-events-proto`](https://docs.rs/satd-events-proto) crate, so this SDK
pulls none of the `node`/RocksDB server stack.
## Install
> **Pre-publish:** this crate is not yet on crates.io. Depend on it via git
> until the published release lands:
```toml
[dependencies]
satd-events-client = { git = "https://github.com/epochbtc/satd", branch = "master" }
```
Once published, `cargo add satd-events-client`.
To read the API docs before publication, generate them locally:
```sh
cargo doc -p satd-events-client --no-deps --all-features --open
```
## Quickstart
```rust,no_run
use satd_events_client::{StreamClient, SubscribeOptions, Categories, Event};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = StreamClient::builder("http://127.0.0.1:50051")
.keepalive_default()
.connect()
.await?;
let mut events = client
.subscribe(SubscribeOptions {
categories: Categories::MEMPOOL | Categories::CHAIN,
..Default::default()
})
.await?;
while let Some(event) = events.message().await? {
if let Event::BlockConnected { height, .. } = event {
println!("block {height}");
}
}
Ok(())
}
```
Over a plaintext `http://` endpoint a bearer token travels in cleartext — enable
TLS (below), restrict bearer auth to loopback, or front the node with a
TLS-terminating proxy.
```rust,no_run
# use satd_events_client::StreamClient;
# async fn run() -> Result<(), Box<dyn std::error::Error>> {
// Pin a satd node's own (self-signed) CA and authenticate with a token.
let client = StreamClient::builder("https://node.example:50051")
.tls_ca_pem(std::fs::read("node-ca.pem")?)
.bearer_token("…")
.connect()
.await?;
# let _ = client; Ok(())
# }
```
## Feature flags
| `bitcoin` | on | Typed bitcoin helpers + the `PrefixWatcher` prefix-watch re-filter (pulls `bitcoin`/secp256k1). |
| `tls` | on | Native TLS / mTLS via tonic's rustls integration (ring provider; no aws-lc-rs / C toolchain). |
For a minimal, plaintext-only build: `default-features = false`.
## Examples
Runnable examples live in [`examples/`](examples/) — `firehose_tail`,
`resilient_tail`, `watch_outpoints`, `descriptor_wallet`, `lifecycle_alarms`,
`prefix_privacy`, `tls_tail`, `mtls_tail`:
```sh
cargo run -p satd-events-client --example firehose_tail -- http://127.0.0.1:50051
```
## Stability & versioning
The crate version **is** the satd version it shipped with, and it follows
[semver](https://semver.org/). Use an SDK whose minor version is at or below
your node's; newer nodes only add fields and event kinds within the same schema.
Each time a stream opens, the SDK compares the node's advertised version
(`satd-version` response header) with its own:
- same or newer node: silent;
- one minor version behind: a `tracing` warning (target
`satd_events_client::compat`) once per node version, which means *upgrade
the node*;
- two or more minor versions, or a major version, behind:
`StreamError::NodeTooOld`, unless the client was built with
`allow_old_node()`, which turns it into the warning;
- a different event schema: `StreamError::SchemaMismatch`, always.
A node older than 0.6.0 sends no header and counts as 0.5.
`StreamClient::node_version()` reports what the node advertised. The rule is
written down in
[`STABILITY_POLICY.md`](https://github.com/epochbtc/satd/blob/master/STABILITY_POLICY.md#streaming-api--sdk-compatibility).
MSRV is **1.93** (an MSRV bump is a minor-version change).
## More
- **Operator Manual — Rust SDK chapter:** <https://epochbtc.github.io/satd/rust-sdk.html>
- **Wire / streaming spec:** [`docs/api/streaming.md`](https://github.com/epochbtc/satd/blob/master/docs/api/streaming.md)
## License
MIT — see [LICENSE](https://github.com/epochbtc/satd/blob/master/LICENSE).