Rust client for the Blokli GraphQL API and transaction endpoints.
blokli-client provides a typed, async interface to a Blokli service. It wraps Blokli's GraphQL API with
ergonomic Rust traits for:
- querying accounts, balances, channels, safes, ticket statistics, chain metadata, and transaction state;
- subscribing to server-sent event streams for accounts, channels, health, graph changes, safe deployments, ticket redemptions, and tracked transactions;
- submitting signed transactions and optionally waiting for tracking or confirmation.
The main entry point is [BlokliClient]. Most operations are trait methods, so bring the trait for the operation
family into scope:
- [
BlokliQueryClient] for one-shot GraphQL queries; - [
BlokliSubscriptionClient] for streaming subscriptions; - [
BlokliTransactionClient] for signed transaction submission and tracking.
API concepts
A [BlokliClient] is configured with the Blokli service base URL, not the GraphQL endpoint itself. For example,
https://blokli.example.org becomes https://blokli.example.org/graphql for GraphQL requests and SSE
subscriptions.
Queries and subscriptions use typed selectors instead of generic filter maps. Address-like values such as
[ChainAddress], [ChannelId], [PacketKey], and [TxReceipt] are byte arrays at the public boundary and are
encoded as hex strings for GraphQL. Blokli-specific identifiers such as [KeyId] and [TxId] are kept distinct:
a [TxId] is a Blokli tracking id, while a [TxReceipt] is the on-chain transaction hash returned by submission
endpoints.
Subscriptions are GraphQL operations delivered over server-sent events. They yield streams of Result<T, E> so
callers can decide how to handle item-level errors. Transaction helpers operate on already-signed raw transaction
bytes; this crate does not sign transactions.
Quick start
Create a client with a Blokli base URL. The client derives the GraphQL endpoint by appending /graphql to that
base URL.
use blokli_client::{BlokliClient, BlokliClientConfig, BlokliQueryClient};
async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = BlokliClient::new("https://blokli.example.org".parse()?, BlokliClientConfig::default());
let version = client.query_version().await?;
let chain = client.query_chain_info().await?;
println!("Blokli {version} indexes chain {}", chain.chain_id);
Ok(())
}
Querying
Selectors are strongly typed. For example, channel queries use [ChannelSelector] plus an optional
[ChannelFilter] and status.
use blokli_client::{BlokliClient, BlokliClientConfig, BlokliQueryClient, ChannelFilter, ChannelSelector};
async fn example(source: u32) -> Result<(), Box<dyn std::error::Error>> {
let client = BlokliClient::new("https://blokli.example.org".parse()?, BlokliClientConfig::default());
let selector = ChannelSelector {
filter: Some(ChannelFilter::SourceKeyId(source)),
..Default::default()
};
let channels = client.query_channels(selector).await?;
println!("{} channels found", channels.channels.len());
Ok(())
}
Subscriptions
Subscription methods return [futures::Stream] values. Streams use Blokli's SSE endpoint and yield Result items
so callers can decide how to handle transient transport, parsing, or GraphQL errors.
use blokli_client::{AccountSelector, BlokliClient, BlokliClientConfig, BlokliSubscriptionClient};
use futures::TryStreamExt;
async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = BlokliClient::new("https://blokli.example.org".parse()?, BlokliClientConfig::default());
let mut accounts = Box::pin(client.subscribe_accounts(AccountSelector::Any)?);
while let Some(account) = accounts.try_next().await? {
println!("account key id: {}", account.keyid);
}
Ok(())
}
Transactions
Transaction methods expect already-signed raw transactions. submit_transaction returns immediately after
submission, submit_and_track_transaction returns a Blokli tracking id, and submit_and_confirm_transaction
waits for the requested number of confirmations.
use std::time::Duration;
use blokli_client::{BlokliClient, BlokliClientConfig, BlokliTransactionClient};
async fn example(signed_transaction: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
let client = BlokliClient::new("https://blokli.example.org".parse()?, BlokliClientConfig::default());
let tx_id = client.submit_and_track_transaction(signed_transaction).await?;
let transaction = client.track_transaction(tx_id, Duration::from_secs(120)).await?;
println!("transaction status: {:?}", transaction.status);
Ok(())
}
Public API layout
Common selectors, traits, and address aliases are re-exported at the crate root. The versioned API remains available
under [api::v1], and GraphQL response models are grouped under [types].
Errors
Fallible operations return [BlokliClientError]. Use [BlokliClientError::kind] when matching on stable client
error categories such as invalid input, GraphQL errors, timeouts, or transaction tracking failures.
DNS override
By default, [BlokliClient] uses the system DNS resolver through reqwest. Callers that need to keep Blokli
communication working while DNS is unreliable can configure [BlokliClientConfig::dns_override] to pin the Blokli
URL hostname to a fixed IP address.
The request hostname is not rewritten. For example, a client configured with https://blokli.example.org and a DNS
override still sends requests for blokli.example.org, preserving TLS SNI and certificate validation while
bypassing system DNS for that hostname. When [BlokliDnsOverride::port] is set, it becomes the request port;
otherwise the original URL port or scheme default is used.
use std::net::IpAddr;
use blokli_client::{BlokliClient, BlokliClientConfig, BlokliDnsOverride};
let client = BlokliClient::new(
"https://blokli.example.org".parse()?,
BlokliClientConfig {
dns_override: Some(BlokliDnsOverride {
ip: IpAddr::from([203, 0, 113, 10]),
port: None,
}),
..Default::default()
},
);
# let _ = client;
# Ok::<(), Box<dyn std::error::Error>>(())
Leave dns_override as None to use normal system DNS resolution.