Skip to content

Commit 31016cf

Browse files
committed
sui-graphql-rust-sdk: add transaction fetching API [9/n]
1 parent 5e359a3 commit 31016cf

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

crates/sui-graphql/src/client/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod chain;
44
pub mod coins;
55
mod objects;
6+
pub mod transactions;
67

78
use reqwest::Url;
89
use serde::Deserialize;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//! Transaction-related convenience methods.
2+
3+
use base64ct::Base64;
4+
use base64ct::Encoding;
5+
use sui_graphql_macros::Response;
6+
use sui_sdk_types::Transaction;
7+
use sui_sdk_types::TransactionEffects;
8+
9+
use super::Client;
10+
use crate::error::Error;
11+
12+
/// A transaction response containing the transaction data and its effects.
13+
///
14+
/// This struct combines the transaction data with its execution results.
15+
#[derive(Debug, Clone)]
16+
#[non_exhaustive]
17+
pub struct TransactionResponse {
18+
/// The transaction data (sender, commands, gas payment, etc.)
19+
pub transaction: Transaction,
20+
/// The execution effects (status, gas used, object changes, etc.)
21+
pub effects: TransactionEffects,
22+
}
23+
24+
impl Client {
25+
/// Fetch a transaction by its digest and deserialize from BCS.
26+
///
27+
/// Returns:
28+
/// - `Ok(Some(response))` if the transaction exists
29+
/// - `Ok(None)` if the transaction does not exist
30+
/// - `Err(Error::Request)` for network errors
31+
/// - `Err(Error::Base64)` / `Err(Error::Bcs)` for decoding errors
32+
///
33+
/// # Example
34+
///
35+
/// ```no_run
36+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
37+
/// use sui_graphql::Client;
38+
///
39+
/// let client = Client::new("https://sui-mainnet.mystenlabs.com/graphql")?;
40+
/// let digest = "ABC123..."; // transaction digest
41+
///
42+
/// match client.get_transaction(digest).await? {
43+
/// Some(tx) => {
44+
/// println!("Sender: {}", tx.transaction.sender);
45+
/// println!("Status: {:?}", tx.effects.status());
46+
/// }
47+
/// None => println!("Transaction not found"),
48+
/// }
49+
/// # Ok(())
50+
/// # }
51+
/// ```
52+
pub async fn get_transaction(
53+
&self,
54+
digest: &str,
55+
) -> Result<Option<TransactionResponse>, Error> {
56+
#[derive(Response)]
57+
struct Response {
58+
#[field(path = "transaction.transactionBcs")]
59+
transaction_bcs: Option<String>,
60+
#[field(path = "transaction.effects.effectsBcs")]
61+
effects_bcs: Option<String>,
62+
}
63+
64+
const QUERY: &str = r#"
65+
query($digest: String!) {
66+
transaction(digest: $digest) {
67+
transactionBcs
68+
effects {
69+
effectsBcs
70+
}
71+
}
72+
}
73+
"#;
74+
75+
let variables = serde_json::json!({ "digest": digest });
76+
77+
let response = self.query::<Response>(QUERY, variables).await?;
78+
79+
let Some(data) = response.into_data() else {
80+
return Ok(None);
81+
};
82+
83+
let (Some(tx_bcs), Some(effects_bcs)) = (data.transaction_bcs, data.effects_bcs) else {
84+
return Ok(None);
85+
};
86+
87+
let tx_bytes = Base64::decode_vec(&tx_bcs)?;
88+
let transaction: Transaction = bcs::from_bytes(&tx_bytes)?;
89+
90+
let effects_bytes = Base64::decode_vec(&effects_bcs)?;
91+
let effects: TransactionEffects = bcs::from_bytes(&effects_bytes)?;
92+
93+
Ok(Some(TransactionResponse {
94+
transaction,
95+
effects,
96+
}))
97+
}
98+
}
99+
100+
#[cfg(test)]
101+
mod tests {
102+
use super::*;
103+
use wiremock::Mock;
104+
use wiremock::MockServer;
105+
use wiremock::ResponseTemplate;
106+
use wiremock::matchers::method;
107+
use wiremock::matchers::path;
108+
109+
#[tokio::test]
110+
async fn test_get_transaction_not_found() {
111+
let mock_server = MockServer::start().await;
112+
113+
Mock::given(method("POST"))
114+
.and(path("/"))
115+
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
116+
"data": {
117+
"transaction": null
118+
}
119+
})))
120+
.mount(&mock_server)
121+
.await;
122+
123+
let client = Client::new(&mock_server.uri()).unwrap();
124+
125+
let result = client.get_transaction("nonexistent").await;
126+
assert!(result.is_ok());
127+
assert!(result.unwrap().is_none());
128+
}
129+
}

crates/sui-graphql/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod scalars;
1414
pub use client::Client;
1515
pub use client::chain::Epoch;
1616
pub use client::coins::Balance;
17+
pub use client::transactions::TransactionResponse;
1718
pub use error::Error;
1819
pub use error::GraphQLError;
1920
pub use error::Location;

0 commit comments

Comments
 (0)