entropycli 1.0.3

Entropy CLI for developing using the Entropic Labs Entropy Beacon
use super::{network::Network, response::TxResponse, wallet::Wallet, tx::TxError};

use serde::Serialize;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum QueryError {
    #[error("Network Error: {0}")]
    NetworkError(#[from] reqwest::Error),
    #[error("Cosmrs Error: {0}")]
    CosmrsError(#[from] cosmrs::ErrorReport),
    #[error("Account \"{0}\" not found")]
    AccountNotFound(String),
    #[error("Error parsing response: {0}")]
    ParseError(String),
}

impl Wallet {
    pub async fn account_number_and_sequence(&self) -> Result<(u64, u64), QueryError> {
        let path = format!("cosmos/auth/v1beta1/accounts/{}", self.address);
        let response = self.network.get(&path).await?;

        let json: serde_json::Value = response.json().await?;

        let account_number = json["account"]["account_number"]
            .as_str()
            .map(|s| {
                s.parse::<u64>()
                    .map_err(|e| QueryError::ParseError(e.to_string()))
            })
            .transpose()?
            .ok_or_else(|| QueryError::AccountNotFound(self.address.to_string()))?;
        let sequence = json["account"]["sequence"]
            .as_str()
            .map(|s| {
                s.parse::<u64>()
                    .map_err(|e| QueryError::ParseError(e.to_string()))
            })
            .transpose()?
            .ok_or_else(|| QueryError::AccountNotFound(self.address.to_string()))?;

        Ok((account_number, sequence))
    }

    pub async fn block_height(&self) -> Result<u32, QueryError> {
        let response = self.network.get("blocks/latest").await?;
        let json: serde_json::Value = response.json().await?;

        let height = json["block"]["header"]["height"]
            .as_str()
            .map(|s| {
                s.parse::<u32>()
                    .map_err(|e| QueryError::ParseError(e.to_string()))
            })
            .transpose()?
            .ok_or_else(|| QueryError::ParseError("Failed to parse block height".to_string()))?;

        Ok(height)
    }

    pub async fn wait_for_hash(&self, tx_hash: String) -> Result<TxResponse, TxError> {
        loop {
            let res = self
                .network
                .get(&format!("cosmos/tx/v1beta1/txs/{}", tx_hash))
                .await
                .map_err(QueryError::NetworkError)?;

            let res = res.json::<serde_json::Value>().await?;

            if res["code"].as_u64().map_or(false, |c| c == 5) {
                tokio::time::sleep(std::time::Duration::from_millis(200)).await;
                continue;
            }

            let res = serde_json::from_value::<TxResponse>(res["tx_response"].clone())
                .map_err(|e| QueryError::ParseError(e.to_string()))?;

            if res.code != 0 {
                return Err(TxError::TxFailed(res));
            }
            return Ok(res);
        }
    }
}

impl Network {
    pub async fn query(
        &self,
        address: String,
        query: impl Serialize,
    ) -> Result<serde_json::Value, QueryError> {
        let path = format!(
            "cosmwasm/wasm/v1/contract/{address}/smart/{query_data}",
            address = address,
            query_data = base64::encode(
                serde_json::to_string(&query).map_err(|e| QueryError::ParseError(e.to_string()))?
            )
        );
        let response = self.get(&path).await?;
        let json: serde_json::Value = response.json().await?;
        if json["code"].as_u64().is_some() {
            return Err(QueryError::ParseError(json["message"].to_string()));
        }

        Ok(json["data"].clone())
    }
}