use zera_proto::zera_api::{
BalanceRequest, BalanceResponse, BaseFeeRequest, BaseFeeResponse, NonceRequest, NonceResponse,
TokenFeeInfoRequest, TokenFeeInfoResponse,
};
use zera_proto::zera_txn::{PublicKey, TransactionType};
use crate::error::{Result, ZeraError};
use crate::grpc::paths;
use crate::grpc::transport::{unary, GrpcWebTransport, UnaryTransport};
use crate::types::RpcConfig;
#[derive(Clone)]
pub struct ValidatorApiClient<T = GrpcWebTransport> {
transport: T,
}
impl ValidatorApiClient<GrpcWebTransport> {
pub fn new(config: RpcConfig) -> Result<Self> {
Ok(Self {
transport: GrpcWebTransport::new(config)?,
})
}
}
impl<T> ValidatorApiClient<T>
where
T: UnaryTransport,
{
pub fn with_transport(transport: T) -> Self {
Self { transport }
}
pub fn transport(&self) -> &T {
&self.transport
}
pub async fn get_nonce(&self, address: &str) -> Result<NonceResponse> {
let request = NonceRequest {
wallet_address: decode_wallet_address(address)?,
encoded: false,
};
unary(&self.transport, &paths::api_path("Nonce"), &request).await
}
pub async fn get_balance(&self, address: &str, contract_id: &str) -> Result<BalanceResponse> {
let request = BalanceRequest {
wallet_address: decode_wallet_address(address)?,
contract_id: contract_id.to_string(),
encoded: false,
};
unary(&self.transport, &paths::api_path("Balance"), &request).await
}
pub async fn get_base_fee(
&self,
public_key: Option<PublicKey>,
txn_type: TransactionType,
) -> Result<BaseFeeResponse> {
let request = BaseFeeRequest {
public_key,
txn_type: txn_type as i32,
};
unary(&self.transport, &paths::api_path("BaseFee"), &request).await
}
pub async fn get_token_fee_info(
&self,
contract_ids: &[String],
) -> Result<TokenFeeInfoResponse> {
let request = TokenFeeInfoRequest {
contract_ids: contract_ids.to_vec(),
};
unary(
&self.transport,
&paths::api_path("GetTokenFeeInfo"),
&request,
)
.await
}
}
pub fn create_validator_api_client(
config: RpcConfig,
) -> Result<ValidatorApiClient<GrpcWebTransport>> {
ValidatorApiClient::new(config)
}
fn decode_wallet_address(address: &str) -> Result<Vec<u8>> {
let sanitized = address
.trim()
.strip_prefix("sc_")
.or_else(|| address.trim().strip_prefix("gov_"))
.or_else(|| address.trim().strip_prefix("r_"))
.unwrap_or(address.trim());
if sanitized.is_empty() {
return Err(ZeraError::InvalidInput(
"Address must be a non-empty string".to_string(),
));
}
Ok(bs58::decode(sanitized).into_vec()?)
}