Skip to main content

sentrix_grpc_wasm/
client.rs

1//! Thin wrapper around the generated tonic client over a gRPC-Web transport.
2//!
3//! Why a wrapper instead of using `SentrixClient` directly:
4//!   1. Centralises the endpoint URL (`crate::GRPC_ENDPOINT`).
5//!   2. Hides the `tonic_web_wasm_client::Client` type so screens don't
6//!      need to know which transport they're on.
7//!   3. Gives us a single place to attach interceptors (auth, telemetry)
8//!      when we add them.
9
10use tonic_web_wasm_client::Client as WebClient;
11
12use super::pb::{
13    get_block_request::Selector, sentrix_client::SentrixClient, BlockHeight, EventFilter,
14    GetBalanceRequest, GetBlockRequest, GetMempoolRequest, GetSupplyRequest,
15    GetValidatorSetRequest, Mempool, StreamEventsRequest, Supply, ValidatorSet,
16};
17
18/// The concrete client type after we've wired the wasm transport.
19pub type Inner = SentrixClient<WebClient>;
20
21#[derive(Clone)]
22pub struct SentrixGrpcClient {
23    inner: Inner,
24}
25
26impl SentrixGrpcClient {
27    /// Build a client targeting the given endpoint. Cheap; just constructs
28    /// a `fetch()` wrapper. No network IO until the first RPC.
29    pub fn new(endpoint: impl Into<String>) -> Self {
30        let transport = WebClient::new(endpoint.into());
31        Self {
32            inner: SentrixClient::new(transport),
33        }
34    }
35
36    /// `latest` selector — matches the `GetBlock { latest: true }` path on
37    /// the chain-side handler.
38    pub async fn get_latest_block(&mut self) -> Result<super::pb::Block, tonic::Status> {
39        let req = GetBlockRequest {
40            selector: Some(Selector::Latest(true)),
41        };
42        let resp = self.inner.get_block(req).await?;
43        Ok(resp.into_inner())
44    }
45
46    pub async fn get_block_by_height(
47        &mut self,
48        height: u64,
49    ) -> Result<super::pb::Block, tonic::Status> {
50        let req = GetBlockRequest {
51            selector: Some(Selector::Height(BlockHeight { value: height })),
52        };
53        let resp = self.inner.get_block(req).await?;
54        Ok(resp.into_inner())
55    }
56
57    pub async fn get_balance(
58        &mut self,
59        addr: [u8; 20],
60    ) -> Result<super::pb::Account, tonic::Status> {
61        let req = GetBalanceRequest {
62            address: Some(super::pb::Address {
63                value: addr.to_vec(),
64            }),
65            at_height: None,
66        };
67        let resp = self.inner.get_balance(req).await?;
68        Ok(resp.into_inner())
69    }
70
71    /// v0.4 — full active set + jail/active flags + per-validator stake.
72    pub async fn get_validator_set(&mut self) -> Result<ValidatorSet, tonic::Status> {
73        let req = GetValidatorSetRequest { at_height: None };
74        let resp = self.inner.get_validator_set(req).await?;
75        Ok(resp.into_inner())
76    }
77
78    /// v0.4 — minted/burned/circulating supply snapshot.
79    pub async fn get_supply(&mut self) -> Result<Supply, tonic::Status> {
80        let req = GetSupplyRequest { at_height: None };
81        let resp = self.inner.get_supply(req).await?;
82        Ok(resp.into_inner())
83    }
84
85    /// v0.4 — pending-tx size + capped header window. `limit = 0` ⇒
86    /// server default (100). Pass a smaller limit for the dashboard
87    /// header card (just need `size`); pass max 500 for the mempool
88    /// panel that lists actual entries.
89    pub async fn get_mempool(&mut self, limit: u32) -> Result<Mempool, tonic::Status> {
90        let req = GetMempoolRequest { limit };
91        let resp = self.inner.get_mempool(req).await?;
92        Ok(resp.into_inner())
93    }
94
95    /// Server-streaming events. Returns a `Streaming<ChainEvent>` that the
96    /// caller drains with `.message().await`. Filter list is sent verbatim;
97    /// empty = subscribe-all (server-side filter).
98    pub async fn subscribe_events(
99        &mut self,
100        filters: Vec<EventFilter>,
101    ) -> Result<tonic::Streaming<super::pb::ChainEvent>, tonic::Status> {
102        let req = StreamEventsRequest {
103            filters: filters.into_iter().map(|f| f as i32).collect(),
104            from_sequence: 0,
105        };
106        let resp = self.inner.stream_events(req).await?;
107        Ok(resp.into_inner())
108    }
109}
110
111/// Convenience: short-form hex of a 32-byte hash for UI rendering.
112/// Returns "—" on length mismatch rather than panicking; the gRPC
113/// contract guarantees 32 bytes but we're playing defence.
114pub fn hash_short(h: &super::pb::Hash) -> String {
115    if h.value.len() != 32 {
116        return "—".into();
117    }
118    let hex = hex::encode(&h.value);
119    format!("{}…{}", &hex[..6], &hex[hex.len() - 4..])
120}