use std::str::FromStr;
use bigdecimal::BigDecimal;
use zera_proto::zera_api::BalanceResponse;
use crate::error::{Result, ZeraError};
use crate::grpc::{UnaryTransport, ValidatorApiClient};
use crate::types::RpcConfig;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnhancedBalanceResponse {
pub balance: String,
pub denomination: String,
pub rate: String,
pub balance_nice: String,
pub rate_nice: String,
}
pub async fn get_balance(
address: &str,
contract_id: &str,
config: RpcConfig,
) -> Result<EnhancedBalanceResponse> {
let client = ValidatorApiClient::new(config)?;
get_balance_with_client(address, contract_id, &client).await
}
pub async fn get_balance_with_client<T>(
address: &str,
contract_id: &str,
client: &ValidatorApiClient<T>,
) -> Result<EnhancedBalanceResponse>
where
T: UnaryTransport,
{
if address.trim().is_empty() {
return Err(ZeraError::InvalidInput(
"Address must be a non-empty string".to_string(),
));
}
if contract_id.trim().is_empty() {
return Err(ZeraError::InvalidInput(
"Contract ID must be a non-empty string".to_string(),
));
}
let response = match client.get_balance(address, contract_id).await {
Ok(response) => response,
Err(error) => {
let message = error.to_string();
if message.contains("Invalid Wallet") || message.contains("INVALID_ARGUMENT") {
BalanceResponse {
balance: "0".to_string(),
denomination: "1".to_string(),
rate: "0".to_string(),
}
} else {
return Err(ZeraError::Rpc(format!(
"Failed to get balance from validator: {message}"
)));
}
}
};
enhance_balance_response(response)
}
pub fn enhance_balance_response(response: BalanceResponse) -> Result<EnhancedBalanceResponse> {
let denomination = if response.denomination.is_empty() {
"1".to_string()
} else {
response.denomination.clone()
};
let rate = if response.rate.is_empty() {
"0".to_string()
} else {
response.rate.clone()
};
let balance_nice = decimal_ratio(&response.balance, &denomination)?;
let rate_nice = decimal_ratio(&rate, "1000000000000000000")?;
Ok(EnhancedBalanceResponse {
balance: response.balance,
denomination,
rate,
balance_nice,
rate_nice,
})
}
fn decimal_ratio(numerator: &str, denominator: &str) -> Result<String> {
let numerator = BigDecimal::from_str(numerator).map_err(|error| {
ZeraError::Serialization(format!("Invalid decimal \"{numerator}\": {error}"))
})?;
let denominator = BigDecimal::from_str(denominator).map_err(|error| {
ZeraError::Serialization(format!("Invalid decimal \"{denominator}\": {error}"))
})?;
if denominator == 0u32 {
return Ok("0".to_string());
}
Ok((numerator / denominator).normalized().to_string())
}