koios_sdk/api/
transaction.rs

1use crate::error::{Error, Result};
2use crate::models::UtxoInfo;
3use crate::{
4    models::{
5        requests::{TransactionIdsRequest, TransactionInfoRequest, UtxoRefsWithExtendedRequest},
6        transaction::{
7            TransactionCbor, TransactionInfo, TransactionMetadata, TransactionStatus, TxMetaLabels,
8        },
9    },
10    Client,
11};
12use reqwest::StatusCode;
13
14impl Client {
15    /// Get UTxO set for requested UTxO references
16    ///
17    /// # Arguments
18    ///
19    /// * `utxo_refs` - List of UTxO references to query
20    /// * `extended` - Optional flag to include extended information
21    ///
22    /// # Examples
23    ///
24    /// ```no_run
25    /// use koios_sdk::Client;
26    ///
27    /// #[tokio::main]
28    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
29    ///     let client = Client::new()?;
30    ///     let utxo_refs = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef#1".to_string()];
31    ///     let utxo_info = client.get_utxo_info(&utxo_refs, Some(true)).await?;
32    ///     println!("UTxO info: {:?}", utxo_info);
33    ///     Ok(())
34    /// }
35    /// ```
36    pub async fn get_utxo_info(
37        &self,
38        utxo_refs: &[String],
39        extended: Option<bool>,
40    ) -> Result<Vec<UtxoInfo>> {
41        let request = UtxoRefsWithExtendedRequest {
42            utxo_refs: utxo_refs.to_vec(),
43            extended,
44        };
45        self.post("/utxo_info", &request).await
46    }
47
48    /// Get raw transaction(s) in CBOR format
49    ///
50    /// # Arguments
51    ///
52    /// * `tx_hashes` - List of transaction hashes to query
53    ///
54    /// # Examples
55    ///
56    /// ```no_run
57    /// use koios_sdk::Client;
58    ///
59    /// #[tokio::main]
60    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
61    ///     let client = Client::new()?;
62    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
63    ///     let tx_cbor = client.get_transaction_cbor(&tx_hashes).await?;
64    ///     println!("Transaction CBOR: {:?}", tx_cbor);
65    ///     Ok(())
66    /// }
67    /// ```
68    pub async fn get_transaction_cbor(&self, tx_hashes: &[String]) -> Result<Vec<TransactionCbor>> {
69        let request = TransactionIdsRequest {
70            tx_hashes: tx_hashes.to_vec(),
71        };
72        self.post("/tx_cbor", &request).await
73    }
74
75    /// Get detailed information about transaction(s)
76    ///
77    /// # Arguments
78    ///
79    /// * `tx_hashes` - List of transaction hashes to query
80    /// * `options` - Optional parameters for customizing the response
81    ///
82    /// # Examples
83    ///
84    /// ```no_run
85    /// use koios_sdk::Client;
86    /// use koios_sdk::models::requests::TransactionInfoRequest;
87    ///
88    /// #[tokio::main]
89    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
90    ///     let client = Client::new()?;
91    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
92    ///     let options = TransactionInfoRequest {
93    ///         tx_hashes,
94    ///         inputs: Some(true),
95    ///         ..Default::default()
96    ///     };
97    ///     let tx_info = client.get_transaction_info(&options).await?;
98    ///     println!("Transaction info: {:?}", tx_info);
99    ///     Ok(())
100    /// }
101    /// ```
102    pub async fn get_transaction_info(
103        &self,
104        options: &TransactionInfoRequest,
105    ) -> Result<Vec<TransactionInfo>> {
106        self.post("/tx_info", options).await
107    }
108
109    /// Get metadata information (if any) for given transaction(s)
110    ///
111    /// # Arguments
112    ///
113    /// * `tx_hashes` - List of transaction hashes to query
114    ///
115    /// # Examples
116    ///
117    /// ```no_run
118    /// use koios_sdk::Client;
119    ///
120    /// #[tokio::main]
121    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
122    ///     let client = Client::new()?;
123    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
124    ///     let metadata = client.get_transaction_metadata(&tx_hashes).await?;
125    ///     println!("Transaction metadata: {:?}", metadata);
126    ///     Ok(())
127    /// }
128    /// ```
129    pub async fn get_transaction_metadata(
130        &self,
131        tx_hashes: &[String],
132    ) -> Result<Vec<TransactionMetadata>> {
133        let request = TransactionIdsRequest {
134            tx_hashes: tx_hashes.to_vec(),
135        };
136        self.post("/tx_metadata", &request).await
137    }
138
139    /// Get a list of all transaction metalabels
140    ///
141    /// # Examples
142    ///
143    /// ```no_run
144    /// use koios_sdk::Client;
145    ///
146    /// #[tokio::main]
147    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
148    ///     let client = Client::new()?;
149    ///     let metalabels = client.get_transaction_metalabels().await?;
150    ///     println!("Transaction metalabels: {:?}", metalabels);
151    ///     Ok(())
152    /// }
153    /// ```
154    pub async fn get_transaction_metalabels(&self) -> Result<Vec<TxMetaLabels>> {
155        self.get("/tx_metalabels").await
156    }
157
158    /// Submit an already serialized transaction to the network
159    ///
160    /// # Arguments
161    ///
162    /// * `cbor_data` - Raw CBOR data of the serialized transaction
163    ///
164    /// # Returns
165    ///
166    /// The transaction ID as a hex string on success
167    ///
168    /// # Errors
169    ///
170    /// Returns an error if:
171    /// - The request fails
172    /// - The server returns a non-202 status code
173    /// - The response is not a valid transaction ID
174    pub async fn submit_transaction(&self, cbor_data: &[u8]) -> Result<String> {
175        // Build URL from base URL
176        let url = format!("{}/submittx", self.base_url());
177
178        // Create request with CBOR data
179        let mut request = reqwest::Client::new()
180            .post(&url)
181            .header("Content-Type", "application/cbor")
182            .body(cbor_data.to_vec());
183
184        // Add authorization if token is present
185        if let Some(token) = self.auth_token() {
186            request = request.header("Authorization", format!("Bearer {}", token));
187        }
188
189        // Send request and handle response
190        let response = request.send().await?;
191
192        match response.status() {
193            StatusCode::ACCEPTED => {
194                let tx_id: String = response.text().await?;
195
196                // Validate transaction ID format (64 character hex string)
197                if tx_id.len() == 64 && tx_id.chars().all(|c| c.is_ascii_hexdigit()) {
198                    Ok(tx_id)
199                } else {
200                    Err(Error::Api {
201                        status: 500,
202                        message: "Invalid transaction ID format in response".to_string(),
203                    })
204                }
205            }
206            StatusCode::BAD_REQUEST => {
207                let message: String = response
208                    .text()
209                    .await
210                    .unwrap_or_else(|_| "Transaction submission failed".to_string());
211                Err(Error::Api {
212                    status: 400,
213                    message,
214                })
215            }
216            status => {
217                let message: String = response
218                    .text()
219                    .await
220                    .unwrap_or_else(|_| "Unknown error".to_string());
221                Err(Error::Api {
222                    status: status.as_u16(),
223                    message,
224                })
225            }
226        }
227    }
228    /// Get the number of block confirmations for a given transaction hash list
229    ///
230    /// # Arguments
231    ///
232    /// * `tx_hashes` - List of transaction hashes to query
233    ///
234    /// # Examples
235    ///
236    /// ```no_run
237    /// use koios_sdk::Client;
238    ///
239    /// #[tokio::main]
240    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
241    ///     let client = Client::new()?;
242    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
243    ///     let status = client.get_transaction_status(&tx_hashes).await?;
244    ///     println!("Transaction status: {:?}", status);
245    ///     Ok(())
246    /// }
247    /// ```
248    pub async fn get_transaction_status(
249        &self,
250        tx_hashes: &[String],
251    ) -> Result<Vec<TransactionStatus>> {
252        let request = TransactionIdsRequest {
253            tx_hashes: tx_hashes.to_vec(),
254        };
255        self.post("/tx_status", &request).await
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use serde_json::json;
263    use wiremock::matchers::{method, path};
264    use wiremock::{Mock, MockServer, ResponseTemplate};
265
266    #[tokio::test]
267    async fn test_get_transaction_metadata() {
268        let mock_server = MockServer::start().await;
269        let client = Client::builder()
270            .base_url(mock_server.uri())
271            .build()
272            .unwrap();
273
274        let tx_hash = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
275        let mock_response = json!([{
276            "tx_hash": tx_hash,
277            "metadata": {
278                "1": {
279                    "key": "value"
280                }
281            }
282        }]);
283
284        Mock::given(method("POST"))
285            .and(path("/tx_metadata"))
286            .respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
287            .mount(&mock_server)
288            .await;
289
290        let response = client
291            .get_transaction_metadata(&[tx_hash.to_string()])
292            .await
293            .unwrap();
294        assert_eq!(response.len(), 1);
295        assert_eq!(response[0].tx_hash, tx_hash);
296    }
297
298    // Add more tests for other endpoints...
299}