Expand description
Bulk Labs WebSocket Trading Client — Actor + Watch architecture.
All mutable state lives inside a single [Actor] task. The public
BulkWsClient handle is a cheap, cloneable struct that communicates
with the actor via:
tokio::sync::watchfor hot-path reads (tickers, prices, margin) — zero-cost.borrow(), no async round-trip.mpsccommand channel for writes (subscribe, place orders, etc.).oneshotfor request/response flows (order placement).
┌──────────────┐ mpsc::channel ┌───────────────┐
│ BulkWsClient │ ───── Command ────────────────▶ │ Actor │
│ (handle) │ ◀──── watch::Receiver ──────── │ (owns state) │
└──────────────┘ └───────┬───────┘
│ │
│ oneshot for order responses │ tokio::select!
└─────────────────────────────────────────────────▶│◀── ws_read§Example
use bulk_client::*;
use bulk_client::common::side::Side;
use bulk_client::common::tif::TimeInForce;
use bulk_client::transaction::TransactionSigner;
use bulk_client::parts::WSConfig;
#[tokio::main]
async fn main() -> eyre::Result<()> {
let signer = TransactionSigner::from_private_key("your_base58_key")?;
let client = BulkWsClient::connect(WSConfig {
url: "wss://exchange-wss.bulk.trade".into(),
symbols: vec!["BTC-USD".into(), "ETH-USD".into()],
signer: Some(signer),
..Default::default()
}).await?;
// Zero-cost read — no lock, no channel round-trip
if let Some(ticker) = client.get_ticker("BTC-USD") {
println!("BTC mark price: {}", ticker.mark_price);
}
// Place an order — goes through actor → ws
let resp = client.place_limit_order(
"BTC-USD", Side::Buy, 95_000.0, 0.01,
TimeInForce::GTC, false, None, None,
).await?;
client.shutdown().await;
Ok(())
}Structs§
- Account
State - Everything a reader might want, exposed as one cheap clone via
watch. The actor publishes a new snapshot after every state-mutating message. - Bulk
WsClient - Cloneable client handle.
Type Aliases§
- Event
Handler - User-supplied callback. Receives the raw JSON payload for the topic. Runs synchronously inside the actor loop — keep it fast or spawn.