| Crates.io | circles-rpc |
| lib.rs | circles-rpc |
| version | 0.1.0 |
| created_at | 2025-12-05 12:13:21.524992+00 |
| updated_at | 2025-12-05 12:13:21.524992+00 |
| description | Circles JSON-RPC client (HTTP + WS) |
| homepage | https://circles-rs-book.vercel.app/ |
| repository | https://github.com/deluXtreme/circles-rs |
| max_upload_size | |
| id | 1968067 |
| size | 176,784 |
Async JSON-RPC client for the Circles protocol, mirroring the TS @rpc package while leaning on Alloy transports and shared Circles types.
CirclesRpc facade with method groups (balance, token, trust, avatar, profile, query, events, invitation, pathfinder, group, tables, health, network, search).try_from_http, TryFrom<&str>); WS subscriptions behind the ws feature with best-effort eth_unsubscribe on drop.circles_query helpers with cursor extraction plus PagedQuery/paged_stream convenience; paged_query is validated against live circles_query.TokenHolderNormalized) and invitation batching with bounded concurrency.[]), flattens batch frames, and maps unknown event types to CrcUnknownEvent.use circles_rpc::CirclesRpc;
use circles_types::Address;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let rpc = CirclesRpc::try_from("https://rpc.helsinki.aboutcircles.com/")?;
// Total balance (v2)
let addr: Address = "0xde374ece6fa50e781e81aac78e811b33d16912c7".parse()?;
let total = rpc.balance().get_total_balance(addr, false, true).await?;
println!("total v2 balance: {}", total.0);
// Token holders (normalized U256 balances)
let holders = rpc.token().get_token_holders(addr).await?;
println!("holders count: {}", holders.len());
Ok(())
}
use circles_rpc::CirclesRpc;
use circles_types::{PagedQueryParams, SortOrder, Address};
#[derive(Debug, serde::Deserialize)]
struct AvatarRow { avatar: Address, timestamp: u64 }
async fn first_page(rpc: &CirclesRpc) -> circles_rpc::Result<()> {
let params = PagedQueryParams {
namespace: "V_Crc".into(),
table: "Avatars".into(),
sort_order: SortOrder::DESC,
columns: vec![],
filter: None,
limit: 50,
};
// Pull one page
let mut pager = rpc.paged_query::<AvatarRow>(params.clone());
if let Some(page) = pager.next_page().await? {
println!("fetched {} rows, has_more={}", page.items.len(), page.has_more);
}
// Or stream rows
let mut stream = rpc.paged_stream::<AvatarRow>(params);
while let Some(row) = stream.next().await.transpose()? {
println!("row: {:?}", row);
}
Ok(())
}
HTTP fetch:
let events = rpc
.events()
.circles_events(Some(addr), 0, None, None)
.await?;
WebSocket subscription (enable the ws feature):
#[cfg(feature = "ws")]
{
let sub = rpc.events().subscribe_parsed_events(serde_json::json!({ "address": addr })).await?;
tokio::pin!(sub);
while let Some(evt) = sub.next().await.transpose()? {
println!("event: {:?}", evt.event_type);
}
}
wss://rpc.aboutcircles.com/ws, wss://rpc.helsinki.aboutcircles.com/ws) emit periodic empty arrays; these are dropped.CrcUnknownEvent. We have observed CrcV2_TransferSingle batches and an unknown CrcV2_TransferSummary type; schema validation on a busier node is still pending.paged_and_ws: fetch one page of avatars via circles_query and, with the ws feature, subscribe to Circles events. Tolerates heartbeats/batches and logs unknown events without panicking.CIRCLES_RPC_URL=https://rpc.aboutcircles.com/ \
CIRCLES_RPC_WS_URL=wss://rpc.aboutcircles.com/ws \
cargo run -p circles-rpc --example paged_and_ws --features ws