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}