sui_graphql/client/
checkpoints.rs

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