sui_graphql/client/
transactions.rs1use sui_graphql_macros::Response;
4use sui_sdk_types::Transaction;
5use sui_sdk_types::TransactionEffects;
6
7use super::Client;
8use crate::bcs::Bcs;
9use crate::error::Error;
10use crate::scalars::DateTime;
11
12pub use sui_rpc::proto::sui::rpc::v2::BalanceChange;
14
15#[derive(Debug, Clone)]
19#[non_exhaustive]
20pub struct TransactionResponse {
21 pub transaction: Transaction,
23 pub effects: TransactionEffects,
25 pub balance_changes: Vec<BalanceChange>,
27 pub checkpoint: u64,
29 pub timestamp: DateTime,
31}
32
33impl Client {
34 pub async fn get_transaction(
62 &self,
63 digest: &str,
64 ) -> Result<Option<TransactionResponse>, Error> {
65 #[derive(Response)]
66 struct Response {
67 #[field(path = "transaction?.transactionBcs?")]
68 transaction_bcs: Option<Bcs<Transaction>>,
69 #[field(path = "transaction?.effects?.effectsBcs?")]
70 effects_bcs: Option<Bcs<TransactionEffects>>,
71 #[field(path = "transaction?.effects?.balanceChangesJson?")]
72 balance_changes: Option<Vec<BalanceChange>>,
73 #[field(path = "transaction?.effects?.checkpoint?.sequenceNumber?")]
74 checkpoint: Option<u64>,
75 #[field(path = "transaction?.effects?.timestamp?")]
76 timestamp: Option<DateTime>,
77 }
78
79 const QUERY: &str = r#"
80 query($digest: String!) {
81 transaction(digest: $digest) {
82 transactionBcs
83 effects {
84 effectsBcs
85 balanceChangesJson
86 checkpoint {
87 sequenceNumber
88 }
89 timestamp
90 }
91 }
92 }
93 "#;
94
95 let variables = serde_json::json!({ "digest": digest });
96
97 let response = self.query::<Response>(QUERY, variables).await?;
98
99 let Some(data) = response.into_data() else {
100 return Ok(None);
101 };
102
103 let (Some(transaction), Some(effects)) = (data.transaction_bcs, data.effects_bcs) else {
104 return Ok(None);
105 };
106
107 let transaction = transaction.0;
108 let effects = effects.0;
109 let balance_changes = data.balance_changes.unwrap_or_default();
110 let checkpoint = data.checkpoint.ok_or(Error::MissingData("checkpoint"))?;
111 let timestamp = data.timestamp.ok_or(Error::MissingData("timestamp"))?;
112
113 Ok(Some(TransactionResponse {
114 transaction,
115 effects,
116 balance_changes,
117 checkpoint,
118 timestamp,
119 }))
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use wiremock::Mock;
127 use wiremock::MockServer;
128 use wiremock::ResponseTemplate;
129 use wiremock::matchers::method;
130 use wiremock::matchers::path;
131
132 #[tokio::test]
133 async fn test_get_transaction_not_found() {
134 let mock_server = MockServer::start().await;
135
136 Mock::given(method("POST"))
137 .and(path("/"))
138 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
139 "data": {
140 "transaction": null
141 }
142 })))
143 .mount(&mock_server)
144 .await;
145
146 let client = Client::new(&mock_server.uri()).unwrap();
147
148 let result = client.get_transaction("nonexistent").await;
149 assert!(result.is_ok());
150 assert!(result.unwrap().is_none());
151 }
152}