zera-sdk 0.1.0

Rust SDK for ZERA transactions, validator APIs, and bridge workflows
Documentation
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()?)
}