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