koios-sdk 0.1.1

A Rust SDK for the Koios Cardano API
Documentation
use crate::error::{Error, Result};
use crate::models::UtxoInfo;
use crate::{
    models::{
        requests::{TransactionIdsRequest, TransactionInfoRequest, UtxoRefsWithExtendedRequest},
        transaction::{
            TransactionCbor, TransactionInfo, TransactionMetadata, TransactionStatus, TxMetaLabels,
        },
    },
    Client,
};
use reqwest::StatusCode;

impl Client {
    /// Get UTxO set for requested UTxO references
    ///
    /// # Arguments
    ///
    /// * `utxo_refs` - List of UTxO references to query
    /// * `extended` - Optional flag to include extended information
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use koios_sdk::Client;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::new()?;
    ///     let utxo_refs = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef#1".to_string()];
    ///     let utxo_info = client.get_utxo_info(&utxo_refs, Some(true)).await?;
    ///     println!("UTxO info: {:?}", utxo_info);
    ///     Ok(())
    /// }
    /// ```
    pub async fn get_utxo_info(
        &self,
        utxo_refs: &[String],
        extended: Option<bool>,
    ) -> Result<Vec<UtxoInfo>> {
        let request = UtxoRefsWithExtendedRequest {
            utxo_refs: utxo_refs.to_vec(),
            extended,
        };
        self.post("/utxo_info", &request).await
    }

    /// Get raw transaction(s) in CBOR format
    ///
    /// # Arguments
    ///
    /// * `tx_hashes` - List of transaction hashes to query
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use koios_sdk::Client;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::new()?;
    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
    ///     let tx_cbor = client.get_transaction_cbor(&tx_hashes).await?;
    ///     println!("Transaction CBOR: {:?}", tx_cbor);
    ///     Ok(())
    /// }
    /// ```
    pub async fn get_transaction_cbor(&self, tx_hashes: &[String]) -> Result<Vec<TransactionCbor>> {
        let request = TransactionIdsRequest {
            tx_hashes: tx_hashes.to_vec(),
        };
        self.post("/tx_cbor", &request).await
    }

    /// Get detailed information about transaction(s)
    ///
    /// # Arguments
    ///
    /// * `tx_hashes` - List of transaction hashes to query
    /// * `options` - Optional parameters for customizing the response
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use koios_sdk::Client;
    /// use koios_sdk::models::requests::TransactionInfoRequest;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::new()?;
    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
    ///     let options = TransactionInfoRequest {
    ///         tx_hashes,
    ///         inputs: Some(true),
    ///         ..Default::default()
    ///     };
    ///     let tx_info = client.get_transaction_info(&options).await?;
    ///     println!("Transaction info: {:?}", tx_info);
    ///     Ok(())
    /// }
    /// ```
    pub async fn get_transaction_info(
        &self,
        options: &TransactionInfoRequest,
    ) -> Result<Vec<TransactionInfo>> {
        self.post("/tx_info", options).await
    }

    /// Get metadata information (if any) for given transaction(s)
    ///
    /// # Arguments
    ///
    /// * `tx_hashes` - List of transaction hashes to query
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use koios_sdk::Client;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::new()?;
    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
    ///     let metadata = client.get_transaction_metadata(&tx_hashes).await?;
    ///     println!("Transaction metadata: {:?}", metadata);
    ///     Ok(())
    /// }
    /// ```
    pub async fn get_transaction_metadata(
        &self,
        tx_hashes: &[String],
    ) -> Result<Vec<TransactionMetadata>> {
        let request = TransactionIdsRequest {
            tx_hashes: tx_hashes.to_vec(),
        };
        self.post("/tx_metadata", &request).await
    }

    /// Get a list of all transaction metalabels
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use koios_sdk::Client;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::new()?;
    ///     let metalabels = client.get_transaction_metalabels().await?;
    ///     println!("Transaction metalabels: {:?}", metalabels);
    ///     Ok(())
    /// }
    /// ```
    pub async fn get_transaction_metalabels(&self) -> Result<Vec<TxMetaLabels>> {
        self.get("/tx_metalabels").await
    }

    /// Submit an already serialized transaction to the network
    ///
    /// # Arguments
    ///
    /// * `cbor_data` - Raw CBOR data of the serialized transaction
    ///
    /// # Returns
    ///
    /// The transaction ID as a hex string on success
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - The request fails
    /// - The server returns a non-202 status code
    /// - The response is not a valid transaction ID
    pub async fn submit_transaction(&self, cbor_data: &[u8]) -> Result<String> {
        // Build URL from base URL
        let url = format!("{}/submittx", self.base_url());

        // Create request with CBOR data
        let mut request = reqwest::Client::new()
            .post(&url)
            .header("Content-Type", "application/cbor")
            .body(cbor_data.to_vec());

        // Add authorization if token is present
        if let Some(token) = self.auth_token() {
            request = request.header("Authorization", format!("Bearer {}", token));
        }

        // Send request and handle response
        let response = request.send().await?;

        match response.status() {
            StatusCode::ACCEPTED => {
                let tx_id: String = response.text().await?;

                // Validate transaction ID format (64 character hex string)
                if tx_id.len() == 64 && tx_id.chars().all(|c| c.is_ascii_hexdigit()) {
                    Ok(tx_id)
                } else {
                    Err(Error::Api {
                        status: 500,
                        message: "Invalid transaction ID format in response".to_string(),
                    })
                }
            }
            StatusCode::BAD_REQUEST => {
                let message: String = response
                    .text()
                    .await
                    .unwrap_or_else(|_| "Transaction submission failed".to_string());
                Err(Error::Api {
                    status: 400,
                    message,
                })
            }
            status => {
                let message: String = response
                    .text()
                    .await
                    .unwrap_or_else(|_| "Unknown error".to_string());
                Err(Error::Api {
                    status: status.as_u16(),
                    message,
                })
            }
        }
    }
    /// Get the number of block confirmations for a given transaction hash list
    ///
    /// # Arguments
    ///
    /// * `tx_hashes` - List of transaction hashes to query
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use koios_sdk::Client;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = Client::new()?;
    ///     let tx_hashes = vec!["1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string()];
    ///     let status = client.get_transaction_status(&tx_hashes).await?;
    ///     println!("Transaction status: {:?}", status);
    ///     Ok(())
    /// }
    /// ```
    pub async fn get_transaction_status(
        &self,
        tx_hashes: &[String],
    ) -> Result<Vec<TransactionStatus>> {
        let request = TransactionIdsRequest {
            tx_hashes: tx_hashes.to_vec(),
        };
        self.post("/tx_status", &request).await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;
    use wiremock::matchers::{method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    #[tokio::test]
    async fn test_get_transaction_metadata() {
        let mock_server = MockServer::start().await;
        let client = Client::builder()
            .base_url(mock_server.uri())
            .build()
            .unwrap();

        let tx_hash = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
        let mock_response = json!([{
            "tx_hash": tx_hash,
            "metadata": {
                "1": {
                    "key": "value"
                }
            }
        }]);

        Mock::given(method("POST"))
            .and(path("/tx_metadata"))
            .respond_with(ResponseTemplate::new(200).set_body_json(&mock_response))
            .mount(&mock_server)
            .await;

        let response = client
            .get_transaction_metadata(&[tx_hash.to_string()])
            .await
            .unwrap();
        assert_eq!(response.len(), 1);
        assert_eq!(response[0].tx_hash, tx_hash);
    }

    // Add more tests for other endpoints...
}