Skip to main content

sui_graphql/client/
checkpoints.rs

1//! Checkpoint-related convenience methods.
2
3use sui_graphql_macros::Response;
4use sui_graphql_macros::graphql_query;
5use sui_sdk_types::CheckpointContents;
6use sui_sdk_types::CheckpointSummary;
7
8use super::Client;
9use crate::bcs::Bcs;
10use crate::error::Error;
11
12/// A checkpoint response containing the summary and contents.
13///
14/// This struct combines the checkpoint header (summary) with its contents.
15#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub struct CheckpointResponse {
18    /// The checkpoint summary (epoch, sequence number, timestamp, etc.)
19    pub summary: CheckpointSummary,
20    /// The checkpoint contents (transaction digests and signatures)
21    pub contents: CheckpointContents,
22}
23
24impl Client {
25    /// Fetch a checkpoint by its sequence number, or the latest checkpoint if not specified.
26    ///
27    /// Returns:
28    /// - `Ok(Some(response))` if the checkpoint exists
29    /// - `Ok(None)` if the checkpoint does not exist
30    /// - `Err(Error::Request)` for network errors
31    /// - `Err(Error::Base64)` / `Err(Error::Bcs)` for decoding errors
32    pub async fn get_checkpoint(
33        &self,
34        sequence_number: Option<u64>,
35    ) -> Result<Option<CheckpointResponse>, Error> {
36        #[derive(Response)]
37        struct Response {
38            #[field(path = "checkpoint?.summaryBcs?")]
39            summary_bcs: Option<Bcs<CheckpointSummary>>,
40            #[field(path = "checkpoint?.contentBcs?")]
41            content_bcs: Option<Bcs<CheckpointContents>>,
42        }
43
44        const QUERY: &str = graphql_query!(
45            "query($sequenceNumber: UInt53) {
46                checkpoint(sequenceNumber: $sequenceNumber) {
47                    summaryBcs
48                    contentBcs
49                }
50            }"
51        );
52        let variables = serde_json::json!({ "sequenceNumber": sequence_number });
53
54        let response = self.query::<Response>(QUERY, variables).await?;
55
56        let Some(data) = response.into_data() else {
57            return Ok(None);
58        };
59
60        let (Some(summary), Some(contents)) = (data.summary_bcs, data.content_bcs) else {
61            return Ok(None);
62        };
63
64        Ok(Some(CheckpointResponse {
65            summary: summary.0,
66            contents: contents.0,
67        }))
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use wiremock::Mock;
75    use wiremock::MockServer;
76    use wiremock::ResponseTemplate;
77    use wiremock::matchers::method;
78    use wiremock::matchers::path;
79
80    #[tokio::test]
81    async fn test_get_checkpoint_not_found() {
82        let mock_server = MockServer::start().await;
83
84        Mock::given(method("POST"))
85            .and(path("/"))
86            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
87                "data": {
88                    "checkpoint": null
89                }
90            })))
91            .mount(&mock_server)
92            .await;
93
94        let client = Client::new(&mock_server.uri()).unwrap();
95
96        let result = client.get_checkpoint(Some(999999999)).await;
97        assert!(result.is_ok());
98        assert!(result.unwrap().is_none());
99    }
100}